From 8dd46f8bd7278b74a91d49d5993a091aaa0d57f5 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 19 May 2026 12:21:58 -0700 Subject: [PATCH 01/10] feat(integrations): add Gong incident.io Railway and New Relic --- apps/docs/components/icons.tsx | 23 + apps/docs/components/ui/icon-mapping.ts | 4 + apps/docs/content/docs/en/tools/gong.mdx | 33 +- .../docs/content/docs/en/tools/incidentio.mdx | 115 +++- apps/docs/content/docs/en/tools/meta.json | 2 + apps/docs/content/docs/en/tools/new_relic.mdx | 143 +++++ apps/docs/content/docs/en/tools/railway.mdx | 332 +++++++++++ .../integrations/data/icon-mapping.ts | 4 + .../integrations/data/integrations.json | 124 +++- apps/sim/blocks/blocks/gong.ts | 140 ++++- apps/sim/blocks/blocks/incidentio.ts | 437 +++++++++----- apps/sim/blocks/blocks/new_relic.ts | 343 +++++++++++ apps/sim/blocks/blocks/railway.ts | 534 ++++++++++++++++++ apps/sim/blocks/registry.ts | 4 + apps/sim/components/icons.tsx | 23 + apps/sim/tools/gong/aggregate_activity.ts | 5 +- apps/sim/tools/gong/answered_scorecards.ts | 5 +- apps/sim/tools/gong/create_call.ts | 157 +++++ apps/sim/tools/gong/get_call.ts | 3 +- apps/sim/tools/gong/get_call_transcript.ts | 3 +- apps/sim/tools/gong/get_coaching.ts | 3 +- apps/sim/tools/gong/get_extensive_calls.ts | 5 +- apps/sim/tools/gong/get_folder_content.ts | 3 +- apps/sim/tools/gong/get_user.ts | 3 +- apps/sim/tools/gong/index.ts | 4 + apps/sim/tools/gong/interaction_stats.ts | 5 +- apps/sim/tools/gong/list_calls.ts | 12 +- apps/sim/tools/gong/list_flows.ts | 7 +- apps/sim/tools/gong/list_library_folders.ts | 3 +- apps/sim/tools/gong/list_scorecards.ts | 3 +- apps/sim/tools/gong/list_trackers.ts | 3 +- apps/sim/tools/gong/list_users.ts | 3 +- apps/sim/tools/gong/list_workspaces.ts | 3 +- apps/sim/tools/gong/lookup_email.ts | 3 +- apps/sim/tools/gong/lookup_phone.ts | 3 +- apps/sim/tools/gong/types.ts | 27 +- apps/sim/tools/gong/utils.ts | 68 +++ apps/sim/tools/incidentio/actions_show.ts | 2 +- .../tools/incidentio/custom_fields_delete.ts | 2 +- .../tools/incidentio/custom_fields_show.ts | 2 +- .../tools/incidentio/custom_fields_update.ts | 2 +- .../incidentio/escalation_paths_create.ts | 4 +- .../incidentio/escalation_paths_delete.ts | 2 +- .../tools/incidentio/escalation_paths_list.ts | 96 ++++ .../tools/incidentio/escalation_paths_show.ts | 4 +- .../incidentio/escalation_paths_update.ts | 19 +- apps/sim/tools/incidentio/escalations_list.ts | 34 +- apps/sim/tools/incidentio/escalations_show.ts | 2 +- apps/sim/tools/incidentio/follow_ups_show.ts | 2 +- .../tools/incidentio/incident_roles_delete.ts | 2 +- .../tools/incidentio/incident_roles_show.ts | 2 +- .../tools/incidentio/incident_roles_update.ts | 2 +- .../incidentio/incident_timestamps_show.ts | 2 +- apps/sim/tools/incidentio/incidents_show.ts | 2 +- apps/sim/tools/incidentio/incidents_update.ts | 2 +- apps/sim/tools/incidentio/index.ts | 4 + .../tools/incidentio/schedule_entries_list.ts | 55 +- .../incidentio/schedule_overrides_create.ts | 8 + apps/sim/tools/incidentio/schedules_delete.ts | 2 +- apps/sim/tools/incidentio/schedules_show.ts | 2 +- apps/sim/tools/incidentio/schedules_update.ts | 2 +- apps/sim/tools/incidentio/types.ts | 74 ++- apps/sim/tools/incidentio/users_show.ts | 2 +- apps/sim/tools/incidentio/workflows_create.ts | 12 +- apps/sim/tools/incidentio/workflows_delete.ts | 2 +- apps/sim/tools/incidentio/workflows_list.ts | 42 +- apps/sim/tools/incidentio/workflows_show.ts | 8 +- apps/sim/tools/incidentio/workflows_update.ts | 100 +++- .../new_relic/create_deployment_event.ts | 381 +++++++++++++ apps/sim/tools/new_relic/get_entity.ts | 93 +++ apps/sim/tools/new_relic/index.ts | 5 + apps/sim/tools/new_relic/nrql_query.ts | 111 ++++ apps/sim/tools/new_relic/search_entities.ts | 131 +++++ apps/sim/tools/new_relic/types.ts | 100 ++++ apps/sim/tools/new_relic/utils.ts | 42 ++ apps/sim/tools/railway/create_environment.ts | 123 ++++ apps/sim/tools/railway/create_project.ts | 109 ++++ apps/sim/tools/railway/delete_environment.ts | 82 +++ apps/sim/tools/railway/delete_project.ts | 82 +++ apps/sim/tools/railway/deploy_service.ts | 88 +++ apps/sim/tools/railway/get_project.ts | 166 ++++++ apps/sim/tools/railway/index.ts | 31 + apps/sim/tools/railway/list_deployments.ts | 170 ++++++ .../sim/tools/railway/list_project_members.ts | 123 ++++ apps/sim/tools/railway/list_projects.ts | 145 +++++ apps/sim/tools/railway/list_variables.ts | 101 ++++ apps/sim/tools/railway/transfer_project.ts | 91 +++ apps/sim/tools/railway/types.ts | 236 ++++++++ apps/sim/tools/railway/update_project.ts | 112 ++++ apps/sim/tools/railway/upsert_variable.ts | 122 ++++ apps/sim/tools/railway/utils.ts | 55 ++ apps/sim/tools/registry.ts | 56 +- 92 files changed, 5455 insertions(+), 388 deletions(-) create mode 100644 apps/docs/content/docs/en/tools/new_relic.mdx create mode 100644 apps/docs/content/docs/en/tools/railway.mdx create mode 100644 apps/sim/blocks/blocks/new_relic.ts create mode 100644 apps/sim/blocks/blocks/railway.ts create mode 100644 apps/sim/tools/gong/create_call.ts create mode 100644 apps/sim/tools/gong/utils.ts create mode 100644 apps/sim/tools/incidentio/escalation_paths_list.ts create mode 100644 apps/sim/tools/new_relic/create_deployment_event.ts create mode 100644 apps/sim/tools/new_relic/get_entity.ts create mode 100644 apps/sim/tools/new_relic/index.ts create mode 100644 apps/sim/tools/new_relic/nrql_query.ts create mode 100644 apps/sim/tools/new_relic/search_entities.ts create mode 100644 apps/sim/tools/new_relic/types.ts create mode 100644 apps/sim/tools/new_relic/utils.ts create mode 100644 apps/sim/tools/railway/create_environment.ts create mode 100644 apps/sim/tools/railway/create_project.ts create mode 100644 apps/sim/tools/railway/delete_environment.ts create mode 100644 apps/sim/tools/railway/delete_project.ts create mode 100644 apps/sim/tools/railway/deploy_service.ts create mode 100644 apps/sim/tools/railway/get_project.ts create mode 100644 apps/sim/tools/railway/index.ts create mode 100644 apps/sim/tools/railway/list_deployments.ts create mode 100644 apps/sim/tools/railway/list_project_members.ts create mode 100644 apps/sim/tools/railway/list_projects.ts create mode 100644 apps/sim/tools/railway/list_variables.ts create mode 100644 apps/sim/tools/railway/transfer_project.ts create mode 100644 apps/sim/tools/railway/types.ts create mode 100644 apps/sim/tools/railway/update_project.ts create mode 100644 apps/sim/tools/railway/upsert_variable.ts create mode 100644 apps/sim/tools/railway/utils.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 81dbe58d948..9cabce46fed 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 ( @@ -6950,3 +6958,18 @@ export function SnowflakeIcon(props: SVGProps) { ) } + +export function NewRelicIcon(props: SVGProps) { + return ( + + + + + ) +} diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index dd5ab47b8cb..6b419847e35 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, @@ -345,6 +347,7 @@ export const blockTypeToIconMap: Record = { mongodb: MongoDBIcon, mysql: MySQLIcon, neo4j: Neo4jIcon, + new_relic: NewRelicIcon, notion: NotionIcon, notion_v2: NotionIcon, obsidian: ObsidianIcon, @@ -368,6 +371,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..2b1731d7b39 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 | Yes | End date/time in ISO-8601 format \(e.g., 2024-01-31T23:59:59Z\) | | `cursor` | string | No | Pagination cursor from a previous response | | `workspaceId` | string | No | Gong workspace ID to filter calls | @@ -80,6 +80,37 @@ Retrieve call data by date range from Gong. | `cursor` | string | Pagination cursor for the next page | | `totalRecords` | number | Total number of records matching the filter | +### `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` Retrieve detailed data for a specific call from Gong. diff --git a/apps/docs/content/docs/en/tools/incidentio.mdx b/apps/docs/content/docs/en/tools/incidentio.mdx index 3476d3535e7..fe85c7859bf 100644 --- a/apps/docs/content/docs/en/tools/incidentio.mdx +++ b/apps/docs/content/docs/en/tools/incidentio.mdx @@ -440,8 +440,6 @@ 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 @@ -454,9 +452,42 @@ List all workflows in your incident.io workspace. | ↳ `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 | + +### `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 | 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 | +| `management_meta` | json | Workflow management metadata | ### `incidentio_workflows_show` @@ -480,6 +511,7 @@ Get details of a specific workflow in incident.io. | ↳ `folder` | string | Folder the workflow belongs to | | ↳ `created_at` | string | When the workflow was created | | ↳ `updated_at` | string | When the workflow was last updated | +| `management_meta` | json | Workflow management metadata | ### `incidentio_workflows_update` @@ -491,9 +523,18 @@ 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 @@ -506,6 +547,7 @@ Update an existing workflow in incident.io. | ↳ `folder` | string | Folder the workflow belongs to | | ↳ `created_at` | string | When the workflow was created | | ↳ `updated_at` | string | When the workflow was last updated | +| `management_meta` | json | Workflow management metadata | ### `incidentio_workflows_delete` @@ -647,6 +689,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 +701,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 +1143,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 +1166,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 +1180,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 +1192,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 +1249,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 +1280,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 +1291,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 +1314,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 5f04f102bb8..90d9718c49f 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..80890633f7e --- /dev/null +++ b/apps/docs/content/docs/en/tools/railway.mdx @@ -0,0 +1,332 @@ +--- +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 | +| `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 | +| `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 | +| `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 | + +#### 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 | + +#### 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 | + +#### 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 64fe8a8556d..6fe7fbf598d 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, @@ -330,6 +332,7 @@ export const blockTypeToIconMap: Record = { mongodb: MongoDBIcon, mysql: MySQLIcon, neo4j: Neo4jIcon, + new_relic: NewRelicIcon, notion_v2: NotionIcon, obsidian: ObsidianIcon, okta: OktaIcon, @@ -351,6 +354,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 d67628c4ab8..cbd62c85c84 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", @@ -5202,7 +5206,7 @@ }, { "name": "Write to Document", - "description": "Write or update content in a Google Docs document" + "description": "Append content to a Google Docs document. Content is inserted literally; Markdown is not interpreted. For formatted output from Markdown, use the Create operation with the markdown toggle enabled." }, { "name": "Create Document", @@ -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..6ec331731d9 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,7 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes field: 'operation', value: ['list_calls'], }, - mode: 'advanced', + required: { field: 'operation', value: 'list_calls' }, wandConfig: { enabled: true, prompt: `Generate an ISO 8601 timestamp based on the user's description. @@ -349,6 +471,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 +615,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 +658,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 +704,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..0eae4a5e3d4 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,15 @@ 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: 'summary', title: 'Summary', @@ -165,15 +151,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 +197,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 +240,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 +285,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,20 +328,6 @@ Return ONLY the title - no explanations.`, placeholder: 'Enter user IDs, comma-separated (required if no path ID)...', condition: { field: 'operation', value: 'incidentio_escalations_create' }, }, - { - id: 'name', - title: 'Name', - type: 'short-input', - placeholder: 'Enter name...', - condition: { - field: 'operation', - value: [ - 'incidentio_incidents_update', - 'incidentio_workflows_update', - 'incidentio_schedules_update', - ], - }, - }, // Actions List inputs { id: 'incident_id', @@ -352,7 +336,11 @@ Return ONLY the title - no explanations.`, placeholder: 'Filter by incident ID...', condition: { field: 'operation', - value: ['incidentio_actions_list', 'incidentio_follow_ups_list'], + value: [ + 'incidentio_actions_list', + 'incidentio_follow_ups_list', + 'incidentio_incident_updates_list', + ], }, }, // Workflows inputs @@ -361,7 +349,10 @@ 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: 'state', @@ -373,7 +364,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 +562,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 +597,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 +605,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 +642,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 +677,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 +738,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', @@ -712,7 +821,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 +843,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 +889,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 +919,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 +950,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 +1010,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 +1026,16 @@ 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) } return result }, @@ -953,6 +1063,28 @@ 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' }, + 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 +1096,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 +1125,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 +1152,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..649ce644c75 --- /dev/null +++ b/apps/sim/blocks/blocks/new_relic.ts @@ -0,0 +1,343 @@ +import { NewRelicIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import type { NewRelicResponse } from '@/tools/new_relic/types' + +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: params.customAttributes + ? typeof params.customAttributes === 'string' + ? JSON.parse(params.customAttributes) + : params.customAttributes + : undefined, + 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..e677a1301be --- /dev/null +++ b/apps/sim/blocks/blocks/railway.ts @@ -0,0 +1,534 @@ +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: '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: '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: '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: '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: '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, + first: params.first ? Number(params.first) : undefined, + after: params.after, + } + case 'create_project': + return { + ...baseParams, + name: params.createProjectName, + defaultEnvironmentName: params.defaultEnvironmentName, + prDeploys: params.prDeploys ? params.prDeploys === 'true' : undefined, + } + case 'update_project': + return { + ...baseParams, + projectId: params.updateProjectId, + name: params.updateProjectName, + description: params.updateProjectDescription, + } + 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, + } + 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, + } + 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' }, + 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' }, + 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' }, + 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' }, + 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' }, + 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 268e010543f..8c720e4804e 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' @@ -393,6 +395,7 @@ export const registry: Record = { mothership: MothershipBlock, mysql: MySQLBlock, neo4j: Neo4jBlock, + new_relic: NewRelicBlock, note: NoteBlock, notion: NotionBlock, notion_v2: NotionV2Block, @@ -417,6 +420,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 81dbe58d948..9cabce46fed 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 ( @@ -6950,3 +6958,18 @@ export function SnowflakeIcon(props: SVGProps) { ) } + +export function NewRelicIcon(props: SVGProps) { + return ( + + + + + ) +} diff --git a/apps/sim/tools/gong/aggregate_activity.ts b/apps/sim/tools/gong/aggregate_activity.ts index d0c3388b0e1..369afad09e7 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 } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const aggregateActivityTool: ToolConfig< @@ -74,9 +75,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..f80f12a6256 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 } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const answeredScorecardsTool: ToolConfig< @@ -102,9 +103,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..9445d6a7790 --- /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, + primaryUser: params.primaryUser.trim(), + parties: parseGongJsonArray(params.parties, 'parties'), + direction: params.direction, + downloadMediaUrl: params.downloadMediaUrl.trim(), + } + + if (params.title) body.title = params.title + if (params.workspaceId) body.workspaceId = params.workspaceId.trim() + if (params.disposition) body.disposition = params.disposition + if (params.purpose) body.purpose = params.purpose + if (params.context) body.context = parseGongJsonArray(params.context, 'context') + if (params.callProviderCode) 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..a14e92c2204 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 = { @@ -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..d24cbd7d76c 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 } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const getCallTranscriptTool: ToolConfig< @@ -79,7 +80,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..d41fcab311a 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 = { @@ -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..3e7b8e3a1cc 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 } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const getExtensiveCallsTool: ToolConfig< @@ -110,9 +111,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..d19aed1aa7e 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< @@ -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..1789ea54292 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 = { @@ -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..02126cf8ff2 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 } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const interactionStatsTool: ToolConfig< @@ -75,9 +76,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..f24957c5c47 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 = { @@ -28,10 +29,9 @@ 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) + url.searchParams.set('toDateTime', params.toDateTime) if (params.cursor) url.searchParams.set('cursor', params.cursor) - if (params.workspaceId) url.searchParams.set('workspaceId', params.workspaceId) + if (params.workspaceId) url.searchParams.set('workspaceId', params.workspaceId.trim()) return url.toString() }, method: 'GET', @@ -66,7 +66,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 ?? '', diff --git a/apps/sim/tools/gong/list_flows.ts b/apps/sim/tools/gong/list_flows.ts index 4f51ecc701b..604c2946ab2 100644 --- a/apps/sim/tools/gong/list_flows.ts +++ b/apps/sim/tools/gong/list_flows.ts @@ -1,4 +1,5 @@ import type { GongListFlowsParams, GongListFlowsResponse } from '@/tools/gong/types' +import { getGongErrorMessage } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const listFlowsTool: ToolConfig = { @@ -45,8 +46,8 @@ 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) + url.searchParams.set('flowEmailOwner', params.flowOwnerEmail.trim()) + if (params.workspaceId) url.searchParams.set('workspaceId', params.workspaceId.trim()) if (params.cursor) url.searchParams.set('cursor', params.cursor) return url.toString() }, @@ -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..72f0d67959e 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< @@ -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..fcc7163a369 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 = { @@ -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..8c9d1e7d2cf 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 = { @@ -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 ?? '', diff --git a/apps/sim/tools/gong/list_workspaces.ts b/apps/sim/tools/gong/list_workspaces.ts index a1e89c09d78..0611c1f3c22 100644 --- a/apps/sim/tools/gong/list_workspaces.ts +++ b/apps/sim/tools/gong/list_workspaces.ts @@ -1,4 +1,5 @@ import type { GongListWorkspacesParams, GongListWorkspacesResponse } from '@/tools/gong/types' +import { getGongErrorMessage } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const listWorkspacesTool: 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..ed9002f8f0a 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 = { @@ -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..eeb5ac2bd2d 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 = { @@ -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..d868ca8c136 100644 --- a/apps/sim/tools/gong/types.ts +++ b/apps/sim/tools/gong/types.ts @@ -9,11 +9,35 @@ interface GongBaseParams { /** List Calls */ export interface GongListCallsParams extends GongBaseParams { fromDateTime: string - toDateTime?: string + toDateTime: string cursor?: string 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 @@ -585,6 +609,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_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_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/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..1e9154dff8c 100644 --- a/apps/sim/tools/incidentio/schedule_entries_list.ts +++ b/apps/sim/tools/incidentio/schedule_entries_list.ts @@ -40,18 +40,6 @@ 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: { @@ -68,14 +56,6 @@ export const scheduleEntriesListTool: ToolConfig< queryParams.push(`entry_window_end=${encodeURIComponent(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}` }, @@ -92,7 +72,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 +84,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 +99,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..0594cd1ef7e 100644 --- a/apps/sim/tools/incidentio/types.ts +++ b/apps/sim/tools/incidentio/types.ts @@ -509,18 +509,11 @@ interface Workflow { } // 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 +536,7 @@ export interface WorkflowsCreateParams extends IncidentioBaseParams { export interface WorkflowsCreateResponse extends ToolResponse { output: { + management_meta?: Record workflow: Workflow } } @@ -554,6 +548,7 @@ export interface WorkflowsShowParams extends IncidentioBaseParams { export interface WorkflowsShowResponse extends ToolResponse { output: { + management_meta?: Record workflow: Workflow } } @@ -561,13 +556,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 } } @@ -784,13 +789,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 +811,10 @@ interface IncidentioEscalation { export interface IncidentioEscalationsListResponse extends ToolResponse { output: { escalations: IncidentioEscalation[] + pagination_meta?: { + after?: string + page_size: number + } } } @@ -1034,8 +1047,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 +1058,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 +1083,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 +1098,7 @@ interface IncidentioScheduleOverride { } export interface IncidentioScheduleOverridesCreateParams extends IncidentioBaseParams { + layer_id: string rotation_id: string schedule_id: string user_id?: string @@ -1122,8 +1137,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 +1191,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_show.ts b/apps/sim/tools/incidentio/users_show.ts index 64dbf7024f0..39e0af3ae7f 100644 --- a/apps/sim/tools/incidentio/users_show.ts +++ b/apps/sim/tools/incidentio/users_show.ts @@ -28,7 +28,7 @@ export const usersShowTool: 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/workflows_create.ts b/apps/sim/tools/incidentio/workflows_create.ts index 87c3e2cd023..fd45b7d3a3f 100644 --- a/apps/sim/tools/incidentio/workflows_create.ts +++ b/apps/sim/tools/incidentio/workflows_create.ts @@ -122,8 +122,7 @@ export const workflowsCreateTool: ToolConfig { - // Helper function to safely parse JSON strings - const parseJsonParam = (jsonString: string | undefined, defaultValue: any) => { + const parseJsonParam = (jsonString: string | undefined, defaultValue: unknown) => { if (!jsonString) return defaultValue try { return JSON.parse(jsonString) @@ -133,8 +132,7 @@ export const workflowsCreateTool: ToolConfig = { + const body: Record = { name: params.name, trigger: params.trigger || 'incident.updated', once_for: parseJsonParam(params.once_for, []), @@ -166,6 +164,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..620a68d6075 100644 --- a/apps/sim/tools/incidentio/workflows_list.ts +++ b/apps/sim/tools/incidentio/workflows_list.ts @@ -14,35 +14,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', @@ -64,12 +39,6 @@ export const workflowsListTool: ToolConfig `https://api.incident.io/v2/workflows/${params.id}`, + url: (params) => `https://api.incident.io/v2/workflows/${params.id.trim()}`, method: 'GET', headers: (params) => ({ 'Content-Type': 'application/json', @@ -37,6 +37,7 @@ export const workflowsShowTool: ToolConfig = { id: 'incidentio_workflows_update', name: 'incident.io Workflows Update', @@ -22,10 +25,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 parseJsonParam = (jsonString: string | undefined, defaultValue: unknown) => { + if (!jsonString) return defaultValue + try { + return JSON.parse(jsonString) + } catch (error) { + logger.warn(`Failed to parse JSON parameter: ${jsonString}`, { + error: error instanceof Error ? error.message : String(error), + }) + return defaultValue + } } - if (params.state) { - body.state = params.state + const body: Record = { + name: params.name, + once_for: parseJsonParam(params.once_for, []), + condition_groups: parseJsonParam(params.condition_groups, []), + steps: parseJsonParam(params.steps, []), + expressions: parseJsonParam(params.expressions, []), + include_private_incidents: params.include_private_incidents, + runs_on_incident_modes: parseJsonParam(params.runs_on_incident_modes, ['standard']), + continue_on_step_error: params.continue_on_step_error, + runs_on_incidents: params.runs_on_incidents, } - 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 = parseJsonParam(params.delay, undefined) return body }, @@ -72,6 +144,7 @@ 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() + 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..e016ccb3056 --- /dev/null +++ b/apps/sim/tools/railway/create_environment.ts @@ -0,0 +1,123 @@ +import type { + RailwayCreatedResource, + RailwayCreateEnvironmentParams, + RailwayCreateEnvironmentResponse, +} from '@/tools/railway/types' +import { + compactVariables, + 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', + }, + }, + + 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: params.sourceEnvironmentId?.trim(), + ephemeral: params.ephemeral, + skipInitialDeploys: params.skipInitialDeploys, + }), + }, + }), + }, + + 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..ba56ed1bb21 --- /dev/null +++ b/apps/sim/tools/railway/create_project.ts @@ -0,0 +1,109 @@ +import type { + RailwayCreatedResource, + RailwayCreateProjectParams, + RailwayCreateProjectResponse, +} from '@/tools/railway/types' +import { + compactVariables, + 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', + }, + 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(), + defaultEnvironmentName: params.defaultEnvironmentName?.trim(), + 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..17e21e1c5c0 --- /dev/null +++ b/apps/sim/tools/railway/deploy_service.ts @@ -0,0 +1,88 @@ +import type { + RailwayDeployServiceParams, + RailwayDeployServiceResponse, +} from '@/tools/railway/types' +import { + 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', + }, + }, + + request: { + url: RAILWAY_GRAPHQL_URL, + method: 'POST', + headers: (params) => railwayHeaders(params.apiKey, params.tokenType), + body: (params) => ({ + 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..2c5e6285fc1 --- /dev/null +++ b/apps/sim/tools/railway/list_deployments.ts @@ -0,0 +1,170 @@ +import type { + RailwayDeploymentSummary, + RailwayListDeploymentsParams, + RailwayListDeploymentsResponse, + RailwayPageInfo, +} from '@/tools/railway/types' +import { + 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: params.after?.trim(), + }, + }), + }, + + 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..0fe8cff9ba7 --- /dev/null +++ b/apps/sim/tools/railway/list_projects.ts @@ -0,0 +1,145 @@ +import type { + RailwayListProjectsParams, + RailwayListProjectsResponse, + RailwayPageInfo, + RailwayProjectSummary, +} from '@/tools/railway/types' +import { + compactVariables, + 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', + }, + 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($first: Int, $after: String) { + projects(first: $first, after: $after) { + edges { + node { + id + name + description + createdAt + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + `, + variables: compactVariables({ + first: params.first ? Number(params.first) : undefined, + after: params.after?.trim(), + }), + }), + }, + + 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, + })) + + 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' }, + }, + }, + }, + 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..b4165a81f9e --- /dev/null +++ b/apps/sim/tools/railway/list_variables.ts @@ -0,0 +1,101 @@ +import type { + RailwayListVariablesParams, + RailwayListVariablesResponse, +} from '@/tools/railway/types' +import { + compactVariables, + 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: params.serviceId?.trim(), + }), + }), + }, + + 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..6870b6c44cb --- /dev/null +++ b/apps/sim/tools/railway/types.ts @@ -0,0 +1,236 @@ +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 +} + +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 { + first?: number + after?: string +} + +export interface RailwayGetProjectParams extends RailwayAuthParams { + projectId: string +} + +export interface RailwayCreateProjectParams extends RailwayAuthParams { + name: string + defaultEnvironmentName?: string + prDeploys?: boolean +} + +export interface RailwayUpdateProjectParams extends RailwayAuthParams { + projectId: string + name?: string + description?: string +} + +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 +} + +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 +} + +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..5e91a6901d0 --- /dev/null +++ b/apps/sim/tools/railway/update_project.ts @@ -0,0 +1,112 @@ +import type { + RailwayUpdatedProject, + RailwayUpdateProjectParams, + RailwayUpdateProjectResponse, +} from '@/tools/railway/types' +import { + compactVariables, + 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', + }, + }, + + 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: params.name?.trim(), + description: params.description?.trim(), + }), + }, + }), + }, + + 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..c92c89df822 --- /dev/null +++ b/apps/sim/tools/railway/upsert_variable.ts @@ -0,0 +1,122 @@ +import type { + RailwayUpsertVariableParams, + RailwayUpsertVariableResponse, +} from '@/tools/railway/types' +import { + compactVariables, + 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: params.serviceId?.trim(), + 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..a0dd9d03ad2 --- /dev/null +++ b/apps/sim/tools/railway/utils.ts @@ -0,0 +1,55 @@ +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 && value !== '') + ) +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index dad88d1a940..16adf332c27 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, @@ -3262,22 +3285,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, @@ -3935,6 +3959,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, @@ -5301,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, @@ -5378,6 +5419,7 @@ export const tools: Record = { incidentio_schedule_entries_list: incidentioScheduleEntriesListTool, incidentio_schedule_overrides_create: incidentioScheduleOverridesCreateTool, 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, From dbd9f83802274b68176af1d274751e94b213be57 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 19 May 2026 12:41:31 -0700 Subject: [PATCH 02/10] fix(railway): preserve explicit empty variable values --- apps/sim/tools/railway/create_environment.ts | 3 ++- apps/sim/tools/railway/create_project.ts | 3 ++- apps/sim/tools/railway/list_deployments.ts | 3 ++- apps/sim/tools/railway/list_projects.ts | 3 ++- apps/sim/tools/railway/list_variables.ts | 3 ++- apps/sim/tools/railway/update_project.ts | 5 +++-- apps/sim/tools/railway/upsert_variable.ts | 3 ++- apps/sim/tools/railway/utils.ts | 9 ++++++--- 8 files changed, 21 insertions(+), 11 deletions(-) diff --git a/apps/sim/tools/railway/create_environment.ts b/apps/sim/tools/railway/create_environment.ts index e016ccb3056..15aa5312e92 100644 --- a/apps/sim/tools/railway/create_environment.ts +++ b/apps/sim/tools/railway/create_environment.ts @@ -5,6 +5,7 @@ import type { } from '@/tools/railway/types' import { compactVariables, + optionalString, parseRailwayGraphqlResponse, RAILWAY_GRAPHQL_URL, railwayHeaders, @@ -86,7 +87,7 @@ export const railwayCreateEnvironmentTool: ToolConfig< input: compactVariables({ projectId: params.projectId.trim(), name: params.name.trim(), - sourceEnvironmentId: params.sourceEnvironmentId?.trim(), + sourceEnvironmentId: optionalString(params.sourceEnvironmentId), ephemeral: params.ephemeral, skipInitialDeploys: params.skipInitialDeploys, }), diff --git a/apps/sim/tools/railway/create_project.ts b/apps/sim/tools/railway/create_project.ts index ba56ed1bb21..c531bca6897 100644 --- a/apps/sim/tools/railway/create_project.ts +++ b/apps/sim/tools/railway/create_project.ts @@ -5,6 +5,7 @@ import type { } from '@/tools/railway/types' import { compactVariables, + optionalString, parseRailwayGraphqlResponse, RAILWAY_GRAPHQL_URL, railwayHeaders, @@ -73,7 +74,7 @@ export const railwayCreateProjectTool: ToolConfig< variables: { input: compactVariables({ name: params.name.trim(), - defaultEnvironmentName: params.defaultEnvironmentName?.trim(), + defaultEnvironmentName: optionalString(params.defaultEnvironmentName), prDeploys: params.prDeploys, }), }, diff --git a/apps/sim/tools/railway/list_deployments.ts b/apps/sim/tools/railway/list_deployments.ts index 2c5e6285fc1..a7d0d7d3501 100644 --- a/apps/sim/tools/railway/list_deployments.ts +++ b/apps/sim/tools/railway/list_deployments.ts @@ -5,6 +5,7 @@ import type { RailwayPageInfo, } from '@/tools/railway/types' import { + optionalString, parseRailwayGraphqlResponse, RAILWAY_GRAPHQL_URL, railwayHeaders, @@ -105,7 +106,7 @@ export const railwayListDeploymentsTool: ToolConfig< environmentId: params.environmentId.trim(), }, first: params.first ? Number(params.first) : 10, - after: params.after?.trim(), + after: optionalString(params.after), }, }), }, diff --git a/apps/sim/tools/railway/list_projects.ts b/apps/sim/tools/railway/list_projects.ts index 0fe8cff9ba7..88818c4ecac 100644 --- a/apps/sim/tools/railway/list_projects.ts +++ b/apps/sim/tools/railway/list_projects.ts @@ -6,6 +6,7 @@ import type { } from '@/tools/railway/types' import { compactVariables, + optionalString, parseRailwayGraphqlResponse, RAILWAY_GRAPHQL_URL, railwayHeaders, @@ -82,7 +83,7 @@ export const railwayListProjectsTool: ToolConfig< `, variables: compactVariables({ first: params.first ? Number(params.first) : undefined, - after: params.after?.trim(), + after: optionalString(params.after), }), }), }, diff --git a/apps/sim/tools/railway/list_variables.ts b/apps/sim/tools/railway/list_variables.ts index b4165a81f9e..91150d6559e 100644 --- a/apps/sim/tools/railway/list_variables.ts +++ b/apps/sim/tools/railway/list_variables.ts @@ -4,6 +4,7 @@ import type { } from '@/tools/railway/types' import { compactVariables, + optionalString, parseRailwayGraphqlResponse, RAILWAY_GRAPHQL_URL, railwayHeaders, @@ -69,7 +70,7 @@ export const railwayListVariablesTool: ToolConfig< variables: compactVariables({ projectId: params.projectId.trim(), environmentId: params.environmentId.trim(), - serviceId: params.serviceId?.trim(), + serviceId: optionalString(params.serviceId), }), }), }, diff --git a/apps/sim/tools/railway/update_project.ts b/apps/sim/tools/railway/update_project.ts index 5e91a6901d0..b8df06ac737 100644 --- a/apps/sim/tools/railway/update_project.ts +++ b/apps/sim/tools/railway/update_project.ts @@ -5,6 +5,7 @@ import type { } from '@/tools/railway/types' import { compactVariables, + optionalString, parseRailwayGraphqlResponse, RAILWAY_GRAPHQL_URL, railwayHeaders, @@ -74,8 +75,8 @@ export const railwayUpdateProjectTool: ToolConfig< variables: { id: params.projectId.trim(), input: compactVariables({ - name: params.name?.trim(), - description: params.description?.trim(), + name: optionalString(params.name), + description: optionalString(params.description), }), }, }), diff --git a/apps/sim/tools/railway/upsert_variable.ts b/apps/sim/tools/railway/upsert_variable.ts index c92c89df822..120b9226496 100644 --- a/apps/sim/tools/railway/upsert_variable.ts +++ b/apps/sim/tools/railway/upsert_variable.ts @@ -4,6 +4,7 @@ import type { } from '@/tools/railway/types' import { compactVariables, + optionalString, parseRailwayGraphqlResponse, RAILWAY_GRAPHQL_URL, railwayHeaders, @@ -91,7 +92,7 @@ export const railwayUpsertVariableTool: ToolConfig< name: params.name.trim(), value: params.value, ...compactVariables({ - serviceId: params.serviceId?.trim(), + serviceId: optionalString(params.serviceId), skipDeploys: params.skipDeploys, }), }, diff --git a/apps/sim/tools/railway/utils.ts b/apps/sim/tools/railway/utils.ts index a0dd9d03ad2..02e1f37160f 100644 --- a/apps/sim/tools/railway/utils.ts +++ b/apps/sim/tools/railway/utils.ts @@ -49,7 +49,10 @@ export async function parseRailwayGraphqlResponse( } export function compactVariables(input: Record) { - return Object.fromEntries( - Object.entries(input).filter(([, value]) => value !== undefined && value !== '') - ) + 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 } From 21089bf02e3c8c13b7bf6d0bce847a583bbfbaa7 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 19 May 2026 12:43:35 -0700 Subject: [PATCH 03/10] fix(incidentio): fail on invalid workflow JSON --- apps/sim/tools/incidentio/utils.ts | 28 ++++++++++++++ apps/sim/tools/incidentio/workflows_create.ts | 30 ++++++--------- apps/sim/tools/incidentio/workflows_update.ts | 37 ++++++++----------- 3 files changed, 55 insertions(+), 40 deletions(-) create mode 100644 apps/sim/tools/incidentio/utils.ts diff --git a/apps/sim/tools/incidentio/utils.ts b/apps/sim/tools/incidentio/utils.ts new file mode 100644 index 00000000000..01c660533de --- /dev/null +++ b/apps/sim/tools/incidentio/utils.ts @@ -0,0 +1,28 @@ +function getJsonParseErrorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error) +} + +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) +} diff --git a/apps/sim/tools/incidentio/workflows_create.ts b/apps/sim/tools/incidentio/workflows_create.ts index fd45b7d3a3f..cec9d5d2a99 100644 --- a/apps/sim/tools/incidentio/workflows_create.ts +++ b/apps/sim/tools/incidentio/workflows_create.ts @@ -1,9 +1,7 @@ -import { createLogger } from '@sim/logger' import type { WorkflowsCreateParams, WorkflowsCreateResponse } from '@/tools/incidentio/types' +import { 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,25 +120,19 @@ export const workflowsCreateTool: ToolConfig { - const parseJsonParam = (jsonString: string | undefined, defaultValue: unknown) => { - if (!jsonString) return defaultValue - try { - return JSON.parse(jsonString) - } catch (error) { - logger.warn(`Failed to parse JSON parameter: ${jsonString}`, error) - return defaultValue - } - } - 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', @@ -151,7 +143,7 @@ export const workflowsCreateTool: ToolConfig = { id: 'incidentio_workflows_update', name: 'incident.io Workflows Update', @@ -106,33 +107,27 @@ export const workflowsUpdateTool: ToolConfig { - const parseJsonParam = (jsonString: string | undefined, defaultValue: unknown) => { - if (!jsonString) return defaultValue - try { - return JSON.parse(jsonString) - } catch (error) { - logger.warn(`Failed to parse JSON parameter: ${jsonString}`, { - error: error instanceof Error ? error.message : String(error), - }) - return defaultValue - } - } - const body: Record = { name: params.name, - once_for: parseJsonParam(params.once_for, []), - condition_groups: parseJsonParam(params.condition_groups, []), - steps: parseJsonParam(params.steps, []), - expressions: parseJsonParam(params.expressions, []), + 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: parseJsonParam(params.runs_on_incident_modes, ['standard']), + 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.delay) body.delay = parseJsonParam(params.delay, undefined) + if (params.delay) body.delay = parseIncidentioJsonParam(params.delay, 'delay', undefined) return body }, From 172dce2091bf8c7c27f20a874d89f1c15e11c30d Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 19 May 2026 12:44:38 -0700 Subject: [PATCH 04/10] fix(new-relic): validate custom attributes JSON --- apps/sim/blocks/blocks/new_relic.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/apps/sim/blocks/blocks/new_relic.ts b/apps/sim/blocks/blocks/new_relic.ts index 649ce644c75..d9001b0df26 100644 --- a/apps/sim/blocks/blocks/new_relic.ts +++ b/apps/sim/blocks/blocks/new_relic.ts @@ -1,7 +1,23 @@ import { NewRelicIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' -import type { NewRelicResponse } from '@/tools/new_relic/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', @@ -291,11 +307,7 @@ Return ONLY the numeric timestamp - no explanations, no extra text.`, deepLink: params.deepLink, user: params.user, groupId: params.groupId, - customAttributes: params.customAttributes - ? typeof params.customAttributes === 'string' - ? JSON.parse(params.customAttributes) - : params.customAttributes - : undefined, + customAttributes: parseCustomAttributes(params.customAttributes), deploymentType: params.deploymentType, timestamp: params.timestamp ? Number(params.timestamp) : undefined, } From e134c6238ed6bfca24e4cf9a0121354635c90131 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 19 May 2026 12:57:01 -0700 Subject: [PATCH 05/10] fix(integrations): address incident workflow review fixes --- .../tools/incidentio/schedule_entries_list.ts | 11 +++-- apps/sim/tools/incidentio/types.ts | 43 +++++++++++++++--- apps/sim/tools/incidentio/utils.ts | 45 +++++++++++++++++++ apps/sim/tools/incidentio/workflows_create.ts | 32 ++----------- apps/sim/tools/incidentio/workflows_list.ts | 34 +++----------- apps/sim/tools/incidentio/workflows_show.ts | 31 ++----------- apps/sim/tools/incidentio/workflows_update.ts | 31 ++----------- 7 files changed, 107 insertions(+), 120 deletions(-) diff --git a/apps/sim/tools/incidentio/schedule_entries_list.ts b/apps/sim/tools/incidentio/schedule_entries_list.ts index 1e9154dff8c..d3d601622ff 100644 --- a/apps/sim/tools/incidentio/schedule_entries_list.ts +++ b/apps/sim/tools/incidentio/schedule_entries_list.ts @@ -44,20 +44,19 @@ export const scheduleEntriesListTool: ToolConfig< 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) } - const queryString = queryParams.length > 0 ? `?${queryParams.join('&')}` : '' - return `https://api.incident.io/v2/schedule_entries${queryString}` + return url.toString() }, method: 'GET', headers: (params) => ({ diff --git a/apps/sim/tools/incidentio/types.ts b/apps/sim/tools/incidentio/types.ts index 0594cd1ef7e..bade70eb916 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 /** @@ -499,13 +520,25 @@ 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 diff --git a/apps/sim/tools/incidentio/utils.ts b/apps/sim/tools/incidentio/utils.ts index 01c660533de..c08e81f4433 100644 --- a/apps/sim/tools/incidentio/utils.ts +++ b/apps/sim/tools/incidentio/utils.ts @@ -1,7 +1,29 @@ +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, @@ -26,3 +48,26 @@ export function parseRequiredIncidentioJsonParam( 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 cec9d5d2a99..4a22510986f 100644 --- a/apps/sim/tools/incidentio/workflows_create.ts +++ b/apps/sim/tools/incidentio/workflows_create.ts @@ -1,5 +1,6 @@ import type { WorkflowsCreateParams, WorkflowsCreateResponse } from '@/tools/incidentio/types' -import { parseIncidentioJsonParam } from '@/tools/incidentio/utils' +import { INCIDENTIO_WORKFLOW_OUTPUT_PROPERTIES } from '@/tools/incidentio/types' +import { mapIncidentioWorkflow, parseIncidentioJsonParam } from '@/tools/incidentio/utils' import type { ToolConfig } from '@/tools/types' export const workflowsCreateTool: ToolConfig = { @@ -157,14 +158,7 @@ export const workflowsCreateTool: ToolConfig = { @@ -31,14 +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, - })), + workflows: + data.workflows?.map((workflow: Record) => + mapIncidentioWorkflow(workflow) + ) ?? [], }, } }, @@ -49,25 +47,7 @@ export const workflowsListTool: ToolConfig = { @@ -38,14 +40,7 @@ export const workflowsShowTool: ToolConfig Date: Tue, 19 May 2026 13:06:20 -0700 Subject: [PATCH 06/10] chore(docs): apply lint formatting --- apps/docs/components/icons.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index a5aec8645c1..3db9c61a75d 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -6973,8 +6973,7 @@ export function NewRelicIcon(props: SVGProps) { ) } - - + export function WizaIcon(props: SVGProps) { return ( From 940c2527e35ceffed70a05408f3e04610b1901ef Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 19 May 2026 13:26:00 -0700 Subject: [PATCH 07/10] chore: refresh integration docs and validation fixes --- apps/docs/content/docs/en/tools/gong.mdx | 4 + .../docs/content/docs/en/tools/incidentio.mdx | 103 +++++++++++++---- apps/docs/content/docs/en/tools/new_relic.mdx | 4 +- apps/docs/content/docs/en/tools/railway.mdx | 13 ++- apps/sim/blocks/blocks/incidentio.ts | 74 +++++++++++- apps/sim/blocks/blocks/railway.ts | 108 ++++++++++++++++++ apps/sim/tools/gong/aggregate_activity.ts | 13 +-- apps/sim/tools/gong/answered_scorecards.ts | 22 ++-- apps/sim/tools/gong/create_call.ts | 12 +- apps/sim/tools/gong/get_call.ts | 2 +- apps/sim/tools/gong/get_call_transcript.ts | 15 ++- apps/sim/tools/gong/get_coaching.ts | 8 +- apps/sim/tools/gong/get_extensive_calls.ts | 20 ++-- apps/sim/tools/gong/get_folder_content.ts | 2 +- apps/sim/tools/gong/get_user.ts | 2 +- apps/sim/tools/gong/interaction_stats.ts | 13 +-- apps/sim/tools/gong/list_calls.ts | 26 ++++- apps/sim/tools/gong/list_flows.ts | 2 +- apps/sim/tools/gong/list_library_folders.ts | 2 +- apps/sim/tools/gong/list_trackers.ts | 2 +- apps/sim/tools/gong/list_users.ts | 8 +- apps/sim/tools/gong/lookup_email.ts | 2 +- apps/sim/tools/gong/lookup_phone.ts | 2 +- apps/sim/tools/gong/types.ts | 4 + apps/sim/tools/incidentio/actions_list.ts | 13 ++- apps/sim/tools/incidentio/follow_ups_list.ts | 13 ++- .../tools/incidentio/incident_updates_list.ts | 11 +- apps/sim/tools/incidentio/incidents_list.ts | 22 +++- apps/sim/tools/incidentio/types.ts | 7 ++ apps/sim/tools/incidentio/users_list.ts | 22 +++- apps/sim/tools/incidentio/workflows_show.ts | 14 ++- apps/sim/tools/railway/create_environment.ts | 7 ++ apps/sim/tools/railway/create_project.ts | 21 ++++ apps/sim/tools/railway/deploy_service.ts | 21 +++- apps/sim/tools/railway/list_projects.ts | 14 ++- apps/sim/tools/railway/types.ts | 9 ++ apps/sim/tools/railway/update_project.ts | 14 +++ apps/sim/tools/registry.ts | 24 ++-- 38 files changed, 548 insertions(+), 127 deletions(-) diff --git a/apps/docs/content/docs/en/tools/gong.mdx b/apps/docs/content/docs/en/tools/gong.mdx index 2b1731d7b39..ebfccbe1f24 100644 --- a/apps/docs/content/docs/en/tools/gong.mdx +++ b/apps/docs/content/docs/en/tools/gong.mdx @@ -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,8 @@ 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` @@ -306,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 fe85c7859bf..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 @@ -446,12 +452,24 @@ List all workflows in your incident.io workspace. | 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 | +| ↳ `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` @@ -481,12 +499,24 @@ Create a new workflow in incident.io. | Parameter | Type | Description | | --------- | ---- | ----------- | | `workflow` | object | The created 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_show` @@ -499,18 +529,31 @@ 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` @@ -541,12 +584,24 @@ Update an existing workflow in incident.io. | 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` diff --git a/apps/docs/content/docs/en/tools/new_relic.mdx b/apps/docs/content/docs/en/tools/new_relic.mdx index 7beb793335a..587b875f972 100644 --- a/apps/docs/content/docs/en/tools/new_relic.mdx +++ b/apps/docs/content/docs/en/tools/new_relic.mdx @@ -5,7 +5,7 @@ description: Query observability data and record deployments in New Relic import { BlockInfoCard } from "@/components/ui/block-info-card" - @@ -141,3 +141,5 @@ Record a deployment change event in New Relic change tracking. | ↳ `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 index 80890633f7e..ab26d387838 100644 --- a/apps/docs/content/docs/en/tools/railway.mdx +++ b/apps/docs/content/docs/en/tools/railway.mdx @@ -5,7 +5,7 @@ description: Manage Railway projects, deployments, and variables import { BlockInfoCard } from "@/components/ui/block-info-card" - @@ -42,6 +42,7 @@ List Railway projects visible to the provided token | --------- | ---- | -------- | ----------- | | `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 | @@ -54,6 +55,7 @@ List Railway projects visible to the provided token | ↳ `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 | @@ -99,6 +101,9 @@ Create a Railway project | `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 | @@ -123,6 +128,8 @@ Update a Railway project name or description | `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 @@ -210,6 +217,7 @@ Create a Railway project environment | `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 @@ -280,6 +288,7 @@ Trigger a deployment for a Railway service in an environment | `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 @@ -330,3 +339,5 @@ Create or update a Railway environment variable | Parameter | Type | Description | | --------- | ---- | ----------- | | `success` | boolean | Whether the variable was created or updated | + + diff --git a/apps/sim/blocks/blocks/incidentio.ts b/apps/sim/blocks/blocks/incidentio.ts index 0eae4a5e3d4..f7bcca49dac 100644 --- a/apps/sim/blocks/blocks/incidentio.ts +++ b/apps/sim/blocks/blocks/incidentio.ts @@ -124,6 +124,30 @@ export const IncidentioBlock: BlockConfig = { }, mode: 'advanced', }, + { + 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', @@ -343,6 +367,23 @@ Return ONLY the title - no explanations.`, ], }, }, + { + 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 { id: 'folder', @@ -354,6 +395,14 @@ Return ONLY the title - no explanations.`, 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', title: 'State', @@ -758,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, }, { @@ -1037,6 +1092,13 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, 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 }, }, @@ -1049,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' }, @@ -1056,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', @@ -1064,6 +1129,7 @@ Return ONLY the JSON array - no explanations or markdown formatting.`, 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: { diff --git a/apps/sim/blocks/blocks/railway.ts b/apps/sim/blocks/blocks/railway.ts index e677a1301be..e011afea67e 100644 --- a/apps/sim/blocks/blocks/railway.ts +++ b/apps/sim/blocks/blocks/railway.ts @@ -58,6 +58,14 @@ export const RailwayBlock: BlockConfig = { 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', @@ -90,6 +98,35 @@ export const RailwayBlock: BlockConfig = { 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', @@ -133,6 +170,32 @@ export const RailwayBlock: BlockConfig = { 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', @@ -215,6 +278,19 @@ export const RailwayBlock: BlockConfig = { 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', @@ -279,6 +355,14 @@ export const RailwayBlock: BlockConfig = { 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', @@ -386,6 +470,7 @@ export const RailwayBlock: BlockConfig = { case 'list_projects': return { ...baseParams, + workspaceId: params.listProjectsWorkspaceId, first: params.first ? Number(params.first) : undefined, after: params.after, } @@ -393,6 +478,11 @@ export const RailwayBlock: BlockConfig = { 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, } @@ -402,6 +492,12 @@ export const RailwayBlock: BlockConfig = { 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 { @@ -429,6 +525,9 @@ export const RailwayBlock: BlockConfig = { skipInitialDeploys: params.skipInitialDeploys ? params.skipInitialDeploys === 'true' : undefined, + stageInitialChanges: params.stageInitialChanges + ? params.stageInitialChanges === 'true' + : undefined, } case 'delete_environment': return { @@ -454,6 +553,7 @@ export const RailwayBlock: BlockConfig = { ...baseParams, serviceId: params.deployServiceId, environmentId: params.deployEnvironmentId, + commitSha: params.deployCommitSha, } case 'list_variables': return { @@ -482,15 +582,21 @@ export const RailwayBlock: BlockConfig = { 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' }, @@ -500,6 +606,7 @@ export const RailwayBlock: BlockConfig = { 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' }, @@ -508,6 +615,7 @@ export const RailwayBlock: BlockConfig = { 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' }, diff --git a/apps/sim/tools/gong/aggregate_activity.ts b/apps/sim/tools/gong/aggregate_activity.ts index 369afad09e7..a962e00dc75 100644 --- a/apps/sim/tools/gong/aggregate_activity.ts +++ b/apps/sim/tools/gong/aggregate_activity.ts @@ -1,5 +1,5 @@ import type { GongAggregateActivityParams, GongAggregateActivityResponse } from '@/tools/gong/types' -import { getGongErrorMessage } from '@/tools/gong/utils' +import { getGongErrorMessage, parseGongIdList } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const aggregateActivityTool: ToolConfig< @@ -60,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 }, }, diff --git a/apps/sim/tools/gong/answered_scorecards.ts b/apps/sim/tools/gong/answered_scorecards.ts index f80f12a6256..92e105fdc55 100644 --- a/apps/sim/tools/gong/answered_scorecards.ts +++ b/apps/sim/tools/gong/answered_scorecards.ts @@ -2,7 +2,7 @@ import type { GongAnsweredScorecardsParams, GongAnsweredScorecardsResponse, } from '@/tools/gong/types' -import { getGongErrorMessage } from '@/tools/gong/utils' +import { getGongErrorMessage, parseGongIdList } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const answeredScorecardsTool: ToolConfig< @@ -84,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 }, }, diff --git a/apps/sim/tools/gong/create_call.ts b/apps/sim/tools/gong/create_call.ts index 9445d6a7790..5a2c35f0858 100644 --- a/apps/sim/tools/gong/create_call.ts +++ b/apps/sim/tools/gong/create_call.ts @@ -105,19 +105,19 @@ export const createCallTool: ToolConfig { const body: Record = { clientUniqueId: params.clientUniqueId.trim(), - actualStart: params.actualStart, + actualStart: params.actualStart.trim(), primaryUser: params.primaryUser.trim(), parties: parseGongJsonArray(params.parties, 'parties'), direction: params.direction, downloadMediaUrl: params.downloadMediaUrl.trim(), } - if (params.title) body.title = params.title - if (params.workspaceId) body.workspaceId = params.workspaceId.trim() - if (params.disposition) body.disposition = params.disposition - if (params.purpose) body.purpose = params.purpose + 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) body.callProviderCode = params.callProviderCode.trim() + if (params.callProviderCode?.trim()) body.callProviderCode = params.callProviderCode.trim() return body }, diff --git a/apps/sim/tools/gong/get_call.ts b/apps/sim/tools/gong/get_call.ts index a14e92c2204..35b31585248 100644 --- a/apps/sim/tools/gong/get_call.ts +++ b/apps/sim/tools/gong/get_call.ts @@ -30,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', diff --git a/apps/sim/tools/gong/get_call_transcript.ts b/apps/sim/tools/gong/get_call_transcript.ts index d24cbd7d76c..ddd26bfb6e6 100644 --- a/apps/sim/tools/gong/get_call_transcript.ts +++ b/apps/sim/tools/gong/get_call_transcript.ts @@ -1,5 +1,5 @@ import type { GongGetCallTranscriptParams, GongGetCallTranscriptResponse } from '@/tools/gong/types' -import { getGongErrorMessage } from '@/tools/gong/utils' +import { getGongErrorMessage, parseGongIdList } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const getCallTranscriptTool: ToolConfig< @@ -65,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 }, }, diff --git a/apps/sim/tools/gong/get_coaching.ts b/apps/sim/tools/gong/get_coaching.ts index d41fcab311a..21b0323131b 100644 --- a/apps/sim/tools/gong/get_coaching.ts +++ b/apps/sim/tools/gong/get_coaching.ts @@ -56,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', diff --git a/apps/sim/tools/gong/get_extensive_calls.ts b/apps/sim/tools/gong/get_extensive_calls.ts index 3e7b8e3a1cc..aee5065e219 100644 --- a/apps/sim/tools/gong/get_extensive_calls.ts +++ b/apps/sim/tools/gong/get_extensive_calls.ts @@ -1,5 +1,5 @@ import type { GongGetExtensiveCallsParams, GongGetExtensiveCallsResponse } from '@/tools/gong/types' -import { getGongErrorMessage } from '@/tools/gong/utils' +import { getGongErrorMessage, parseGongIdList } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const getExtensiveCallsTool: ToolConfig< @@ -71,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: { @@ -103,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 }, }, diff --git a/apps/sim/tools/gong/get_folder_content.ts b/apps/sim/tools/gong/get_folder_content.ts index d19aed1aa7e..65a91da7843 100644 --- a/apps/sim/tools/gong/get_folder_content.ts +++ b/apps/sim/tools/gong/get_folder_content.ts @@ -35,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', diff --git a/apps/sim/tools/gong/get_user.ts b/apps/sim/tools/gong/get_user.ts index 1789ea54292..6c5be146d69 100644 --- a/apps/sim/tools/gong/get_user.ts +++ b/apps/sim/tools/gong/get_user.ts @@ -30,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', diff --git a/apps/sim/tools/gong/interaction_stats.ts b/apps/sim/tools/gong/interaction_stats.ts index 02126cf8ff2..d8b16709174 100644 --- a/apps/sim/tools/gong/interaction_stats.ts +++ b/apps/sim/tools/gong/interaction_stats.ts @@ -1,5 +1,5 @@ import type { GongInteractionStatsParams, GongInteractionStatsResponse } from '@/tools/gong/types' -import { getGongErrorMessage } from '@/tools/gong/utils' +import { getGongErrorMessage, parseGongIdList } from '@/tools/gong/utils' import type { ToolConfig } from '@/tools/types' export const interactionStatsTool: ToolConfig< @@ -61,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 }, }, diff --git a/apps/sim/tools/gong/list_calls.ts b/apps/sim/tools/gong/list_calls.ts index f24957c5c47..fbf9909bfc4 100644 --- a/apps/sim/tools/gong/list_calls.ts +++ b/apps/sim/tools/gong/list_calls.ts @@ -50,10 +50,10 @@ export const listCallsTool: ToolConfig { const url = new URL('https://api.gong.io/v2/calls') - url.searchParams.set('fromDateTime', params.fromDateTime) - url.searchParams.set('toDateTime', params.toDateTime) - if (params.cursor) url.searchParams.set('cursor', params.cursor) - if (params.workspaceId) url.searchParams.set('workspaceId', params.workspaceId.trim()) + url.searchParams.set('fromDateTime', params.fromDateTime.trim()) + url.searchParams.set('toDateTime', params.toDateTime.trim()) + 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', @@ -93,14 +93,22 @@ export const listCallsTool: ToolConfig { 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', diff --git a/apps/sim/tools/gong/list_trackers.ts b/apps/sim/tools/gong/list_trackers.ts index fcc7163a369..ff0d9055fa9 100644 --- a/apps/sim/tools/gong/list_trackers.ts +++ b/apps/sim/tools/gong/list_trackers.ts @@ -33,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', diff --git a/apps/sim/tools/gong/list_users.ts b/apps/sim/tools/gong/list_users.ts index 8c9d1e7d2cf..b829075eacc 100644 --- a/apps/sim/tools/gong/list_users.ts +++ b/apps/sim/tools/gong/list_users.ts @@ -38,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() }, @@ -75,6 +75,7 @@ export const listUsersTool: 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', diff --git a/apps/sim/tools/gong/lookup_phone.ts b/apps/sim/tools/gong/lookup_phone.ts index eeb5ac2bd2d..9101d40b9dd 100644 --- a/apps/sim/tools/gong/lookup_phone.ts +++ b/apps/sim/tools/gong/lookup_phone.ts @@ -33,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', diff --git a/apps/sim/tools/gong/types.ts b/apps/sim/tools/gong/types.ts index d868ca8c136..5432ff492dc 100644 --- a/apps/sim/tools/gong/types.ts +++ b/apps/sim/tools/gong/types.ts @@ -76,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 } } @@ -192,6 +195,7 @@ interface GongUser { export interface GongListUsersResponse extends ToolResponse { output: { + requestId: string | null users: GongUser[] cursor: string | null totalRecords: number | null 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/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/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/types.ts b/apps/sim/tools/incidentio/types.ts index bade70eb916..c43971184f9 100644 --- a/apps/sim/tools/incidentio/types.ts +++ b/apps/sim/tools/incidentio/types.ts @@ -300,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 { @@ -417,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 { @@ -466,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 { @@ -577,6 +581,7 @@ 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 { @@ -701,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 { 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/workflows/${params.id.trim()}`, + 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', diff --git a/apps/sim/tools/railway/create_environment.ts b/apps/sim/tools/railway/create_environment.ts index 15aa5312e92..68a3b17c064 100644 --- a/apps/sim/tools/railway/create_environment.ts +++ b/apps/sim/tools/railway/create_environment.ts @@ -68,6 +68,12 @@ export const railwayCreateEnvironmentTool: ToolConfig< 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: { @@ -90,6 +96,7 @@ export const railwayCreateEnvironmentTool: ToolConfig< sourceEnvironmentId: optionalString(params.sourceEnvironmentId), ephemeral: params.ephemeral, skipInitialDeploys: params.skipInitialDeploys, + stageInitialChanges: params.stageInitialChanges, }), }, }), diff --git a/apps/sim/tools/railway/create_project.ts b/apps/sim/tools/railway/create_project.ts index c531bca6897..0634801bbef 100644 --- a/apps/sim/tools/railway/create_project.ts +++ b/apps/sim/tools/railway/create_project.ts @@ -44,6 +44,24 @@ export const railwayCreateProjectTool: ToolConfig< 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, @@ -74,6 +92,9 @@ export const railwayCreateProjectTool: ToolConfig< 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, }), diff --git a/apps/sim/tools/railway/deploy_service.ts b/apps/sim/tools/railway/deploy_service.ts index 17e21e1c5c0..233254f876d 100644 --- a/apps/sim/tools/railway/deploy_service.ts +++ b/apps/sim/tools/railway/deploy_service.ts @@ -3,6 +3,8 @@ import type { RailwayDeployServiceResponse, } from '@/tools/railway/types' import { + compactVariables, + optionalString, parseRailwayGraphqlResponse, RAILWAY_GRAPHQL_URL, railwayHeaders, @@ -47,6 +49,12 @@ export const railwayDeployServiceTool: ToolConfig< 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: { @@ -55,14 +63,19 @@ export const railwayDeployServiceTool: ToolConfig< headers: (params) => railwayHeaders(params.apiKey, params.tokenType), body: (params) => ({ query: ` - mutation DeployService($serviceId: String!, $environmentId: String!) { - serviceInstanceDeployV2(serviceId: $serviceId, environmentId: $environmentId) + mutation DeployService($serviceId: String!, $environmentId: String!, $commitSha: String) { + serviceInstanceDeployV2( + serviceId: $serviceId + environmentId: $environmentId + commitSha: $commitSha + ) } `, - variables: { + variables: compactVariables({ serviceId: params.serviceId.trim(), environmentId: params.environmentId.trim(), - }, + commitSha: optionalString(params.commitSha), + }), }), }, diff --git a/apps/sim/tools/railway/list_projects.ts b/apps/sim/tools/railway/list_projects.ts index 88818c4ecac..37b1553f202 100644 --- a/apps/sim/tools/railway/list_projects.ts +++ b/apps/sim/tools/railway/list_projects.ts @@ -44,6 +44,12 @@ export const railwayListProjectsTool: ToolConfig< 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, @@ -64,14 +70,15 @@ export const railwayListProjectsTool: ToolConfig< headers: (params) => railwayHeaders(params.apiKey, params.tokenType), body: (params) => ({ query: ` - query ListProjects($first: Int, $after: String) { - projects(first: $first, after: $after) { + query ListProjects($workspaceId: String, $first: Int, $after: String) { + projects(workspaceId: $workspaceId, first: $first, after: $after) { edges { node { id name description createdAt + updatedAt } } pageInfo { @@ -82,6 +89,7 @@ export const railwayListProjectsTool: ToolConfig< } `, variables: compactVariables({ + workspaceId: optionalString(params.workspaceId), first: params.first ? Number(params.first) : undefined, after: optionalString(params.after), }), @@ -101,6 +109,7 @@ export const railwayListProjectsTool: ToolConfig< name: project.name, description: project.description ?? null, createdAt: project.createdAt, + updatedAt: project.updatedAt ?? null, })) return { @@ -127,6 +136,7 @@ export const railwayListProjectsTool: ToolConfig< 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 }, }, }, }, diff --git a/apps/sim/tools/railway/types.ts b/apps/sim/tools/railway/types.ts index 6870b6c44cb..deb68792614 100644 --- a/apps/sim/tools/railway/types.ts +++ b/apps/sim/tools/railway/types.ts @@ -17,6 +17,7 @@ export interface RailwayProjectSummary { name: string description: string | null createdAt: string + updatedAt?: string | null } export interface RailwayUpdatedProject { @@ -60,6 +61,7 @@ export interface RailwayDeploymentSummary { } export interface RailwayListProjectsParams extends RailwayAuthParams { + workspaceId?: string first?: number after?: string } @@ -70,6 +72,9 @@ export interface RailwayGetProjectParams extends RailwayAuthParams { export interface RailwayCreateProjectParams extends RailwayAuthParams { name: string + description?: string + workspaceId?: string + isPublic?: boolean defaultEnvironmentName?: string prDeploys?: boolean } @@ -78,6 +83,8 @@ export interface RailwayUpdateProjectParams extends RailwayAuthParams { projectId: string name?: string description?: string + isPublic?: boolean + prDeploys?: boolean } export interface RailwayDeleteProjectParams extends RailwayAuthParams { @@ -99,6 +106,7 @@ export interface RailwayCreateEnvironmentParams extends RailwayAuthParams { sourceEnvironmentId?: string ephemeral?: boolean skipInitialDeploys?: boolean + stageInitialChanges?: boolean } export interface RailwayDeleteEnvironmentParams extends RailwayAuthParams { @@ -116,6 +124,7 @@ export interface RailwayListDeploymentsParams extends RailwayAuthParams { export interface RailwayDeployServiceParams extends RailwayAuthParams { serviceId: string environmentId: string + commitSha?: string } export interface RailwayListVariablesParams extends RailwayAuthParams { diff --git a/apps/sim/tools/railway/update_project.ts b/apps/sim/tools/railway/update_project.ts index b8df06ac737..3e021d4083c 100644 --- a/apps/sim/tools/railway/update_project.ts +++ b/apps/sim/tools/railway/update_project.ts @@ -56,6 +56,18 @@ export const railwayUpdateProjectTool: ToolConfig< 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: { @@ -77,6 +89,8 @@ export const railwayUpdateProjectTool: ToolConfig< input: compactVariables({ name: optionalString(params.name), description: optionalString(params.description), + isPublic: params.isPublic, + prDeploys: params.prDeploys, }), }, }), diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 93530a11ed1..278891187a9 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -4244,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, @@ -5407,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, @@ -5415,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, @@ -5430,6 +5425,11 @@ 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, From 9f44ae150083ebf8ccf9832e71f2b2a1881e9936 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 19 May 2026 13:36:36 -0700 Subject: [PATCH 08/10] more --- apps/docs/content/docs/en/tools/new_relic.mdx | 4 +- apps/sim/tools/railway/deploy_service.ts | 49 +++++++++++++------ 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/apps/docs/content/docs/en/tools/new_relic.mdx b/apps/docs/content/docs/en/tools/new_relic.mdx index 587b875f972..7beb793335a 100644 --- a/apps/docs/content/docs/en/tools/new_relic.mdx +++ b/apps/docs/content/docs/en/tools/new_relic.mdx @@ -5,7 +5,7 @@ description: Query observability data and record deployments in New Relic import { BlockInfoCard } from "@/components/ui/block-info-card" - @@ -141,5 +141,3 @@ Record a deployment change event in New Relic change tracking. | ↳ `guid` | string | Entity GUID | | ↳ `name` | string | Entity name | | `messages` | array | Messages returned by New Relic for the created change event | - - diff --git a/apps/sim/tools/railway/deploy_service.ts b/apps/sim/tools/railway/deploy_service.ts index 233254f876d..a7acd359a01 100644 --- a/apps/sim/tools/railway/deploy_service.ts +++ b/apps/sim/tools/railway/deploy_service.ts @@ -3,7 +3,6 @@ import type { RailwayDeployServiceResponse, } from '@/tools/railway/types' import { - compactVariables, optionalString, parseRailwayGraphqlResponse, RAILWAY_GRAPHQL_URL, @@ -61,22 +60,40 @@ export const railwayDeployServiceTool: ToolConfig< url: RAILWAY_GRAPHQL_URL, method: 'POST', headers: (params) => railwayHeaders(params.apiKey, params.tokenType), - body: (params) => ({ - query: ` - mutation DeployService($serviceId: String!, $environmentId: String!, $commitSha: String) { - serviceInstanceDeployV2( - serviceId: $serviceId - environmentId: $environmentId - commitSha: $commitSha - ) + 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, + }, } - `, - variables: compactVariables({ - serviceId: params.serviceId.trim(), - environmentId: params.environmentId.trim(), - commitSha: optionalString(params.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) => { From c951ac62105ed5e4d8f6dd0775f9b3b14cfa8434 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 19 May 2026 13:48:16 -0700 Subject: [PATCH 09/10] fix(integrations): address PR review comments --- apps/docs/content/docs/en/tools/gong.mdx | 2 +- apps/sim/blocks/blocks/gong.ts | 1 - apps/sim/tools/gong/list_calls.ts | 7 ++++--- apps/sim/tools/gong/types.ts | 2 +- apps/sim/tools/new_relic/create_deployment_event.ts | 3 +++ 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/docs/content/docs/en/tools/gong.mdx b/apps/docs/content/docs/en/tools/gong.mdx index ebfccbe1f24..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 | Yes | End date/time in ISO-8601 format \(e.g., 2024-01-31T23:59:59Z\) | +| `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 | diff --git a/apps/sim/blocks/blocks/gong.ts b/apps/sim/blocks/blocks/gong.ts index 6ec331731d9..7049dbdafc2 100644 --- a/apps/sim/blocks/blocks/gong.ts +++ b/apps/sim/blocks/blocks/gong.ts @@ -179,7 +179,6 @@ Return ONLY the JSON array - no explanations, no quotes, no extra text.`, field: 'operation', value: ['list_calls'], }, - required: { field: 'operation', value: 'list_calls' }, wandConfig: { enabled: true, prompt: `Generate an ISO 8601 timestamp based on the user's description. diff --git a/apps/sim/tools/gong/list_calls.ts b/apps/sim/tools/gong/list_calls.ts index fbf9909bfc4..655bed2966d 100644 --- a/apps/sim/tools/gong/list_calls.ts +++ b/apps/sim/tools/gong/list_calls.ts @@ -29,9 +29,10 @@ export const listCallsTool: ToolConfig { const url = new URL('https://api.gong.io/v2/calls') url.searchParams.set('fromDateTime', params.fromDateTime.trim()) - url.searchParams.set('toDateTime', params.toDateTime.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() diff --git a/apps/sim/tools/gong/types.ts b/apps/sim/tools/gong/types.ts index 5432ff492dc..55d7c9b3c94 100644 --- a/apps/sim/tools/gong/types.ts +++ b/apps/sim/tools/gong/types.ts @@ -9,7 +9,7 @@ interface GongBaseParams { /** List Calls */ export interface GongListCallsParams extends GongBaseParams { fromDateTime: string - toDateTime: string + toDateTime?: string cursor?: string workspaceId?: string } diff --git a/apps/sim/tools/new_relic/create_deployment_event.ts b/apps/sim/tools/new_relic/create_deployment_event.ts index a5d676372d6..75099818961 100644 --- a/apps/sim/tools/new_relic/create_deployment_event.ts +++ b/apps/sim/tools/new_relic/create_deployment_event.ts @@ -134,6 +134,9 @@ const buildCustomAttributes = ( 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) From b5fbb25d7d276e85c36b13e0baaf291dbc08db90 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 19 May 2026 14:00:11 -0700 Subject: [PATCH 10/10] fix(gong): align list calls block validation --- apps/sim/blocks/blocks/gong.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/blocks/blocks/gong.ts b/apps/sim/blocks/blocks/gong.ts index 7049dbdafc2..a7756b1be00 100644 --- a/apps/sim/blocks/blocks/gong.ts +++ b/apps/sim/blocks/blocks/gong.ts @@ -179,6 +179,7 @@ Return ONLY the JSON array - no explanations, no quotes, no extra text.`, field: 'operation', value: ['list_calls'], }, + required: { field: 'operation', value: 'list_calls' }, wandConfig: { enabled: true, prompt: `Generate an ISO 8601 timestamp based on the user's description. @@ -203,7 +204,6 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes field: 'operation', value: ['list_calls'], }, - required: { field: 'operation', value: 'list_calls' }, wandConfig: { enabled: true, prompt: `Generate an ISO 8601 timestamp based on the user's description.