mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
improvement(tools): validate integrations, add Gong activity tools, regenerate docs (#4937)
* improvement(tools): validate integrations, add Gong activity tools, regenerate docs * fix(servicenow): give list-attachments limit a unique subBlock id Read Records and List Attachments shared the subBlock id 'limit', so the single-value-per-id store could bleed the value across operations. Rename the new list-attachments field to attachmentLimit and map it back to the tool's limit param. * fix(servicenow): type upload-attachment response json to fix build typecheck secureFetchWithValidation's response.json() resolves to unknown under the build's stricter typecheck; cast the parsed body so data.result is accessible. * refactor(servicenow): type upload-attachment response per file-route convention Match the SharePoint/OneDrive upload pattern: import the named ServiceNowAttachment type from the tool's types, cast response.json() to it, and extract specific fields with ?? null instead of passing data.result through as an opaque unknown blob. * fix(servicenow): remove invalid 'custom' generationType from groupBy wandConfig 'custom' is not a member of the GenerationType union, breaking the build typecheck. No valid type fits a comma-separated field list, so drop the wandConfig (consistent with the block's other field-list inputs). * fix(servicenow): drop redundant hidden fileContent param from upload attachment Per project rule, visibility:'hidden' is reserved for framework-injected tokens, not user-supplied data. fileContent was a copied-over legacy fallback with no caller (the block uploads via the canonical file/UserFile path), so remove it from the tool, types, contract, and route. * fix(dub): require a link selector for bulk update links Bulk Update Links could run with only Update Data and no Link IDs or External IDs, sending Dub a request with no target links and producing a confusing API error. Guard the tool to throw a clear validation error when neither selector is provided, and clarify the block placeholder.
This commit is contained in:
@@ -4987,7 +4987,7 @@ export function IntercomIcon(props: SVGProps<SVGSVGElement>) {
|
||||
<g>
|
||||
<path
|
||||
d='M221.66,144.56 C221.66,146.9 220.76,149.15 219.17,150.8 C217.57,152.46 215.4,153.39 213.15,153.39 C210.89,153.39 208.72,152.46 207.12,150.8 C205.53,149.15 204.63,146.9 204.63,144.56 L204.63,65.68 C204.63,63.34 205.53,61.09 207.12,59.43 C208.72,57.78 210.89,56.85 213.15,56.85 C215.4,56.85 217.57,57.78 219.17,59.43 C220.76,61.09 221.66,63.34 221.66,65.68 L221.66,144.56 Z M218.7,199.22 C217.58,200.39 185.91,227.75 127.86,227.75 C69.81,227.75 38.35,200.53 37.01,199.36 C36.17,198.63 35.48,197.73 34.97,196.71 C34.47,195.69 34.16,194.59 34.07,193.45 C33.98,192.31 34.1,191.16 34.44,190.07 C34.77,188.98 35.32,187.98 36.03,187.1 C37.5,185.34 39.58,184.25 41.82,184.07 C44.06,183.9 46.28,184.64 47.99,186.16 C48.48,186.52 76.77,210.31 127.79,210.31 C178.81,210.31 207.3,186.37 207.59,186.16 C209.32,184.66 211.54,183.92 213.79,184.1 C216.04,184.27 218.13,185.35 219.62,187.1 C221.06,188.84 221.77,191.1 221.61,193.38 C221.46,195.66 220.44,197.78 218.78,199.29 L218.7,199.22 Z M33.99,65.68 C34.12,63.33 35.13,61.13 36.82,59.55 C38.51,57.98 40.73,57.17 43,57.29 C45.1,57.41 47.08,58.33 48.56,59.88 C50.04,61.43 50.92,63.5 51.02,65.68 L51.02,144.42 C51.02,146.76 50.12,149 48.52,150.66 C46.93,152.31 44.76,153.24 42.5,153.24 C40.24,153.24 38.08,152.31 36.48,150.66 C34.88,149 33.99,146.76 33.99,144.42 L33.99,65.68 Z M76.7,48.16 C76.83,45.81 77.85,43.61 79.54,42.04 C81.22,40.47 83.44,39.65 85.71,39.77 C87.81,39.9 89.79,40.82 91.27,42.37 C92.75,43.92 93.63,45.98 93.73,48.16 L93.73,164.92 C93.73,167.26 92.83,169.51 91.24,171.16 C89.64,172.82 87.47,173.75 85.22,173.75 C82.96,173.75 80.79,172.82 79.2,171.16 C77.6,169.51 76.7,167.26 76.7,164.92 L76.7,48.16 Z M119.63,43.79 C119.63,41.44 120.52,39.2 122.12,37.54 C123.72,35.89 125.88,34.96 128.14,34.96 C130.4,34.96 132.56,35.89 134.16,37.54 C135.76,39.2 136.66,41.44 136.66,43.79 L136.66,170.76 C136.66,173.1 135.76,175.35 134.16,177 C132.56,178.66 130.4,179.59 128.14,179.59 C125.88,179.59 123.72,178.66 122.12,177 C120.52,175.35 119.63,173.1 119.63,170.76 L119.63,43.79 Z M161.85,48.16 C161.85,45.82 162.74,43.58 164.34,41.92 C165.94,40.26 168.1,39.33 170.36,39.33 C172.62,39.33 174.79,40.26 176.38,41.92 C177.98,43.58 178.88,45.82 178.88,48.16 L178.88,164.92 C178.88,167.26 177.98,169.51 176.38,171.16 C174.79,172.82 172.62,173.75 170.36,173.75 C168.1,173.75 165.94,172.82 164.34,171.16 C162.74,169.51 161.85,167.26 161.85,164.92 L161.85,48.16 Z M223.98,0 L32.02,0 C27.83,-0.03 23.69,0.8 19.81,2.44 C15.94,4.07 12.41,6.48 9.44,9.53 C6.47,12.58 4.1,16.21 2.48,20.21 C0.86,24.21 0.02,28.5 0,32.84 L0,229.87 C0.02,234.2 0.86,238.5 2.48,242.49 C4.1,246.49 6.47,250.12 9.44,253.17 C12.41,256.22 15.94,258.64 19.81,260.27 C23.69,261.91 27.83,262.73 32.02,262.71 L223.98,262.71 C228.16,262.73 232.3,261.91 236.17,260.28 C240.04,258.65 243.56,256.24 246.53,253.2 C249.51,250.16 251.87,246.54 253.5,242.55 C255.12,238.55 255.97,234.27 256,229.94 L256,32.84 C255.98,28.51 255.14,24.22 253.52,20.23 C251.91,16.23 249.55,12.61 246.58,9.56 C243.62,6.51 240.1,4.1 236.24,2.46 C232.37,0.82 228.23,-0.02 224.05,0'
|
||||
fill='currentColor'
|
||||
fill='#000000'
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -5028,7 +5028,7 @@ export function MailchimpIcon(props: SVGProps<SVGSVGElement>) {
|
||||
y='0px'
|
||||
viewBox='0 0 230.81 244.96'
|
||||
xmlSpace='preserve'
|
||||
fill='currentColor'
|
||||
fill='#000000'
|
||||
>
|
||||
<g>
|
||||
<g>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="brightdata"
|
||||
color="#0F4C81"
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
@@ -64,6 +64,61 @@ Execute a SQL statement against a Databricks SQL warehouse and return results in
|
||||
| `totalRows` | number | Total number of rows in the result |
|
||||
| `truncated` | boolean | Whether the result set was truncated due to row_limit or byte_limit |
|
||||
|
||||
### `databricks_get_statement`
|
||||
|
||||
Poll a SQL statement by its ID to retrieve status and results. Use this after Execute SQL when a query runs longer than the wait timeout.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `host` | string | Yes | Databricks workspace host \(e.g., dbc-abc123.cloud.databricks.com\) |
|
||||
| `apiKey` | string | Yes | Databricks Personal Access Token |
|
||||
| `statementId` | string | Yes | The ID of the statement to fetch \(returned by Execute SQL\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `statementId` | string | Unique identifier for the statement |
|
||||
| `status` | string | Execution status \(SUCCEEDED, PENDING, RUNNING, FAILED, CANCELED, CLOSED\) |
|
||||
| `columns` | array | Column schema of the result set |
|
||||
| ↳ `name` | string | Column name |
|
||||
| ↳ `position` | number | Column position \(0-based\) |
|
||||
| ↳ `typeName` | string | Column type \(STRING, INT, LONG, DOUBLE, BOOLEAN, TIMESTAMP, DATE, DECIMAL, etc.\) |
|
||||
| `data` | array | Result rows as a 2D array of strings where each inner array is a row of column values |
|
||||
| `totalRows` | number | Total number of rows in the result |
|
||||
| `truncated` | boolean | Whether the result set was truncated due to row_limit or byte_limit |
|
||||
|
||||
### `databricks_list_warehouses`
|
||||
|
||||
List all SQL warehouses in a Databricks workspace including their size, state, and type. Use this to discover the warehouse ID needed for Execute SQL.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `host` | string | Yes | Databricks workspace host \(e.g., dbc-abc123.cloud.databricks.com\) |
|
||||
| `apiKey` | string | Yes | Databricks Personal Access Token |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `warehouses` | array | List of SQL warehouses in the workspace |
|
||||
| ↳ `warehouseId` | string | Unique warehouse identifier |
|
||||
| ↳ `name` | string | Warehouse display name |
|
||||
| ↳ `clusterSize` | string | Warehouse size \(e.g., 2X-Small, Small, Medium, Large\) |
|
||||
| ↳ `state` | string | Current state \(STARTING, RUNNING, STOPPING, STOPPED, DELETING, DELETED\) |
|
||||
| ↳ `warehouseType` | string | Warehouse type \(CLASSIC, PRO\) |
|
||||
| ↳ `creatorName` | string | Email of the warehouse creator |
|
||||
| ↳ `autoStopMinutes` | number | Minutes of inactivity before auto-stop \(0 = disabled\) |
|
||||
| ↳ `numClusters` | number | Current number of running clusters |
|
||||
| ↳ `minNumClusters` | number | Minimum cluster count for scaling |
|
||||
| ↳ `maxNumClusters` | number | Maximum cluster count for scaling |
|
||||
| ↳ `numActiveSessions` | number | Number of active sessions |
|
||||
| ↳ `enableServerlessCompute` | boolean | Whether serverless compute is enabled |
|
||||
|
||||
### `databricks_list_jobs`
|
||||
|
||||
List all jobs in a Databricks workspace with optional filtering by name.
|
||||
@@ -93,6 +148,34 @@ List all jobs in a Databricks workspace with optional filtering by name.
|
||||
| `hasMore` | boolean | Whether more jobs are available for pagination |
|
||||
| `nextPageToken` | string | Token for fetching the next page of results |
|
||||
|
||||
### `databricks_get_job`
|
||||
|
||||
Get the full definition and settings of a single Databricks job by its job ID.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `host` | string | Yes | Databricks workspace host \(e.g., dbc-abc123.cloud.databricks.com\) |
|
||||
| `apiKey` | string | Yes | Databricks Personal Access Token |
|
||||
| `jobId` | number | Yes | The canonical identifier of the job to retrieve |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `jobId` | number | The job ID |
|
||||
| `name` | string | Job name |
|
||||
| `creatorUserName` | string | Email of the job creator |
|
||||
| `runAsUserName` | string | User the job runs as |
|
||||
| `createdTime` | number | Job creation timestamp \(epoch ms\) |
|
||||
| `format` | string | Job format \(SINGLE_TASK or MULTI_TASK\) |
|
||||
| `maxConcurrentRuns` | number | Maximum number of concurrent runs |
|
||||
| `timeoutSeconds` | number | Job-level timeout in seconds \(0 or null means no timeout\) |
|
||||
| `schedule` | object | Cron schedule configuration \(quartz_cron_expression, timezone_id, pause_status\) |
|
||||
| `tags` | object | Key-value tags applied to the job |
|
||||
| `tasks` | array | Task definitions for the job \(empty for single-task jobs\) |
|
||||
|
||||
### `databricks_run_job`
|
||||
|
||||
Trigger an existing Databricks job to run immediately with optional job-level or notebook parameters.
|
||||
@@ -264,4 +347,37 @@ List all clusters in a Databricks workspace including their state, configuration
|
||||
| ↳ `autoterminationMinutes` | number | Minutes of inactivity before auto-termination \(0 = disabled\) |
|
||||
| ↳ `startTime` | number | Cluster start timestamp \(epoch ms\) |
|
||||
|
||||
### `databricks_get_cluster`
|
||||
|
||||
Get the state, configuration, and resource details of a single Databricks cluster by its cluster ID.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `host` | string | Yes | Databricks workspace host \(e.g., dbc-abc123.cloud.databricks.com\) |
|
||||
| `apiKey` | string | Yes | Databricks Personal Access Token |
|
||||
| `clusterId` | string | Yes | The ID of the cluster to retrieve |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `cluster` | object | Cluster detail |
|
||||
| ↳ `clusterId` | string | Unique cluster identifier |
|
||||
| ↳ `clusterName` | string | Cluster display name |
|
||||
| ↳ `state` | string | Current state \(PENDING, RUNNING, RESTARTING, RESIZING, TERMINATING, TERMINATED, ERROR, UNKNOWN\) |
|
||||
| ↳ `stateMessage` | string | Human-readable state description |
|
||||
| ↳ `creatorUserName` | string | Email of the cluster creator |
|
||||
| ↳ `sparkVersion` | string | Spark runtime version \(e.g., 13.3.x-scala2.12\) |
|
||||
| ↳ `nodeTypeId` | string | Worker node type identifier |
|
||||
| ↳ `driverNodeTypeId` | string | Driver node type identifier |
|
||||
| ↳ `numWorkers` | number | Number of worker nodes \(for fixed-size clusters\) |
|
||||
| ↳ `autoscale` | object | Autoscaling configuration \(null for fixed-size clusters\) |
|
||||
| ↳ `minWorkers` | number | Minimum number of workers |
|
||||
| ↳ `maxWorkers` | number | Maximum number of workers |
|
||||
| ↳ `clusterSource` | string | Origin \(API, UI, JOB, MODELS, PIPELINE, PIPELINE_MAINTENANCE, SQL\) |
|
||||
| ↳ `autoterminationMinutes` | number | Minutes of inactivity before auto-termination \(0 = disabled\) |
|
||||
| ↳ `startTime` | number | Cluster start timestamp \(epoch ms\) |
|
||||
|
||||
|
||||
|
||||
@@ -273,8 +273,6 @@ Retrieve a paginated list of short links for the authenticated workspace. Suppor
|
||||
| `search` | string | No | Search query matched against the short link slug and destination URL |
|
||||
| `tagIds` | string | No | Comma-separated tag IDs to filter by |
|
||||
| `showArchived` | boolean | No | Whether to include archived links \(defaults to false\) |
|
||||
| `sortBy` | string | No | Sort by field: createdAt, clicks, saleAmount, or lastClicked |
|
||||
| `sortOrder` | string | No | Sort order: asc or desc |
|
||||
| `page` | number | No | Page number \(default: 1\) |
|
||||
| `pageSize` | number | No | Number of links per page \(default: 100, max: 100\) |
|
||||
|
||||
@@ -285,6 +283,86 @@ Retrieve a paginated list of short links for the authenticated workspace. Suppor
|
||||
| `links` | json | Array of link objects \(id, domain, key, url, shortLink, clicks, tags, createdAt\) |
|
||||
| `count` | number | Number of links returned |
|
||||
|
||||
### `dub_get_links_count`
|
||||
|
||||
Retrieve the number of short links for the authenticated workspace, optionally filtered and grouped by domain, tag, user, or folder.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Dub API key |
|
||||
| `domain` | string | No | Filter by domain |
|
||||
| `search` | string | No | Search query matched against the short link slug and destination URL |
|
||||
| `tagIds` | string | No | Comma-separated tag IDs to filter by |
|
||||
| `tagNames` | string | No | Comma-separated tag names to filter by \(case-insensitive\) |
|
||||
| `folderId` | string | No | Filter by folder ID |
|
||||
| `showArchived` | boolean | No | Whether to include archived links \(defaults to false\) |
|
||||
| `groupBy` | string | No | Group counts by: domain, tagId, userId, or folderId |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `count` | number | Total number of links matching the filters |
|
||||
| `groups` | json | Per-group counts when groupBy is set \(e.g. \[\{ domain, count \}\]\) |
|
||||
|
||||
### `dub_bulk_create_links`
|
||||
|
||||
Create up to 100 short links in a single request. Returns the created links alongside any per-link errors.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Dub API key |
|
||||
| `links` | json | Yes | JSON array of link objects to create. Each object requires a "url" and may include domain, key, tagIds, and other link fields \(max 100\). |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `created` | json | Array of successfully created link objects |
|
||||
| `errors` | json | Array of per-link errors \(\{ link, error, code \}\) for links that failed |
|
||||
| `count` | number | Number of links successfully created |
|
||||
|
||||
### `dub_bulk_update_links`
|
||||
|
||||
Apply the same set of field updates to up to 100 links at once, selected by link IDs or external IDs.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Dub API key |
|
||||
| `linkIds` | string | No | Comma-separated link IDs to update \(max 100, takes precedence over externalIds\) |
|
||||
| `externalIds` | string | No | Comma-separated external IDs to update \(max 100\) |
|
||||
| `data` | json | Yes | JSON object of fields to apply to every selected link \(e.g. \{ "archived": true, "tagIds": \["..."\] \}\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `updated` | json | Array of updated link objects |
|
||||
| `count` | number | Number of links updated |
|
||||
|
||||
### `dub_bulk_delete_links`
|
||||
|
||||
Delete up to 100 short links in a single request by their link IDs. Non-existing IDs are ignored.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Dub API key |
|
||||
| `linkIds` | string | Yes | Comma-separated link IDs to delete \(max 100\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `deletedCount` | number | Number of links that were deleted |
|
||||
|
||||
### `dub_get_analytics`
|
||||
|
||||
Retrieve analytics for links including clicks, leads, and sales. Supports filtering by link, time range, and grouping by various dimensions.
|
||||
@@ -315,4 +393,57 @@ Retrieve analytics for links including clicks, leads, and sales. Supports filter
|
||||
| `saleAmount` | number | Total sale amount in cents |
|
||||
| `data` | json | Grouped analytics data \(timeseries, countries, devices, etc.\) |
|
||||
|
||||
### `dub_get_events`
|
||||
|
||||
Retrieve a paginated stream of individual click, lead, and sale events for links, with filtering by link, time range, and location.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Dub API key |
|
||||
| `event` | string | No | Event type: clicks \(default\), leads, or sales |
|
||||
| `linkId` | string | No | Filter by link ID |
|
||||
| `externalId` | string | No | Filter by external ID \(prefix with ext_\) |
|
||||
| `domain` | string | No | Filter by domain |
|
||||
| `interval` | string | No | Time interval: 24h \(default\), 7d, 30d, 90d, 1y, mtd, qtd, ytd, or all |
|
||||
| `start` | string | No | Start date/time in ISO 8601 format \(overrides interval\) |
|
||||
| `end` | string | No | End date/time in ISO 8601 format \(defaults to now\) |
|
||||
| `country` | string | No | Filter by country \(ISO 3166-1 alpha-2 code\) |
|
||||
| `timezone` | string | No | IANA timezone for event timestamps \(defaults to UTC\) |
|
||||
| `page` | number | No | Page number \(default: 1\) |
|
||||
| `limit` | number | No | Number of events per page \(default: 100, max: 1000\) |
|
||||
| `sortOrder` | string | No | Sort order: desc \(default\) or asc |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `events` | json | Array of event objects \(event, timestamp, click, link, and customer/sale data when applicable\) |
|
||||
| `count` | number | Number of events returned |
|
||||
|
||||
### `dub_get_qr_code`
|
||||
|
||||
Generate a customizable QR code (PNG) for a short link, with control over size, error correction, colors, and margin.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Dub API key |
|
||||
| `url` | string | Yes | The short link URL to encode in the QR code |
|
||||
| `size` | number | No | QR code size in pixels \(default: 600\) |
|
||||
| `level` | string | No | Error correction level: L \(default\), M, Q, or H |
|
||||
| `fgColor` | string | No | Foreground color in hex \(default: #000000\) |
|
||||
| `bgColor` | string | No | Background color in hex \(default: #FFFFFF\) |
|
||||
| `hideLogo` | boolean | No | Whether to hide the logo in the center of the QR code \(default: false\) |
|
||||
| `margin` | number | No | Margin \(quiet zone\) around the QR code \(default: 2\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `file` | file | Generated QR code image stored in execution files |
|
||||
| `content` | string | Base64-encoded PNG image data |
|
||||
|
||||
|
||||
|
||||
@@ -54,10 +54,14 @@ Search the web using DuckDuckGo Instant Answers API. Returns instant answers, ab
|
||||
| `abstractText` | string | Plain text version of the abstract |
|
||||
| `abstractSource` | string | The source of the abstract \(e.g., Wikipedia\) |
|
||||
| `abstractURL` | string | URL to the source of the abstract |
|
||||
| `definition` | string | Dictionary-style definition if available |
|
||||
| `definitionSource` | string | The source of the definition |
|
||||
| `definitionURL` | string | URL to the source of the definition |
|
||||
| `image` | string | URL to an image related to the topic |
|
||||
| `answer` | string | Direct answer if available \(e.g., for calculations\) |
|
||||
| `answerType` | string | Type of the answer \(e.g., calc, ip, etc.\) |
|
||||
| `type` | string | Response type: A \(article\), D \(disambiguation\), C \(category\), N \(name\), E \(exclusive\) |
|
||||
| `redirect` | string | !bang redirect URL, populated only for bang queries |
|
||||
| `relatedTopics` | array | Array of related topics with URLs and descriptions |
|
||||
| ↳ `FirstURL` | string | URL to the related topic |
|
||||
| ↳ `Text` | string | Description of the related topic |
|
||||
|
||||
@@ -690,6 +690,41 @@ Advanced people search with complex filters for location, company size, seniorit
|
||||
| ↳ `start` | number | Start position |
|
||||
| ↳ `limit` | number | Limit |
|
||||
|
||||
### `enrich_search_jobs`
|
||||
|
||||
Search LinkedIn job postings by keywords with filters for location, job type, workplace type, experience level, and company.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Enrich API key |
|
||||
| `keywords` | string | Yes | Search keywords \(e.g., "software engineer"\) |
|
||||
| `location` | string | No | Location filter \(e.g., London\) |
|
||||
| `jobTypes` | string | No | Comma-separated job types \(e.g., "full time, part time"\) |
|
||||
| `workplaceTypes` | string | No | Comma-separated workplace types \(e.g., "on site, remote"\) |
|
||||
| `experienceLevels` | string | No | Comma-separated experience levels \(e.g., "internship, associate"\) |
|
||||
| `companyIds` | string | No | Comma-separated LinkedIn company IDs to filter by \(e.g., "2048, 3050"\) |
|
||||
| `timePosted` | string | No | Time filter \(e.g., past_24hrs, past_week, past_month\) |
|
||||
| `start` | number | No | Number of records to skip for pagination \(default: 0\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `count` | number | Number of job postings returned |
|
||||
| `jobs` | array | Job postings |
|
||||
| ↳ `title` | string | Job title |
|
||||
| ↳ `companyName` | string | Hiring company name |
|
||||
| ↳ `companyLink` | string | Company LinkedIn URL |
|
||||
| ↳ `companyLogo` | string | Company logo URL |
|
||||
| ↳ `location` | string | Job location |
|
||||
| ↳ `url` | string | Job posting URL |
|
||||
| ↳ `postedDate` | string | Date the job was posted |
|
||||
| ↳ `postedTimestamp` | string | Timestamp the job was posted |
|
||||
| ↳ `hiringStatus` | string | Hiring status |
|
||||
| ↳ `criteria` | object | Employment criteria \(seniority, type, function\) |
|
||||
|
||||
### `enrich_search_posts`
|
||||
|
||||
Search LinkedIn posts by keywords with date filtering.
|
||||
@@ -780,6 +815,35 @@ Get reactions on a LinkedIn post with filtering by reaction type.
|
||||
| ↳ `profilePicture` | string | Profile picture URL |
|
||||
| ↳ `linkedInUrl` | string | LinkedIn URL |
|
||||
|
||||
### `enrich_search_post_reactions_by_url`
|
||||
|
||||
Get reactions on a LinkedIn post by its URL, filtered by reaction type.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Enrich API key |
|
||||
| `postUrl` | string | Yes | LinkedIn post URL \(e.g., https://www.linkedin.com/posts/...\) |
|
||||
| `reactionType` | string | Yes | Reaction type filter: all, like, love, celebrate, insightful, or funny \(default: all\) |
|
||||
| `page` | number | Yes | Page number \(starts at 1\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `page` | number | Current page number |
|
||||
| `totalPage` | number | Total number of pages |
|
||||
| `count` | number | Number of reactions returned |
|
||||
| `reactions` | array | Reactions |
|
||||
| ↳ `reactionType` | string | Type of reaction |
|
||||
| ↳ `reactor` | object | Person who reacted |
|
||||
| ↳ `name` | string | Name |
|
||||
| ↳ `subTitle` | string | Job title |
|
||||
| ↳ `profileId` | string | Profile ID |
|
||||
| ↳ `profilePicture` | string | Profile picture URL |
|
||||
| ↳ `linkedInUrl` | string | LinkedIn URL |
|
||||
|
||||
### `enrich_search_post_comments`
|
||||
|
||||
Get comments on a LinkedIn post.
|
||||
@@ -818,6 +882,44 @@ Get comments on a LinkedIn post.
|
||||
| ↳ `empathy` | number | Number of empathy reactions |
|
||||
| ↳ `other` | number | Number of other reactions |
|
||||
|
||||
### `enrich_search_post_comments_by_url`
|
||||
|
||||
Get comments on a LinkedIn post by its URL.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Enrich API key |
|
||||
| `postUrl` | string | Yes | LinkedIn post URL \(e.g., https://www.linkedin.com/posts/...\) |
|
||||
| `page` | number | No | Page number \(starts at 1, default: 1\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `page` | number | Current page number |
|
||||
| `totalPage` | number | Total number of pages |
|
||||
| `count` | number | Number of comments returned |
|
||||
| `comments` | array | Comments |
|
||||
| ↳ `activityId` | string | Comment activity ID |
|
||||
| ↳ `commentary` | string | Comment text |
|
||||
| ↳ `linkedInUrl` | string | Link to comment |
|
||||
| ↳ `commenter` | object | Commenter info |
|
||||
| ↳ `profileId` | string | Profile ID |
|
||||
| ↳ `firstName` | string | First name |
|
||||
| ↳ `lastName` | string | Last name |
|
||||
| ↳ `subTitle` | string | Subtitle/headline |
|
||||
| ↳ `profilePicture` | string | Profile picture URL |
|
||||
| ↳ `backgroundImage` | string | Background image URL |
|
||||
| ↳ `entityUrn` | string | Entity URN |
|
||||
| ↳ `objectUrn` | string | Object URN |
|
||||
| ↳ `profileType` | string | Profile type |
|
||||
| ↳ `reactionBreakdown` | object | Reactions on the comment |
|
||||
| ↳ `likes` | number | Number of likes |
|
||||
| ↳ `empathy` | number | Number of empathy reactions |
|
||||
| ↳ `other` | number | Number of other reactions |
|
||||
|
||||
### `enrich_search_people_activities`
|
||||
|
||||
Get a person
|
||||
|
||||
@@ -171,6 +171,13 @@ Delete a transcript from Fireflies.ai
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the transcript was successfully deleted |
|
||||
| `transcript` | object | The deleted transcript |
|
||||
| ↳ `id` | string | Transcript ID |
|
||||
| ↳ `title` | string | Meeting title |
|
||||
| ↳ `date` | number | Meeting timestamp |
|
||||
| ↳ `duration` | number | Meeting duration |
|
||||
| ↳ `host_email` | string | Host email address |
|
||||
| ↳ `organizer_email` | string | Organizer email address |
|
||||
|
||||
### `fireflies_add_to_live_meeting`
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ Retrieve transcripts of calls from Gong by call IDs or date range.
|
||||
|
||||
### `gong_get_extensive_calls`
|
||||
|
||||
Retrieve detailed call data including trackers, topics, and highlights from Gong.
|
||||
Retrieve detailed call data including trackers, topics, highlights, and AI spotlight content (brief, outline, key points, call outcome) from Gong.
|
||||
|
||||
#### Input
|
||||
|
||||
@@ -240,6 +240,17 @@ Retrieve detailed call data including trackers, topics, and highlights from Gong
|
||||
| ↳ `methods` | array | Whether invited or attended |
|
||||
| ↳ `context` | array | Links to external systems for this party |
|
||||
| ↳ `content` | object | Call content data |
|
||||
| ↳ `brief` | string | AI-generated brief summary of the call \(Call Spotlight\) |
|
||||
| ↳ `outline` | array | AI-generated call outline sections |
|
||||
| ↳ `section` | string | Outline section name |
|
||||
| ↳ `startTime` | number | Section start in seconds from call start |
|
||||
| ↳ `duration` | number | Section duration in seconds |
|
||||
| ↳ `keyPoints` | array | AI-generated key points of the call |
|
||||
| ↳ `text` | string | Key point text |
|
||||
| ↳ `callOutcome` | object | AI-determined call outcome \(Call Spotlight\) |
|
||||
| ↳ `id` | string | Outcome category ID |
|
||||
| ↳ `category` | string | Outcome category name |
|
||||
| ↳ `name` | string | Outcome name |
|
||||
| ↳ `structure` | array | Call agenda parts |
|
||||
| ↳ `name` | string | Agenda name |
|
||||
| ↳ `duration` | number | Duration of this part in seconds |
|
||||
@@ -425,6 +436,93 @@ Retrieve aggregated activity statistics for users by date range from Gong.
|
||||
| `toDateTime` | string | End of results in ISO-8601 format |
|
||||
| `cursor` | string | Pagination cursor for the next page |
|
||||
|
||||
### `gong_day_by_day_activity`
|
||||
|
||||
Retrieve detailed day-by-day activity (call IDs per activity type) for users by date range from Gong.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `accessKey` | string | Yes | Gong API Access Key |
|
||||
| `accessKeySecret` | string | Yes | Gong API Access Key Secret |
|
||||
| `userIds` | string | No | Comma-separated list of Gong user IDs \(up to 20 digits each\) |
|
||||
| `fromDate` | string | Yes | Start date in YYYY-MM-DD format \(inclusive, in company timezone\) |
|
||||
| `toDate` | string | Yes | End date in YYYY-MM-DD format \(exclusive, in company timezone, cannot exceed current day\) |
|
||||
| `cursor` | string | No | Pagination cursor from a previous response |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `requestId` | string | A Gong request reference ID for troubleshooting purposes |
|
||||
| `usersDetailedActivities` | array | Day-by-day activity per user, with call IDs grouped by activity type |
|
||||
| ↳ `userId` | string | Gong's unique numeric identifier for the user |
|
||||
| ↳ `userEmailAddress` | string | Email address of the Gong user |
|
||||
| ↳ `userDailyActivityStats` | array | One record per day in the date range |
|
||||
| ↳ `fromDate` | string | Start of the day \(ISO-8601\) |
|
||||
| ↳ `toDate` | string | End of the day \(ISO-8601\) |
|
||||
| ↳ `callsAsHost` | array | IDs of calls the user hosted |
|
||||
| ↳ `callsAttended` | array | IDs of calls the user attended \(not host\) |
|
||||
| ↳ `callsGaveFeedback` | array | IDs of calls the user gave feedback on |
|
||||
| ↳ `callsReceivedFeedback` | array | IDs of calls the user received feedback on |
|
||||
| ↳ `callsRequestedFeedback` | array | IDs of calls the user requested feedback on |
|
||||
| ↳ `callsScorecardsFilled` | array | IDs of calls the user filled scorecards on |
|
||||
| ↳ `callsScorecardsReceived` | array | IDs of the user's calls that received a scorecard |
|
||||
| ↳ `ownCallsListenedTo` | array | IDs of the user's own calls the user listened to |
|
||||
| ↳ `othersCallsListenedTo` | array | IDs of other users' calls the user listened to |
|
||||
| ↳ `callsSharedInternally` | array | IDs of calls the user shared internally |
|
||||
| ↳ `callsSharedExternally` | array | IDs of calls the user shared externally |
|
||||
| ↳ `callsCommentsGiven` | array | IDs of calls the user commented on |
|
||||
| ↳ `callsCommentsReceived` | array | IDs of the user's calls that received a comment |
|
||||
| ↳ `callsMarkedAsFeedbackGiven` | array | IDs of calls the user marked as reviewed |
|
||||
| ↳ `callsMarkedAsFeedbackReceived` | array | IDs of the user's calls marked as reviewed by others |
|
||||
| `cursor` | string | Pagination cursor for the next page |
|
||||
|
||||
### `gong_aggregate_by_period`
|
||||
|
||||
Retrieve aggregated user activity grouped into time periods (day, week, month, quarter, year) by date range from Gong.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `accessKey` | string | Yes | Gong API Access Key |
|
||||
| `accessKeySecret` | string | Yes | Gong API Access Key Secret |
|
||||
| `aggregationPeriod` | string | Yes | Calendar period to group activity by: DAY, WEEK, MONTH, QUARTER, or YEAR \(week starts Monday\) |
|
||||
| `userIds` | string | No | Comma-separated list of Gong user IDs \(up to 20 digits each\) |
|
||||
| `fromDate` | string | Yes | Start date in YYYY-MM-DD format \(inclusive, in company timezone\) |
|
||||
| `toDate` | string | Yes | End date in YYYY-MM-DD format \(exclusive, in company timezone, cannot exceed current day\) |
|
||||
| `cursor` | string | No | Pagination cursor from a previous response |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `requestId` | string | A Gong request reference ID for troubleshooting purposes |
|
||||
| `usersAggregateActivity` | array | Aggregated activity per user, one item per consecutive time period in the range |
|
||||
| ↳ `userId` | string | Gong's unique numeric identifier for the user |
|
||||
| ↳ `userEmailAddress` | string | Email address of the Gong user |
|
||||
| ↳ `userAggregateActivity` | array | Activity counts per time period |
|
||||
| ↳ `fromDate` | string | Start of the period \(ISO-8601\) |
|
||||
| ↳ `toDate` | string | End of the period \(ISO-8601\) |
|
||||
| ↳ `callsAsHost` | number | Calls the user hosted |
|
||||
| ↳ `callsAttended` | number | Calls the user attended \(not host\) |
|
||||
| ↳ `callsGaveFeedback` | number | Calls the user gave feedback on |
|
||||
| ↳ `callsReceivedFeedback` | number | Calls the user received feedback on |
|
||||
| ↳ `callsRequestedFeedback` | number | Calls the user requested feedback on |
|
||||
| ↳ `callsScorecardsFilled` | number | Scorecards the user completed |
|
||||
| ↳ `callsScorecardsReceived` | number | Calls where someone filled a scorecard on the user's calls |
|
||||
| ↳ `ownCallsListenedTo` | number | The user's own calls the user listened to |
|
||||
| ↳ `othersCallsListenedTo` | number | Other users' calls the user listened to |
|
||||
| ↳ `callsSharedInternally` | number | Calls the user shared internally |
|
||||
| ↳ `callsSharedExternally` | number | Calls the user shared externally |
|
||||
| ↳ `callsCommentsGiven` | number | Calls the user commented on |
|
||||
| ↳ `callsCommentsReceived` | number | Calls where the user's calls received a comment |
|
||||
| ↳ `callsMarkedAsFeedbackGiven` | number | Calls the user marked as reviewed |
|
||||
| ↳ `callsMarkedAsFeedbackReceived` | number | The user's calls marked as reviewed by others |
|
||||
| `cursor` | string | Pagination cursor for the next page |
|
||||
|
||||
### `gong_interaction_stats`
|
||||
|
||||
Retrieve interaction statistics for users by date range from Gong. Only includes calls with Whisper enabled.
|
||||
|
||||
@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="intercom_v2"
|
||||
color="#1F8DED"
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="mailchimp"
|
||||
color="#1A1A1A"
|
||||
color="#FFE01B"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="millionverifier"
|
||||
color="#00B67A"
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
|
||||
@@ -123,4 +123,105 @@ Delete a record from a ServiceNow table
|
||||
| `success` | boolean | Whether the deletion was successful |
|
||||
| `metadata` | json | Operation metadata |
|
||||
|
||||
### `servicenow_aggregate`
|
||||
|
||||
Compute aggregate statistics (count, sum, average, min, max, group by) over a ServiceNow table
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com\) |
|
||||
| `username` | string | Yes | ServiceNow username |
|
||||
| `password` | string | Yes | ServiceNow password |
|
||||
| `tableName` | string | Yes | Table name \(e.g., incident, change_request, task\) |
|
||||
| `query` | string | No | Encoded query string to filter records before aggregating \(e.g., "active=true"\) |
|
||||
| `count` | boolean | No | Return the count of matching records |
|
||||
| `groupBy` | string | No | Comma-separated fields to group results by \(e.g., category,priority\) |
|
||||
| `avgFields` | string | No | Comma-separated numeric fields to average \(e.g., reassignment_count\) |
|
||||
| `sumFields` | string | No | Comma-separated numeric fields to sum |
|
||||
| `minFields` | string | No | Comma-separated fields to compute the minimum of |
|
||||
| `maxFields` | string | No | Comma-separated fields to compute the maximum of |
|
||||
| `having` | string | No | Filter on aggregate results \(e.g., "count>5"\) |
|
||||
| `displayValue` | string | No | Return display values for grouped reference fields: "true", "false", or "all" |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `result` | json | Aggregate result. Ungrouped: \{stats: \{count, sum, avg, min, max\}\}. Grouped: array of \{stats, groupby_fields\}. |
|
||||
| `count` | number | Total matching record count \(only present for ungrouped count queries\) |
|
||||
| `metadata` | json | Operation metadata \(grouped, groupCount\) |
|
||||
|
||||
### `servicenow_list_attachments`
|
||||
|
||||
List the attachments on a ServiceNow record
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com\) |
|
||||
| `username` | string | Yes | ServiceNow username |
|
||||
| `password` | string | Yes | ServiceNow password |
|
||||
| `tableName` | string | Yes | Table that owns the record \(e.g., incident, change_request\) |
|
||||
| `recordSysId` | string | Yes | sys_id of the record whose attachments should be listed |
|
||||
| `limit` | number | No | Maximum number of attachments to return |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `attachments` | array | Attachment metadata records |
|
||||
| ↳ `sys_id` | string | Attachment sys_id |
|
||||
| ↳ `file_name` | string | File name |
|
||||
| ↳ `content_type` | string | MIME type |
|
||||
| ↳ `size_bytes` | string | File size in bytes |
|
||||
| ↳ `download_link` | string | Direct download URL for the file |
|
||||
| `metadata` | json | Operation metadata \(recordCount\) |
|
||||
|
||||
### `servicenow_download_attachment`
|
||||
|
||||
Download an attachment file from ServiceNow by its sys_id
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com\) |
|
||||
| `username` | string | Yes | ServiceNow username |
|
||||
| `password` | string | Yes | ServiceNow password |
|
||||
| `attachmentSysId` | string | Yes | sys_id of the attachment to download \(from List Attachments\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `file` | file | Downloaded attachment stored in execution files |
|
||||
| `content` | string | Base64 encoded file content |
|
||||
|
||||
### `servicenow_upload_attachment`
|
||||
|
||||
Attach a file to a ServiceNow record
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `instanceUrl` | string | Yes | ServiceNow instance URL \(e.g., https://instance.service-now.com\) |
|
||||
| `username` | string | Yes | ServiceNow username |
|
||||
| `password` | string | Yes | ServiceNow password |
|
||||
| `tableName` | string | Yes | Table that owns the record \(e.g., incident, change_request\) |
|
||||
| `recordSysId` | string | Yes | sys_id of the record to attach the file to |
|
||||
| `fileName` | string | Yes | Name to give the uploaded file \(e.g., logs.txt\) |
|
||||
| `file` | file | No | File to upload \(UserFile object\) |
|
||||
| `fileContent` | string | No | Base64-encoded file content \(legacy\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `attachment` | json | Created attachment metadata \(sys_id, file_name, content_type, download_link\) |
|
||||
| `metadata` | json | Operation metadata |
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="zerobounce"
|
||||
color="#330D49"
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="intercom"
|
||||
color="#1F8DED"
|
||||
color="#FFFFFF"
|
||||
/>
|
||||
|
||||
Intercom provides 6 triggers for automating workflows based on events.
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { getErrorMessage } from '@sim/utils/errors'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { servicenowUploadAttachmentContract } from '@/lib/api/contracts/tools/servicenow'
|
||||
import { parseRequest } from '@/lib/api/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
|
||||
import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils'
|
||||
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
|
||||
import { assertToolFileAccess } from '@/app/api/files/authorization'
|
||||
import type { ServiceNowAttachment } from '@/tools/servicenow/types'
|
||||
import { createBasicAuthHeader } from '@/tools/servicenow/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('ServiceNowUploadAttachmentAPI')
|
||||
|
||||
export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
const requestId = generateRequestId()
|
||||
|
||||
try {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
|
||||
if (!authResult.success || !authResult.userId) {
|
||||
logger.warn(`[${requestId}] Unauthorized ServiceNow upload attempt: ${authResult.error}`)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: authResult.error || 'Authentication required' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const parsed = await parseRequest(servicenowUploadAttachmentContract, request, {})
|
||||
if (!parsed.success) return parsed.response
|
||||
const body = parsed.data.body
|
||||
|
||||
if (!body.file) {
|
||||
return NextResponse.json({ success: false, error: 'A file is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
let userFile
|
||||
try {
|
||||
userFile = processSingleFileToUserFile(body.file, requestId, logger)
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: getErrorMessage(error, 'Failed to process file') },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const denied = await assertToolFileAccess(userFile.key, authResult.userId, requestId, logger)
|
||||
if (denied) return denied
|
||||
|
||||
const contentType = userFile.type || 'application/octet-stream'
|
||||
const fileBuffer = await downloadFileFromStorage(userFile, requestId, logger)
|
||||
|
||||
const baseUrl = body.instanceUrl.trim().replace(/\/$/, '')
|
||||
const uploadParams = new URLSearchParams({
|
||||
table_name: body.tableName.trim(),
|
||||
table_sys_id: body.recordSysId.trim(),
|
||||
file_name: body.fileName,
|
||||
})
|
||||
const uploadUrl = `${baseUrl}/api/now/attachment/file?${uploadParams.toString()}`
|
||||
|
||||
const response = await secureFetchWithValidation(
|
||||
uploadUrl,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: createBasicAuthHeader(body.username, body.password),
|
||||
'Content-Type': contentType,
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: fileBuffer,
|
||||
},
|
||||
'instanceUrl'
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = (await response.json().catch(() => ({}))) as {
|
||||
error?: { message?: string }
|
||||
}
|
||||
const errorMessage =
|
||||
errorData?.error?.message ??
|
||||
`ServiceNow API error: ${response.status} ${response.statusText}`
|
||||
logger.error(`[${requestId}] ServiceNow upload attachment failed`, {
|
||||
status: response.status,
|
||||
})
|
||||
return NextResponse.json({ success: false, error: errorMessage }, { status: response.status })
|
||||
}
|
||||
|
||||
const data = (await response.json()) as { result?: ServiceNowAttachment }
|
||||
const result = data.result
|
||||
|
||||
logger.info(`[${requestId}] File attached to ServiceNow record successfully`, {
|
||||
tableName: body.tableName,
|
||||
recordSysId: body.recordSysId,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
attachment: result
|
||||
? {
|
||||
sys_id: result.sys_id ?? null,
|
||||
file_name: result.file_name ?? null,
|
||||
content_type: result.content_type ?? null,
|
||||
size_bytes: result.size_bytes ?? null,
|
||||
table_name: result.table_name ?? null,
|
||||
table_sys_id: result.table_sys_id ?? null,
|
||||
download_link: result.download_link ?? null,
|
||||
}
|
||||
: null,
|
||||
metadata: { recordCount: 1 },
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error uploading attachment to ServiceNow:`, error)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: getErrorMessage(error, 'Internal server error') },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -28,21 +28,25 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
const changeJobDetailData: Record<string, unknown> = {
|
||||
Reason_Reference: wdRef('Change_Job_Subcategory_ID', data.reason),
|
||||
}
|
||||
if (data.newPositionId) {
|
||||
changeJobDetailData.Position_Reference = wdRef('Position_ID', data.newPositionId)
|
||||
}
|
||||
if (data.newJobProfileId) {
|
||||
changeJobDetailData.Job_Profile_Reference = wdRef('Job_Profile_ID', data.newJobProfileId)
|
||||
}
|
||||
if (data.newLocationId) {
|
||||
changeJobDetailData.Location_Reference = wdRef('Location_ID', data.newLocationId)
|
||||
}
|
||||
if (data.newSupervisoryOrgId) {
|
||||
changeJobDetailData.Supervisory_Organization_Reference = wdRef(
|
||||
'Supervisory_Organization_ID',
|
||||
'Organization_Reference_ID',
|
||||
data.newSupervisoryOrgId
|
||||
)
|
||||
}
|
||||
if (data.newPositionId) {
|
||||
changeJobDetailData.Proposed_Position_Reference = wdRef('Position_ID', data.newPositionId)
|
||||
}
|
||||
const jobDetailsData: Record<string, unknown> = {}
|
||||
if (data.newJobProfileId) {
|
||||
jobDetailsData.Job_Profile_Reference = wdRef('Job_Profile_ID', data.newJobProfileId)
|
||||
}
|
||||
if (data.newLocationId) {
|
||||
jobDetailsData.Location_Reference = wdRef('Location_ID', data.newLocationId)
|
||||
}
|
||||
if (Object.keys(jobDetailsData).length > 0) {
|
||||
changeJobDetailData.Job_Details_Data = jobDetailsData
|
||||
}
|
||||
|
||||
const client = await createWorkdaySoapClient(
|
||||
data.tenantUrl,
|
||||
|
||||
@@ -38,7 +38,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
Auto_Complete: true,
|
||||
Run_Now: true,
|
||||
},
|
||||
Change_Personal_Information_Data: {
|
||||
Change_Personal_Information_Business_Process_Data: {
|
||||
Person_Reference: wdRef('Employee_ID', data.workerId),
|
||||
Personal_Information_Data: data.fields,
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@ export const BrightDataBlock: BlockConfig<BrightDataResponse> = {
|
||||
docsLink: 'https://docs.sim.ai/tools/brightdata',
|
||||
category: 'tools',
|
||||
integrationType: IntegrationType.Search,
|
||||
bgColor: '#0F4C81',
|
||||
bgColor: '#FFFFFF',
|
||||
icon: BrightDataIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
|
||||
@@ -22,13 +22,17 @@ export const DatabricksBlock: BlockConfig<DatabricksResponse> = {
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Execute SQL', id: 'execute_sql' },
|
||||
{ label: 'Get Statement', id: 'get_statement' },
|
||||
{ label: 'List Warehouses', id: 'list_warehouses' },
|
||||
{ label: 'List Jobs', id: 'list_jobs' },
|
||||
{ label: 'Get Job', id: 'get_job' },
|
||||
{ label: 'Run Job', id: 'run_job' },
|
||||
{ label: 'Get Run', id: 'get_run' },
|
||||
{ label: 'List Runs', id: 'list_runs' },
|
||||
{ label: 'Cancel Run', id: 'cancel_run' },
|
||||
{ label: 'Get Run Output', id: 'get_run_output' },
|
||||
{ label: 'List Clusters', id: 'list_clusters' },
|
||||
{ label: 'Get Cluster', id: 'get_cluster' },
|
||||
],
|
||||
value: () => 'execute_sql',
|
||||
},
|
||||
@@ -83,6 +87,16 @@ export const DatabricksBlock: BlockConfig<DatabricksResponse> = {
|
||||
mode: 'advanced',
|
||||
},
|
||||
|
||||
// ── Get Statement ──
|
||||
{
|
||||
id: 'statementId',
|
||||
title: 'Statement ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter the statement ID',
|
||||
condition: { field: 'operation', value: 'get_statement' },
|
||||
required: { field: 'operation', value: 'get_statement' },
|
||||
},
|
||||
|
||||
// ── List Jobs ──
|
||||
{
|
||||
id: 'name',
|
||||
@@ -126,8 +140,8 @@ export const DatabricksBlock: BlockConfig<DatabricksResponse> = {
|
||||
title: 'Job ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter the job ID',
|
||||
condition: { field: 'operation', value: ['run_job', 'list_runs'] },
|
||||
required: { field: 'operation', value: 'run_job' },
|
||||
condition: { field: 'operation', value: ['run_job', 'list_runs', 'get_job'] },
|
||||
required: { field: 'operation', value: ['run_job', 'get_job'] },
|
||||
},
|
||||
{
|
||||
id: 'jobParameters',
|
||||
@@ -295,6 +309,16 @@ Return ONLY the numeric timestamp in milliseconds - no explanations, no extra te
|
||||
},
|
||||
},
|
||||
|
||||
// ── Get Cluster ──
|
||||
{
|
||||
id: 'clusterId',
|
||||
title: 'Cluster ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter the cluster ID',
|
||||
condition: { field: 'operation', value: 'get_cluster' },
|
||||
required: { field: 'operation', value: 'get_cluster' },
|
||||
},
|
||||
|
||||
// ── Credentials (common to all operations) ──
|
||||
{
|
||||
id: 'host',
|
||||
@@ -315,13 +339,17 @@ Return ONLY the numeric timestamp in milliseconds - no explanations, no extra te
|
||||
tools: {
|
||||
access: [
|
||||
'databricks_execute_sql',
|
||||
'databricks_get_statement',
|
||||
'databricks_list_warehouses',
|
||||
'databricks_list_jobs',
|
||||
'databricks_get_job',
|
||||
'databricks_run_job',
|
||||
'databricks_get_run',
|
||||
'databricks_list_runs',
|
||||
'databricks_cancel_run',
|
||||
'databricks_get_run_output',
|
||||
'databricks_list_clusters',
|
||||
'databricks_get_cluster',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => `databricks_${params.operation}`,
|
||||
@@ -350,6 +378,8 @@ Return ONLY the numeric timestamp in milliseconds - no explanations, no extra te
|
||||
apiKey: { type: 'string', description: 'Databricks Personal Access Token' },
|
||||
warehouseId: { type: 'string', description: 'SQL warehouse ID' },
|
||||
statement: { type: 'string', description: 'SQL statement to execute' },
|
||||
statementId: { type: 'string', description: 'Statement ID to poll for results' },
|
||||
clusterId: { type: 'string', description: 'Cluster ID to retrieve' },
|
||||
catalog: { type: 'string', description: 'Unity Catalog name' },
|
||||
schema: { type: 'string', description: 'Schema name' },
|
||||
rowLimit: { type: 'number', description: 'Maximum rows to return' },
|
||||
@@ -383,6 +413,25 @@ Return ONLY the numeric timestamp in milliseconds - no explanations, no extra te
|
||||
jobs: { type: 'json', description: 'List of jobs' },
|
||||
hasMore: { type: 'boolean', description: 'Whether more results are available' },
|
||||
nextPageToken: { type: 'string', description: 'Pagination token for next page' },
|
||||
// List Warehouses
|
||||
warehouses: {
|
||||
type: 'json',
|
||||
description:
|
||||
'List of SQL warehouses ([{warehouseId, name, clusterSize, state, warehouseType, ...}])',
|
||||
},
|
||||
// Get Job
|
||||
name: { type: 'string', description: 'Job name' },
|
||||
runAsUserName: { type: 'string', description: 'User the job runs as' },
|
||||
format: { type: 'string', description: 'Job format (SINGLE_TASK or MULTI_TASK)' },
|
||||
maxConcurrentRuns: { type: 'number', description: 'Maximum number of concurrent runs' },
|
||||
timeoutSeconds: { type: 'number', description: 'Job-level timeout in seconds' },
|
||||
createdTime: { type: 'number', description: 'Job creation timestamp (epoch ms)' },
|
||||
schedule: {
|
||||
type: 'json',
|
||||
description: 'Cron schedule (quartz_cron_expression, timezone_id, pause_status)',
|
||||
},
|
||||
tags: { type: 'json', description: 'Key-value tags applied to the job' },
|
||||
tasks: { type: 'json', description: 'Task definitions for the job' },
|
||||
// Run Job
|
||||
runId: { type: 'number', description: 'Triggered run ID' },
|
||||
numberInJob: { type: 'number', description: 'Run sequence number in job' },
|
||||
@@ -415,6 +464,11 @@ Return ONLY the numeric timestamp in milliseconds - no explanations, no extra te
|
||||
logsTruncated: { type: 'boolean', description: 'Whether logs were truncated' },
|
||||
// List Clusters
|
||||
clusters: { type: 'json', description: 'List of clusters' },
|
||||
// Get Cluster
|
||||
cluster: {
|
||||
type: 'json',
|
||||
description: 'Cluster detail (clusterId, clusterName, state, sparkVersion, autoscale, ...)',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+424
-27
@@ -27,7 +27,13 @@ export const DubBlock: BlockConfig<DubResponse> = {
|
||||
{ label: 'Update Link', id: 'update_link' },
|
||||
{ label: 'Delete Link', id: 'delete_link' },
|
||||
{ label: 'List Links', id: 'list_links' },
|
||||
{ label: 'Count Links', id: 'get_links_count' },
|
||||
{ label: 'Bulk Create Links', id: 'bulk_create_links' },
|
||||
{ label: 'Bulk Update Links', id: 'bulk_update_links' },
|
||||
{ label: 'Bulk Delete Links', id: 'bulk_delete_links' },
|
||||
{ label: 'Get Analytics', id: 'get_analytics' },
|
||||
{ label: 'List Events', id: 'get_events' },
|
||||
{ label: 'Get QR Code', id: 'get_qr_code' },
|
||||
],
|
||||
value: () => 'create_link',
|
||||
},
|
||||
@@ -158,6 +164,30 @@ export const DubBlock: BlockConfig<DubResponse> = {
|
||||
condition: { field: 'operation', value: ['create_link', 'upsert_link', 'update_link'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'linkRewrite',
|
||||
title: 'Link Cloaking',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: ['create_link', 'upsert_link', 'update_link'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'linkArchived',
|
||||
title: 'Archived',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: ['create_link', 'upsert_link', 'update_link'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'linkId',
|
||||
title: 'Link ID',
|
||||
@@ -232,32 +262,6 @@ export const DubBlock: BlockConfig<DubResponse> = {
|
||||
condition: { field: 'operation', value: 'list_links' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'sortBy',
|
||||
title: 'Sort By',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Created At', id: 'createdAt' },
|
||||
{ label: 'Clicks', id: 'clicks' },
|
||||
{ label: 'Sale Amount', id: 'saleAmount' },
|
||||
{ label: 'Last Clicked', id: 'lastClicked' },
|
||||
],
|
||||
value: () => 'createdAt',
|
||||
condition: { field: 'operation', value: 'list_links' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'sortOrder',
|
||||
title: 'Sort Order',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Descending', id: 'desc' },
|
||||
{ label: 'Ascending', id: 'asc' },
|
||||
],
|
||||
value: () => 'desc',
|
||||
condition: { field: 'operation', value: 'list_links' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'page',
|
||||
title: 'Page',
|
||||
@@ -392,6 +396,316 @@ export const DubBlock: BlockConfig<DubResponse> = {
|
||||
condition: { field: 'operation', value: 'get_analytics' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'countSearch',
|
||||
title: 'Search',
|
||||
type: 'short-input',
|
||||
placeholder: 'Search links by slug or destination URL',
|
||||
condition: { field: 'operation', value: 'get_links_count' },
|
||||
},
|
||||
{
|
||||
id: 'countDomain',
|
||||
title: 'Filter by Domain',
|
||||
type: 'short-input',
|
||||
placeholder: 'dub.sh',
|
||||
condition: { field: 'operation', value: 'get_links_count' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'countTagIds',
|
||||
title: 'Filter by Tag IDs',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated tag IDs',
|
||||
condition: { field: 'operation', value: 'get_links_count' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'countTagNames',
|
||||
title: 'Filter by Tag Names',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated tag names',
|
||||
condition: { field: 'operation', value: 'get_links_count' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'countFolderId',
|
||||
title: 'Filter by Folder ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Folder ID',
|
||||
condition: { field: 'operation', value: 'get_links_count' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'countShowArchived',
|
||||
title: 'Show Archived',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: 'get_links_count' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'countGroupBy',
|
||||
title: 'Group By',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'None (total)', id: '' },
|
||||
{ label: 'Domain', id: 'domain' },
|
||||
{ label: 'Tag', id: 'tagId' },
|
||||
{ label: 'User', id: 'userId' },
|
||||
{ label: 'Folder', id: 'folderId' },
|
||||
],
|
||||
value: () => '',
|
||||
condition: { field: 'operation', value: 'get_links_count' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'bulkLinks',
|
||||
title: 'Links',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
placeholder: '[\n { "url": "https://example.com", "key": "my-link" }\n]',
|
||||
condition: { field: 'operation', value: 'bulk_create_links' },
|
||||
required: { field: 'operation', value: 'bulk_create_links' },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate a JSON array of Dub link objects based on the user's description. Each object must include a "url" and may include "key", "domain", "tagIds" (array), and UTM fields. Return ONLY the JSON array - no explanations, no extra text.`,
|
||||
placeholder: 'Describe the links to create (e.g., "links for these 5 product pages")...',
|
||||
generationType: 'json-object',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'bulkUpdateLinkIds',
|
||||
title: 'Link IDs',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated link IDs (required unless External IDs is set)',
|
||||
condition: { field: 'operation', value: 'bulk_update_links' },
|
||||
},
|
||||
{
|
||||
id: 'bulkUpdateExternalIds',
|
||||
title: 'External IDs',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated external IDs (used if no link IDs)',
|
||||
condition: { field: 'operation', value: 'bulk_update_links' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'bulkUpdateData',
|
||||
title: 'Update Data',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
placeholder: '{\n "archived": true,\n "tagIds": ["tag_123"]\n}',
|
||||
condition: { field: 'operation', value: 'bulk_update_links' },
|
||||
required: { field: 'operation', value: 'bulk_update_links' },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate a JSON object of Dub link fields to update based on the user's description (e.g. archived, tagIds, expiresAt, comments, UTM fields). Return ONLY the JSON object - no explanations, no extra text.`,
|
||||
placeholder: 'Describe the changes to apply (e.g., "archive them and add the Q3 tag")...',
|
||||
generationType: 'json-object',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'bulkDeleteLinkIds',
|
||||
title: 'Link IDs',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated link IDs (max 100)',
|
||||
condition: { field: 'operation', value: 'bulk_delete_links' },
|
||||
required: { field: 'operation', value: 'bulk_delete_links' },
|
||||
},
|
||||
{
|
||||
id: 'eventsEvent',
|
||||
title: 'Event Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Clicks', id: 'clicks' },
|
||||
{ label: 'Leads', id: 'leads' },
|
||||
{ label: 'Sales', id: 'sales' },
|
||||
],
|
||||
value: () => 'clicks',
|
||||
condition: { field: 'operation', value: 'get_events' },
|
||||
},
|
||||
{
|
||||
id: 'eventsLinkId',
|
||||
title: 'Link ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter events by link ID',
|
||||
condition: { field: 'operation', value: 'get_events' },
|
||||
},
|
||||
{
|
||||
id: 'eventsExternalId',
|
||||
title: 'External ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by external ID (prefix with ext_)',
|
||||
condition: { field: 'operation', value: 'get_events' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'eventsDomain',
|
||||
title: 'Domain',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by domain',
|
||||
condition: { field: 'operation', value: 'get_events' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'eventsInterval',
|
||||
title: 'Interval',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: '24 Hours', id: '24h' },
|
||||
{ label: '7 Days', id: '7d' },
|
||||
{ label: '30 Days', id: '30d' },
|
||||
{ label: '90 Days', id: '90d' },
|
||||
{ label: '1 Year', id: '1y' },
|
||||
{ label: 'Month to Date', id: 'mtd' },
|
||||
{ label: 'Quarter to Date', id: 'qtd' },
|
||||
{ label: 'Year to Date', id: 'ytd' },
|
||||
{ label: 'All Time', id: 'all' },
|
||||
],
|
||||
value: () => '24h',
|
||||
condition: { field: 'operation', value: 'get_events' },
|
||||
},
|
||||
{
|
||||
id: 'eventsStart',
|
||||
title: 'Start Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'ISO 8601 date (overrides interval)',
|
||||
condition: { field: 'operation', value: 'get_events' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate an ISO 8601 timestamp based on the user's description. Return ONLY the timestamp string - no explanations, no extra text.`,
|
||||
placeholder: 'Describe the start date (e.g., "7 days ago", "start of month")...',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'eventsEnd',
|
||||
title: 'End Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'ISO 8601 date (defaults to now)',
|
||||
condition: { field: 'operation', value: 'get_events' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate an ISO 8601 timestamp based on the user's description. Return ONLY the timestamp string - no explanations, no extra text.`,
|
||||
placeholder: 'Describe the end date (e.g., "today", "end of last month")...',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'eventsCountry',
|
||||
title: 'Country',
|
||||
type: 'short-input',
|
||||
placeholder: 'ISO 3166-1 alpha-2 code (e.g., US)',
|
||||
condition: { field: 'operation', value: 'get_events' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'eventsTimezone',
|
||||
title: 'Timezone',
|
||||
type: 'short-input',
|
||||
placeholder: 'IANA timezone (e.g., America/New_York)',
|
||||
condition: { field: 'operation', value: 'get_events' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'eventsSortOrder',
|
||||
title: 'Sort Order',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Descending', id: 'desc' },
|
||||
{ label: 'Ascending', id: 'asc' },
|
||||
],
|
||||
value: () => 'desc',
|
||||
condition: { field: 'operation', value: 'get_events' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'eventsPage',
|
||||
title: 'Page',
|
||||
type: 'short-input',
|
||||
placeholder: '1',
|
||||
condition: { field: 'operation', value: 'get_events' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'eventsLimit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '100 (max: 1000)',
|
||||
condition: { field: 'operation', value: 'get_events' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'qrUrl',
|
||||
title: 'Short Link URL',
|
||||
type: 'short-input',
|
||||
placeholder: 'https://dub.sh/my-link',
|
||||
condition: { field: 'operation', value: 'get_qr_code' },
|
||||
required: { field: 'operation', value: 'get_qr_code' },
|
||||
},
|
||||
{
|
||||
id: 'qrSize',
|
||||
title: 'Size (px)',
|
||||
type: 'short-input',
|
||||
placeholder: '600',
|
||||
condition: { field: 'operation', value: 'get_qr_code' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'qrLevel',
|
||||
title: 'Error Correction',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Low (L)', id: 'L' },
|
||||
{ label: 'Medium (M)', id: 'M' },
|
||||
{ label: 'Quartile (Q)', id: 'Q' },
|
||||
{ label: 'High (H)', id: 'H' },
|
||||
],
|
||||
value: () => 'L',
|
||||
condition: { field: 'operation', value: 'get_qr_code' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'qrFgColor',
|
||||
title: 'Foreground Color',
|
||||
type: 'short-input',
|
||||
placeholder: '#000000',
|
||||
condition: { field: 'operation', value: 'get_qr_code' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'qrBgColor',
|
||||
title: 'Background Color',
|
||||
type: 'short-input',
|
||||
placeholder: '#FFFFFF',
|
||||
condition: { field: 'operation', value: 'get_qr_code' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'qrHideLogo',
|
||||
title: 'Hide Logo',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: 'get_qr_code' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'qrMargin',
|
||||
title: 'Margin',
|
||||
type: 'short-input',
|
||||
placeholder: '2',
|
||||
condition: { field: 'operation', value: 'get_qr_code' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'apiKey',
|
||||
title: 'API Key',
|
||||
@@ -409,12 +723,26 @@ export const DubBlock: BlockConfig<DubResponse> = {
|
||||
'dub_update_link',
|
||||
'dub_delete_link',
|
||||
'dub_list_links',
|
||||
'dub_get_links_count',
|
||||
'dub_bulk_create_links',
|
||||
'dub_bulk_update_links',
|
||||
'dub_bulk_delete_links',
|
||||
'dub_get_analytics',
|
||||
'dub_get_events',
|
||||
'dub_get_qr_code',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => `dub_${params.operation}`,
|
||||
params: (params) => {
|
||||
const result: Record<string, unknown> = {}
|
||||
if (
|
||||
params.operation === 'create_link' ||
|
||||
params.operation === 'upsert_link' ||
|
||||
params.operation === 'update_link'
|
||||
) {
|
||||
if (params.linkRewrite === 'true') result.rewrite = true
|
||||
if (params.linkArchived === 'true') result.archived = true
|
||||
}
|
||||
if (params.operation === 'get_link') {
|
||||
if (params.getLinkExternalId) result.externalId = params.getLinkExternalId
|
||||
if (params.getLinkDomain) result.domain = params.getLinkDomain
|
||||
@@ -442,6 +770,49 @@ export const DubBlock: BlockConfig<DubResponse> = {
|
||||
if (params.analyticsCountry) result.country = params.analyticsCountry
|
||||
if (params.analyticsTimezone) result.timezone = params.analyticsTimezone
|
||||
}
|
||||
if (params.operation === 'get_links_count') {
|
||||
if (params.countSearch) result.search = params.countSearch
|
||||
if (params.countDomain) result.domain = params.countDomain
|
||||
if (params.countTagIds) result.tagIds = params.countTagIds
|
||||
if (params.countTagNames) result.tagNames = params.countTagNames
|
||||
if (params.countFolderId) result.folderId = params.countFolderId
|
||||
if (params.countShowArchived === 'true') result.showArchived = true
|
||||
if (params.countGroupBy) result.groupBy = params.countGroupBy
|
||||
}
|
||||
if (params.operation === 'bulk_create_links') {
|
||||
if (params.bulkLinks) result.links = params.bulkLinks
|
||||
}
|
||||
if (params.operation === 'bulk_update_links') {
|
||||
if (params.bulkUpdateLinkIds) result.linkIds = params.bulkUpdateLinkIds
|
||||
if (params.bulkUpdateExternalIds) result.externalIds = params.bulkUpdateExternalIds
|
||||
if (params.bulkUpdateData) result.data = params.bulkUpdateData
|
||||
}
|
||||
if (params.operation === 'bulk_delete_links') {
|
||||
if (params.bulkDeleteLinkIds) result.linkIds = params.bulkDeleteLinkIds
|
||||
}
|
||||
if (params.operation === 'get_events') {
|
||||
if (params.eventsEvent) result.event = params.eventsEvent
|
||||
if (params.eventsLinkId) result.linkId = params.eventsLinkId
|
||||
if (params.eventsExternalId) result.externalId = params.eventsExternalId
|
||||
if (params.eventsDomain) result.domain = params.eventsDomain
|
||||
if (params.eventsInterval) result.interval = params.eventsInterval
|
||||
if (params.eventsStart) result.start = params.eventsStart
|
||||
if (params.eventsEnd) result.end = params.eventsEnd
|
||||
if (params.eventsCountry) result.country = params.eventsCountry
|
||||
if (params.eventsTimezone) result.timezone = params.eventsTimezone
|
||||
if (params.eventsSortOrder) result.sortOrder = params.eventsSortOrder
|
||||
if (params.eventsPage) result.page = Number(params.eventsPage)
|
||||
if (params.eventsLimit) result.limit = Number(params.eventsLimit)
|
||||
}
|
||||
if (params.operation === 'get_qr_code') {
|
||||
if (params.qrUrl) result.url = params.qrUrl
|
||||
if (params.qrSize) result.size = Number(params.qrSize)
|
||||
if (params.qrLevel) result.level = params.qrLevel
|
||||
if (params.qrFgColor) result.fgColor = params.qrFgColor
|
||||
if (params.qrBgColor) result.bgColor = params.qrBgColor
|
||||
if (params.qrHideLogo === 'true') result.hideLogo = true
|
||||
if (params.qrMargin) result.margin = Number(params.qrMargin)
|
||||
}
|
||||
return result
|
||||
},
|
||||
},
|
||||
@@ -454,6 +825,9 @@ export const DubBlock: BlockConfig<DubResponse> = {
|
||||
domain: { type: 'string', description: 'Custom domain for the short link' },
|
||||
key: { type: 'string', description: 'Custom slug for the short link' },
|
||||
search: { type: 'string', description: 'Search query for listing links' },
|
||||
links: { type: 'json', description: 'JSON array of link objects for bulk create' },
|
||||
data: { type: 'json', description: 'JSON object of fields to apply for bulk update' },
|
||||
linkIds: { type: 'string', description: 'Comma-separated link IDs for bulk operations' },
|
||||
},
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Link ID' },
|
||||
@@ -483,11 +857,34 @@ export const DubBlock: BlockConfig<DubResponse> = {
|
||||
type: 'json',
|
||||
description: 'Array of links (id, domain, key, url, shortLink, clicks, tags, createdAt)',
|
||||
},
|
||||
count: { type: 'number', description: 'Number of links returned (list operation)' },
|
||||
count: { type: 'number', description: 'Number of items returned (list/count/events/bulk)' },
|
||||
data: {
|
||||
type: 'json',
|
||||
description: 'Grouped analytics data (timeseries, countries, devices, etc.)',
|
||||
},
|
||||
groups: {
|
||||
type: 'json',
|
||||
description: 'Per-group link counts when Count Links uses groupBy ([{ field, count }])',
|
||||
},
|
||||
events: {
|
||||
type: 'json',
|
||||
description: 'Array of events (event, timestamp, click, link, customer/sale data)',
|
||||
},
|
||||
created: {
|
||||
type: 'json',
|
||||
description: 'Bulk create: array of successfully created link objects',
|
||||
},
|
||||
errors: {
|
||||
type: 'json',
|
||||
description: 'Bulk create: array of per-link errors ({ link, error, code })',
|
||||
},
|
||||
updated: {
|
||||
type: 'json',
|
||||
description: 'Bulk update: array of updated link objects',
|
||||
},
|
||||
deletedCount: { type: 'number', description: 'Bulk delete: number of links deleted' },
|
||||
file: { type: 'file', description: 'QR code image (PNG) stored in execution files' },
|
||||
content: { type: 'string', description: 'QR code as base64-encoded PNG data' },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -27,11 +27,13 @@ export const DuckDuckGoBlock: BlockConfig<DuckDuckGoResponse> = {
|
||||
title: 'Remove HTML',
|
||||
type: 'switch',
|
||||
defaultValue: true,
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'skipDisambig',
|
||||
title: 'Skip Disambiguation',
|
||||
type: 'switch',
|
||||
mode: 'advanced',
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
@@ -51,10 +53,17 @@ export const DuckDuckGoBlock: BlockConfig<DuckDuckGoResponse> = {
|
||||
abstractText: { type: 'string', description: 'Plain text version of the abstract' },
|
||||
abstractSource: { type: 'string', description: 'The source of the abstract' },
|
||||
abstractURL: { type: 'string', description: 'URL to the source of the abstract' },
|
||||
definition: { type: 'string', description: 'Dictionary-style definition if available' },
|
||||
definitionSource: { type: 'string', description: 'The source of the definition' },
|
||||
definitionURL: { type: 'string', description: 'URL to the source of the definition' },
|
||||
image: { type: 'string', description: 'URL to an image related to the topic' },
|
||||
answer: { type: 'string', description: 'Direct answer if available' },
|
||||
answerType: { type: 'string', description: 'Type of the answer' },
|
||||
type: { type: 'string', description: 'Response type (A, D, C, N, E)' },
|
||||
redirect: {
|
||||
type: 'string',
|
||||
description: '!bang redirect URL, populated only for bang queries',
|
||||
},
|
||||
relatedTopics: { type: 'json', description: 'Array of related topics' },
|
||||
results: { type: 'json', description: 'Array of external link results' },
|
||||
},
|
||||
|
||||
@@ -47,11 +47,14 @@ export const EnrichBlock: BlockConfig = {
|
||||
{ label: 'Search Company Employees', id: 'search_company_employees' },
|
||||
{ label: 'Search Similar Companies', id: 'search_similar_companies' },
|
||||
{ label: 'Sales Pointer (People)', id: 'sales_pointer_people' },
|
||||
{ label: 'Search Jobs', id: 'search_jobs' },
|
||||
// LinkedIn Posts/Activities
|
||||
{ label: 'Search Posts', id: 'search_posts' },
|
||||
{ label: 'Get Post Details', id: 'get_post_details' },
|
||||
{ label: 'Search Post Reactions', id: 'search_post_reactions' },
|
||||
{ label: 'Search Post Reactions (by URL)', id: 'search_post_reactions_by_url' },
|
||||
{ label: 'Search Post Comments', id: 'search_post_comments' },
|
||||
{ label: 'Search Post Comments (by URL)', id: 'search_post_comments_by_url' },
|
||||
{ label: 'Search People Activities', id: 'search_people_activities' },
|
||||
{ label: 'Search Company Activities', id: 'search_company_activities' },
|
||||
// Other
|
||||
@@ -351,8 +354,8 @@ export const EnrichBlock: BlockConfig = {
|
||||
title: 'Keywords',
|
||||
type: 'short-input',
|
||||
placeholder: 'AI automation',
|
||||
condition: { field: 'operation', value: 'search_posts' },
|
||||
required: { field: 'operation', value: 'search_posts' },
|
||||
condition: { field: 'operation', value: ['search_posts', 'search_jobs'] },
|
||||
required: { field: 'operation', value: ['search_posts', 'search_jobs'] },
|
||||
},
|
||||
{
|
||||
id: 'datePosted',
|
||||
@@ -367,13 +370,103 @@ export const EnrichBlock: BlockConfig = {
|
||||
condition: { field: 'operation', value: 'search_posts' },
|
||||
},
|
||||
|
||||
{
|
||||
id: 'jobLocation',
|
||||
title: 'Location',
|
||||
type: 'short-input',
|
||||
placeholder: 'London',
|
||||
condition: { field: 'operation', value: 'search_jobs' },
|
||||
},
|
||||
{
|
||||
id: 'timePosted',
|
||||
title: 'Time Posted',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Any time', id: '' },
|
||||
{ label: 'Past 24 hours', id: 'past_24hrs' },
|
||||
{ label: 'Past week', id: 'past_week' },
|
||||
{ label: 'Past month', id: 'past_month' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'search_jobs' },
|
||||
},
|
||||
{
|
||||
id: 'jobTypes',
|
||||
title: 'Job Types',
|
||||
type: 'short-input',
|
||||
placeholder: 'full time, part time',
|
||||
condition: { field: 'operation', value: 'search_jobs' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
placeholder: 'Describe the job types to include',
|
||||
prompt:
|
||||
'Convert the request into a comma-separated list of LinkedIn job types (e.g., full time, part time, contract, internship, temporary). Return ONLY the comma-separated list - no explanations, no extra text.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'workplaceTypes',
|
||||
title: 'Workplace Types',
|
||||
type: 'short-input',
|
||||
placeholder: 'on site, remote',
|
||||
condition: { field: 'operation', value: 'search_jobs' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
placeholder: 'Describe the workplace types to include',
|
||||
prompt:
|
||||
'Convert the request into a comma-separated list of LinkedIn workplace types (on site, remote, hybrid). Return ONLY the comma-separated list - no explanations, no extra text.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'experienceLevels',
|
||||
title: 'Experience Levels',
|
||||
type: 'short-input',
|
||||
placeholder: 'internship, associate',
|
||||
condition: { field: 'operation', value: 'search_jobs' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
placeholder: 'Describe the experience levels to include',
|
||||
prompt:
|
||||
'Convert the request into a comma-separated list of LinkedIn experience levels (internship, entry level, associate, mid-senior level, director, executive). Return ONLY the comma-separated list - no explanations, no extra text.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'jobCompanyIds',
|
||||
title: 'Company IDs',
|
||||
type: 'short-input',
|
||||
placeholder: '2048, 3050',
|
||||
condition: { field: 'operation', value: 'search_jobs' },
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
placeholder: 'Describe the companies to filter by',
|
||||
prompt:
|
||||
'Convert the request into a comma-separated list of LinkedIn company IDs (numeric). Return ONLY the comma-separated list - no explanations, no extra text.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'start',
|
||||
title: 'Start Offset',
|
||||
type: 'short-input',
|
||||
placeholder: '0',
|
||||
condition: { field: 'operation', value: 'search_jobs' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'postUrl',
|
||||
title: 'LinkedIn Post URL',
|
||||
type: 'short-input',
|
||||
placeholder: 'https://www.linkedin.com/posts/...',
|
||||
condition: { field: 'operation', value: 'get_post_details' },
|
||||
required: { field: 'operation', value: 'get_post_details' },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_post_details', 'search_post_reactions_by_url', 'search_post_comments_by_url'],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: ['get_post_details', 'search_post_reactions_by_url', 'search_post_comments_by_url'],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
@@ -402,7 +495,11 @@ export const EnrichBlock: BlockConfig = {
|
||||
{ label: 'Insightful', id: 'insightful' },
|
||||
{ label: 'Funny', id: 'funny' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'search_post_reactions' },
|
||||
value: () => 'all',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['search_post_reactions', 'search_post_reactions_by_url'],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
@@ -422,6 +519,7 @@ export const EnrichBlock: BlockConfig = {
|
||||
{ label: 'Comments', id: 'comments' },
|
||||
{ label: 'Articles', id: 'articles' },
|
||||
],
|
||||
value: () => 'posts',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['search_people_activities', 'search_company_activities'],
|
||||
@@ -469,10 +567,15 @@ export const EnrichBlock: BlockConfig = {
|
||||
'sales_pointer_people',
|
||||
'search_posts',
|
||||
'search_post_reactions',
|
||||
'search_post_reactions_by_url',
|
||||
'search_post_comments',
|
||||
'search_post_comments_by_url',
|
||||
],
|
||||
},
|
||||
required: { field: 'operation', value: 'sales_pointer_people' },
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: ['sales_pointer_people', 'search_post_reactions', 'search_post_reactions_by_url'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'pageSize',
|
||||
@@ -519,10 +622,13 @@ export const EnrichBlock: BlockConfig = {
|
||||
'enrich_search_company_employees',
|
||||
'enrich_search_similar_companies',
|
||||
'enrich_sales_pointer_people',
|
||||
'enrich_search_jobs',
|
||||
'enrich_search_posts',
|
||||
'enrich_get_post_details',
|
||||
'enrich_search_post_reactions',
|
||||
'enrich_search_post_reactions_by_url',
|
||||
'enrich_search_post_comments',
|
||||
'enrich_search_post_comments_by_url',
|
||||
'enrich_search_people_activities',
|
||||
'enrich_search_company_activities',
|
||||
'enrich_reverse_hash_lookup',
|
||||
@@ -594,6 +700,12 @@ export const EnrichBlock: BlockConfig = {
|
||||
if (operation === 'search_logo') {
|
||||
parsedParams.url = rest.domain
|
||||
}
|
||||
if (operation === 'search_jobs') {
|
||||
parsedParams.location = rest.jobLocation
|
||||
parsedParams.jobLocation = undefined
|
||||
parsedParams.companyIds = rest.jobCompanyIds
|
||||
parsedParams.jobCompanyIds = undefined
|
||||
}
|
||||
|
||||
if (parsedParams.page) {
|
||||
const pageNum = Number(parsedParams.page)
|
||||
@@ -607,6 +719,7 @@ export const EnrichBlock: BlockConfig = {
|
||||
if (parsedParams.pageSize) parsedParams.pageSize = Number(parsedParams.pageSize)
|
||||
if (parsedParams.num) parsedParams.num = Number(parsedParams.num)
|
||||
if (parsedParams.offset) parsedParams.offset = Number(parsedParams.offset)
|
||||
if (parsedParams.start) parsedParams.start = Number(parsedParams.start)
|
||||
if (parsedParams.staffCountMin)
|
||||
parsedParams.staffCountMin = Number(parsedParams.staffCountMin)
|
||||
if (parsedParams.staffCountMax)
|
||||
|
||||
@@ -179,6 +179,18 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
value: ['fireflies_list_transcripts', 'fireflies_list_bites'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'skip',
|
||||
title: 'Skip',
|
||||
type: 'short-input',
|
||||
placeholder: 'Number of results to skip (default: 0)',
|
||||
required: false,
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['fireflies_list_transcripts', 'fireflies_list_bites'],
|
||||
},
|
||||
},
|
||||
// Upload Audio fields - File upload (basic mode)
|
||||
{
|
||||
id: 'audioFile',
|
||||
@@ -380,6 +392,21 @@ Return ONLY the valid JSON array - no explanations, no markdown code blocks.`,
|
||||
value: 'fireflies_create_bite',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'mediaType',
|
||||
title: 'Media Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Video', id: 'video' },
|
||||
{ label: 'Audio', id: 'audio' },
|
||||
],
|
||||
required: false,
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'fireflies_create_bite',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'biteName',
|
||||
title: 'Bite Name',
|
||||
@@ -453,6 +480,7 @@ Return ONLY the summary text - no quotes, no labels.`,
|
||||
hostEmail: params.hostEmail || undefined,
|
||||
participants: params.participants || undefined,
|
||||
limit: params.limit ? Number(params.limit) : undefined,
|
||||
skip: params.skip ? Number(params.skip) : undefined,
|
||||
}
|
||||
|
||||
case 'fireflies_get_transcript':
|
||||
@@ -528,6 +556,7 @@ Return ONLY the summary text - no quotes, no labels.`,
|
||||
startTime: Number(params.startTime),
|
||||
endTime: Number(params.endTime),
|
||||
name: params.biteName?.trim() || undefined,
|
||||
mediaType: params.mediaType?.trim() || undefined,
|
||||
summary: params.biteSummary?.trim() || undefined,
|
||||
}
|
||||
|
||||
@@ -537,6 +566,7 @@ Return ONLY the summary text - no quotes, no labels.`,
|
||||
transcriptId: params.transcriptId?.trim() || undefined,
|
||||
mine: true,
|
||||
limit: params.limit ? Number(params.limit) : undefined,
|
||||
skip: params.skip ? Number(params.skip) : undefined,
|
||||
}
|
||||
|
||||
case 'fireflies_list_contacts':
|
||||
@@ -559,6 +589,7 @@ Return ONLY the summary text - no quotes, no labels.`,
|
||||
hostEmail: { type: 'string', description: 'Filter by host email' },
|
||||
participants: { type: 'string', description: 'Filter by participants (comma-separated)' },
|
||||
limit: { type: 'number', description: 'Maximum results to return' },
|
||||
skip: { type: 'number', description: 'Number of results to skip for pagination' },
|
||||
audioFile: { type: 'json', description: 'Audio/video file (canonical param)' },
|
||||
audioUrl: { type: 'string', description: 'Public URL to audio file' },
|
||||
title: { type: 'string', description: 'Meeting title' },
|
||||
@@ -570,6 +601,7 @@ Return ONLY the summary text - no quotes, no labels.`,
|
||||
duration: { type: 'number', description: 'Meeting duration in minutes (15-120)' },
|
||||
startTime: { type: 'number', description: 'Bite start time in seconds' },
|
||||
endTime: { type: 'number', description: 'Bite end time in seconds' },
|
||||
mediaType: { type: 'string', description: 'Bite media type (video or audio)' },
|
||||
biteName: { type: 'string', description: 'Name for the bite/highlight' },
|
||||
biteSummary: { type: 'string', description: 'Summary for the bite' },
|
||||
},
|
||||
|
||||
@@ -33,6 +33,8 @@ export const GongBlock: BlockConfig<GongResponse> = {
|
||||
{ label: 'List Users', id: 'list_users' },
|
||||
{ label: 'Get User', id: 'get_user' },
|
||||
{ label: 'Aggregate Activity', id: 'aggregate_activity' },
|
||||
{ label: 'Day-by-Day Activity', id: 'day_by_day_activity' },
|
||||
{ label: 'Aggregate by Period', id: 'aggregate_by_period' },
|
||||
{ label: 'Interaction Stats', id: 'interaction_stats' },
|
||||
{ label: 'Answered Scorecards', id: 'answered_scorecards' },
|
||||
{ label: 'List Library Folders', id: 'list_library_folders' },
|
||||
@@ -319,8 +321,24 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes
|
||||
title: 'From Date',
|
||||
type: 'short-input',
|
||||
placeholder: '2024-01-01 (YYYY-MM-DD, inclusive)',
|
||||
condition: { field: 'operation', value: ['aggregate_activity', 'interaction_stats'] },
|
||||
required: { field: 'operation', value: ['aggregate_activity', 'interaction_stats'] },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'aggregate_activity',
|
||||
'day_by_day_activity',
|
||||
'aggregate_by_period',
|
||||
'interaction_stats',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'aggregate_activity',
|
||||
'day_by_day_activity',
|
||||
'aggregate_by_period',
|
||||
'interaction_stats',
|
||||
],
|
||||
},
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate a date string in YYYY-MM-DD format based on the user's description.
|
||||
@@ -340,8 +358,24 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
||||
title: 'To Date',
|
||||
type: 'short-input',
|
||||
placeholder: '2024-01-31 (YYYY-MM-DD, exclusive)',
|
||||
condition: { field: 'operation', value: ['aggregate_activity', 'interaction_stats'] },
|
||||
required: { field: 'operation', value: ['aggregate_activity', 'interaction_stats'] },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'aggregate_activity',
|
||||
'day_by_day_activity',
|
||||
'aggregate_by_period',
|
||||
'interaction_stats',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'aggregate_activity',
|
||||
'day_by_day_activity',
|
||||
'aggregate_by_period',
|
||||
'interaction_stats',
|
||||
],
|
||||
},
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate a date string in YYYY-MM-DD format based on the user's description.
|
||||
@@ -361,10 +395,35 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
|
||||
title: 'User IDs',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated user IDs (optional)',
|
||||
condition: { field: 'operation', value: ['aggregate_activity', 'interaction_stats'] },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'aggregate_activity',
|
||||
'day_by_day_activity',
|
||||
'aggregate_by_period',
|
||||
'interaction_stats',
|
||||
],
|
||||
},
|
||||
mode: 'advanced',
|
||||
},
|
||||
|
||||
// Aggregate by Period inputs
|
||||
{
|
||||
id: 'aggregationPeriod',
|
||||
title: 'Aggregation Period',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Day', id: 'DAY' },
|
||||
{ label: 'Week', id: 'WEEK' },
|
||||
{ label: 'Month', id: 'MONTH' },
|
||||
{ label: 'Quarter', id: 'QUARTER' },
|
||||
{ label: 'Year', id: 'YEAR' },
|
||||
],
|
||||
value: () => 'WEEK',
|
||||
condition: { field: 'operation', value: 'aggregate_by_period' },
|
||||
required: { field: 'operation', value: 'aggregate_by_period' },
|
||||
},
|
||||
|
||||
// Answered Scorecards inputs
|
||||
{
|
||||
id: 'callFromDate',
|
||||
@@ -585,6 +644,8 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes
|
||||
'get_extensive_calls',
|
||||
'list_users',
|
||||
'aggregate_activity',
|
||||
'day_by_day_activity',
|
||||
'aggregate_by_period',
|
||||
'interaction_stats',
|
||||
'answered_scorecards',
|
||||
'list_flows',
|
||||
@@ -621,6 +682,8 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes
|
||||
'gong_list_users',
|
||||
'gong_get_user',
|
||||
'gong_aggregate_activity',
|
||||
'gong_day_by_day_activity',
|
||||
'gong_aggregate_by_period',
|
||||
'gong_interaction_stats',
|
||||
'gong_answered_scorecards',
|
||||
'gong_list_library_folders',
|
||||
@@ -677,6 +740,10 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes
|
||||
callIds: { type: 'string', description: 'Comma-separated call IDs' },
|
||||
userId: { type: 'string', description: 'Gong user ID' },
|
||||
userIds: { type: 'string', description: 'Comma-separated user IDs' },
|
||||
aggregationPeriod: {
|
||||
type: 'string',
|
||||
description: 'Calendar period for aggregate-by-period (DAY/WEEK/MONTH/QUARTER/YEAR)',
|
||||
},
|
||||
statsFromDate: { type: 'string', description: 'Start date in YYYY-MM-DD format (stats)' },
|
||||
statsToDate: { type: 'string', description: 'End date in YYYY-MM-DD format (stats)' },
|
||||
callFromDate: { type: 'string', description: 'Call start date in YYYY-MM-DD (scorecards)' },
|
||||
@@ -701,10 +768,152 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes
|
||||
cursor: { type: 'string', description: 'Pagination cursor' },
|
||||
},
|
||||
outputs: {
|
||||
response: {
|
||||
// Shared across most operations
|
||||
requestId: { type: 'string', description: 'Gong request reference ID for troubleshooting' },
|
||||
cursor: { type: 'string', description: 'Pagination cursor for the next page' },
|
||||
totalRecords: { type: 'number', description: 'Total number of records matching the filter' },
|
||||
currentPageSize: { type: 'number', description: 'Number of records in the current page' },
|
||||
currentPageNumber: { type: 'number', description: 'Current page number' },
|
||||
|
||||
// list_calls / get_extensive_calls / get_folder_content / lookup_email / lookup_phone
|
||||
calls: {
|
||||
type: 'json',
|
||||
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.',
|
||||
'Calls returned by the operation (shape varies: call list, extensive calls, folder calls, or call references)',
|
||||
},
|
||||
|
||||
// create_call / get_call
|
||||
callId: { type: 'string', description: 'Gong call ID of the created call' },
|
||||
url: { type: 'string', description: 'URL to the call in the Gong web app' },
|
||||
id: { type: 'string', description: 'Gong ID of the returned call or user' },
|
||||
title: { type: 'string', description: 'Call title' },
|
||||
scheduled: { type: 'string', description: 'Scheduled call time (ISO-8601)' },
|
||||
started: { type: 'string', description: 'Recording start time (ISO-8601)' },
|
||||
duration: { type: 'number', description: 'Call duration in seconds' },
|
||||
direction: { type: 'string', description: 'Call direction (Inbound/Outbound)' },
|
||||
system: { type: 'string', description: 'Communication platform used' },
|
||||
scope: { type: 'string', description: "Call scope: 'Internal', 'External', or 'Unknown'" },
|
||||
media: { type: 'string', description: 'Media type (e.g., Video)' },
|
||||
language: { type: 'string', description: 'Language code (ISO-639-2B)' },
|
||||
primaryUserId: { type: 'string', description: 'Host team member identifier' },
|
||||
workspaceId: { type: 'string', description: 'Workspace identifier' },
|
||||
sdrDisposition: { type: 'string', description: 'SDR disposition classification' },
|
||||
clientUniqueId: {
|
||||
type: 'string',
|
||||
description: 'Call identifier from the origin recording system',
|
||||
},
|
||||
customData: { type: 'string', description: 'Metadata provided during call creation' },
|
||||
purpose: { type: 'string', description: 'Call purpose' },
|
||||
meetingUrl: { type: 'string', description: 'Web conference provider URL' },
|
||||
isPrivate: { type: 'boolean', description: 'Whether the call is private' },
|
||||
calendarEventId: { type: 'string', description: 'Calendar event identifier' },
|
||||
|
||||
// get_call_transcript
|
||||
callTranscripts: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Call transcripts: [{callId, transcript: [{speakerId, topic, sentences: [{start, end, text}]}]}]',
|
||||
},
|
||||
|
||||
// list_users / get_user
|
||||
users: { type: 'json', description: 'List of Gong users with profile and settings fields' },
|
||||
emailAddress: { type: 'string', description: 'User email address' },
|
||||
created: { type: 'string', description: 'User creation timestamp (ISO-8601)' },
|
||||
active: { type: 'boolean', description: 'Whether the user is active' },
|
||||
emailAliases: { type: 'json', description: "User's alternative email addresses" },
|
||||
trustedEmailAddress: { type: 'string', description: 'Trusted email address for the user' },
|
||||
firstName: { type: 'string', description: 'User first name' },
|
||||
lastName: { type: 'string', description: 'User last name' },
|
||||
phoneNumber: { type: 'string', description: 'User phone number' },
|
||||
extension: { type: 'string', description: 'Phone extension number' },
|
||||
personalMeetingUrls: { type: 'json', description: 'Personal meeting URLs' },
|
||||
settings: { type: 'json', description: 'User settings (recording, import, and consent flags)' },
|
||||
managerId: { type: 'string', description: 'Manager user ID' },
|
||||
meetingConsentPageUrl: { type: 'string', description: 'Meeting consent page URL' },
|
||||
spokenLanguages: { type: 'json', description: 'Languages spoken: [{language, primary}]' },
|
||||
|
||||
// aggregate_activity / interaction_stats / day_by_day_activity / aggregate_by_period
|
||||
usersActivity: { type: 'json', description: 'Aggregated activity stats per user' },
|
||||
usersDetailedActivities: {
|
||||
type: 'json',
|
||||
description: 'Day-by-day activity per user: call IDs grouped by activity type per day',
|
||||
},
|
||||
usersAggregateActivity: {
|
||||
type: 'json',
|
||||
description: 'Aggregated activity per user grouped into time periods (with fromDate/toDate)',
|
||||
},
|
||||
peopleInteractionStats: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Interaction stats per user: [{userId, userEmailAddress, personInteractionStats: [{name, value}]}]',
|
||||
},
|
||||
timeZone: { type: 'string', description: "The company's defined timezone in Gong" },
|
||||
fromDateTime: { type: 'string', description: 'Start of results (ISO-8601)' },
|
||||
toDateTime: { type: 'string', description: 'End of results (ISO-8601)' },
|
||||
|
||||
// answered_scorecards
|
||||
answeredScorecards: {
|
||||
type: 'json',
|
||||
description: 'Answered scorecards with scores and answers',
|
||||
},
|
||||
|
||||
// list_library_folders / get_folder_content
|
||||
folders: {
|
||||
type: 'json',
|
||||
description: 'Library folders: [{id, name, parentFolderId, createdBy, updated}]',
|
||||
},
|
||||
folderId: { type: 'string', description: 'Library folder ID' },
|
||||
folderName: { type: 'string', description: 'Library folder display name' },
|
||||
createdBy: { type: 'string', description: 'User ID who created the folder' },
|
||||
updated: { type: 'string', description: "Folder's last update time (ISO-8601)" },
|
||||
|
||||
// list_scorecards
|
||||
scorecards: { type: 'json', description: 'Scorecard definitions with questions' },
|
||||
|
||||
// list_trackers
|
||||
trackers: { type: 'json', description: 'Keyword/smart tracker definitions' },
|
||||
|
||||
// list_workspaces
|
||||
workspaces: { type: 'json', description: 'Gong workspaces: [{id, name, description}]' },
|
||||
|
||||
// list_flows
|
||||
flows: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Gong Engage flows: [{id, name, folderId, folderName, visibility, creationDate, exclusive}]',
|
||||
},
|
||||
|
||||
// get_coaching
|
||||
coachingData: {
|
||||
type: 'json',
|
||||
description: "Coaching data per manager's team with direct-report metrics",
|
||||
},
|
||||
|
||||
// lookup_email / lookup_phone
|
||||
emails: {
|
||||
type: 'json',
|
||||
description: 'Related email messages: [{id, from, sentTime, mailbox, messageHash}]',
|
||||
},
|
||||
meetings: { type: 'json', description: 'Related meetings: [{id}]' },
|
||||
customerData: {
|
||||
type: 'json',
|
||||
description: 'Linked external-system (CRM) objects referencing the contact',
|
||||
},
|
||||
customerEngagement: {
|
||||
type: 'json',
|
||||
description: 'Customer engagement events (e.g., viewing shared calls)',
|
||||
},
|
||||
suppliedPhoneNumber: {
|
||||
type: 'string',
|
||||
description: 'The phone number supplied in the lookup request',
|
||||
},
|
||||
matchingPhoneNumbers: {
|
||||
type: 'json',
|
||||
description: 'Phone numbers in the system matching the supplied number',
|
||||
},
|
||||
emailAddresses: {
|
||||
type: 'json',
|
||||
description: 'Email addresses associated with the phone number',
|
||||
},
|
||||
},
|
||||
triggers: {
|
||||
|
||||
@@ -79,10 +79,49 @@ export const GooglePagespeedBlock: BlockConfig<GooglePagespeedAnalyzeResponse> =
|
||||
},
|
||||
|
||||
outputs: {
|
||||
response: {
|
||||
type: 'json',
|
||||
description:
|
||||
'PageSpeed analysis results including category scores (performanceScore, accessibilityScore, bestPracticesScore, seoScore), Core Web Vitals display values and numeric values (firstContentfulPaint, largestContentfulPaint, totalBlockingTime, cumulativeLayoutShift, speedIndex, interactive), and metadata (finalUrl, overallCategory, analysisTimestamp, lighthouseVersion)',
|
||||
finalUrl: { type: 'string', description: 'The final URL after redirects' },
|
||||
performanceScore: { type: 'number', description: 'Performance category score (0-1)' },
|
||||
accessibilityScore: { type: 'number', description: 'Accessibility category score (0-1)' },
|
||||
bestPracticesScore: { type: 'number', description: 'Best Practices category score (0-1)' },
|
||||
seoScore: { type: 'number', description: 'SEO category score (0-1)' },
|
||||
firstContentfulPaint: {
|
||||
type: 'string',
|
||||
description: 'Time to First Contentful Paint (display value)',
|
||||
},
|
||||
firstContentfulPaintMs: {
|
||||
type: 'number',
|
||||
description: 'Time to First Contentful Paint in milliseconds',
|
||||
},
|
||||
largestContentfulPaint: {
|
||||
type: 'string',
|
||||
description: 'Time to Largest Contentful Paint (display value)',
|
||||
},
|
||||
largestContentfulPaintMs: {
|
||||
type: 'number',
|
||||
description: 'Time to Largest Contentful Paint in milliseconds',
|
||||
},
|
||||
totalBlockingTime: { type: 'string', description: 'Total Blocking Time (display value)' },
|
||||
totalBlockingTimeMs: { type: 'number', description: 'Total Blocking Time in milliseconds' },
|
||||
cumulativeLayoutShift: {
|
||||
type: 'string',
|
||||
description: 'Cumulative Layout Shift (display value)',
|
||||
},
|
||||
cumulativeLayoutShiftValue: {
|
||||
type: 'number',
|
||||
description: 'Cumulative Layout Shift numeric value',
|
||||
},
|
||||
speedIndex: { type: 'string', description: 'Speed Index (display value)' },
|
||||
speedIndexMs: { type: 'number', description: 'Speed Index in milliseconds' },
|
||||
interactive: { type: 'string', description: 'Time to Interactive (display value)' },
|
||||
interactiveMs: { type: 'number', description: 'Time to Interactive in milliseconds' },
|
||||
overallCategory: {
|
||||
type: 'string',
|
||||
description: 'Overall loading experience category (FAST, AVERAGE, SLOW, or NONE)',
|
||||
},
|
||||
analysisTimestamp: { type: 'string', description: 'UTC timestamp of the analysis' },
|
||||
lighthouseVersion: {
|
||||
type: 'string',
|
||||
description: 'Version of Lighthouse used for the analysis',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export const IntercomBlock: BlockConfig = {
|
||||
authMode: AuthMode.ApiKey,
|
||||
category: 'tools',
|
||||
integrationType: IntegrationType.Support,
|
||||
bgColor: '#1F8DED',
|
||||
bgColor: '#FFFFFF',
|
||||
icon: IntercomIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ export const MailchimpBlock: BlockConfig = {
|
||||
authMode: AuthMode.ApiKey,
|
||||
category: 'tools',
|
||||
integrationType: IntegrationType.Email,
|
||||
bgColor: '#1A1A1A',
|
||||
bgColor: '#FFE01B',
|
||||
icon: MailchimpIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ export const MillionVerifierBlock: BlockConfig<MillionVerifierResponse> = {
|
||||
docsLink: 'https://docs.sim.ai/tools/millionverifier',
|
||||
category: 'tools',
|
||||
integrationType: IntegrationType.Sales,
|
||||
bgColor: '#00B67A',
|
||||
bgColor: '#FFFFFF',
|
||||
icon: MillionVerifierIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
|
||||
@@ -294,8 +294,11 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
if (params.groupName) result.name = params.groupName
|
||||
if (params.groupDescription !== undefined) result.description = params.groupDescription
|
||||
|
||||
// Pass through all other non-empty params
|
||||
// Allow empty strings so users can clear fields (e.g. update_user partial updates)
|
||||
// Pass through all other params, skipping empty values. Blank fields in a
|
||||
// partial update (e.g. update_user, a POST merge) must be omitted so they
|
||||
// leave the existing Okta value unchanged rather than overwriting it with
|
||||
// an empty string. This mirrors the agent tool-call path, which already
|
||||
// filters empty params before execution.
|
||||
const skipKeys = new Set([
|
||||
'operation',
|
||||
'apiKey',
|
||||
@@ -305,7 +308,7 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
'groupDescription',
|
||||
])
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (!skipKeys.has(key) && value !== undefined && value !== null) {
|
||||
if (!skipKeys.has(key) && value !== undefined && value !== null && value !== '') {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ServiceNowIcon } from '@/components/icons'
|
||||
import type { BlockConfig, BlockMeta } from '@/blocks/types'
|
||||
import { IntegrationType } from '@/blocks/types'
|
||||
import { AuthMode, IntegrationType } from '@/blocks/types'
|
||||
import { normalizeFileInput } from '@/blocks/utils'
|
||||
import type { ServiceNowResponse } from '@/tools/servicenow/types'
|
||||
import { getTrigger } from '@/triggers'
|
||||
|
||||
@@ -13,6 +14,7 @@ export const ServiceNowBlock: BlockConfig<ServiceNowResponse> = {
|
||||
docsLink: 'https://docs.sim.ai/tools/servicenow',
|
||||
category: 'tools',
|
||||
integrationType: IntegrationType.Support,
|
||||
authMode: AuthMode.ApiKey,
|
||||
bgColor: '#032D42',
|
||||
icon: ServiceNowIcon,
|
||||
subBlocks: [
|
||||
@@ -26,6 +28,10 @@ export const ServiceNowBlock: BlockConfig<ServiceNowResponse> = {
|
||||
{ label: 'Read Records', id: 'servicenow_read_record' },
|
||||
{ label: 'Update Record', id: 'servicenow_update_record' },
|
||||
{ label: 'Delete Record', id: 'servicenow_delete_record' },
|
||||
{ label: 'Aggregate Records', id: 'servicenow_aggregate' },
|
||||
{ label: 'List Attachments', id: 'servicenow_list_attachments' },
|
||||
{ label: 'Download Attachment', id: 'servicenow_download_attachment' },
|
||||
{ label: 'Upload Attachment', id: 'servicenow_upload_attachment' },
|
||||
],
|
||||
value: () => 'servicenow_read_record',
|
||||
},
|
||||
@@ -57,12 +63,13 @@ export const ServiceNowBlock: BlockConfig<ServiceNowResponse> = {
|
||||
required: true,
|
||||
description: 'Password for the ServiceNow user',
|
||||
},
|
||||
// Table Name
|
||||
// Table Name (not needed for download attachment, which is addressed by attachment sys_id)
|
||||
{
|
||||
id: 'tableName',
|
||||
title: 'Table Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'incident, task, sys_user, etc.',
|
||||
condition: { field: 'operation', value: 'servicenow_download_attachment', not: true },
|
||||
required: true,
|
||||
description: 'ServiceNow table name',
|
||||
},
|
||||
@@ -120,7 +127,10 @@ Output: {"short_description": "Network outage", "description": "Network connecti
|
||||
title: 'Query String',
|
||||
type: 'short-input',
|
||||
placeholder: 'active=true^priority=1',
|
||||
condition: { field: 'operation', value: 'servicenow_read_record' },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['servicenow_read_record', 'servicenow_aggregate'],
|
||||
},
|
||||
description: 'ServiceNow encoded query string',
|
||||
mode: 'advanced',
|
||||
},
|
||||
@@ -152,7 +162,10 @@ Output: {"short_description": "Network outage", "description": "Network connecti
|
||||
{ label: 'All (both)', id: 'all' },
|
||||
],
|
||||
value: () => '',
|
||||
condition: { field: 'operation', value: 'servicenow_read_record' },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['servicenow_read_record', 'servicenow_aggregate'],
|
||||
},
|
||||
description: 'Return display values for reference fields instead of sys_ids',
|
||||
mode: 'advanced',
|
||||
},
|
||||
@@ -215,6 +228,136 @@ Output: {"state": "2", "assigned_to": "john.doe", "work_notes": "Assigned and st
|
||||
condition: { field: 'operation', value: 'servicenow_delete_record' },
|
||||
required: true,
|
||||
},
|
||||
// Aggregate-specific
|
||||
{
|
||||
id: 'count',
|
||||
title: 'Return Count',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: { field: 'operation', value: 'servicenow_aggregate' },
|
||||
description: 'Return the count of matching records',
|
||||
},
|
||||
{
|
||||
id: 'groupBy',
|
||||
title: 'Group By',
|
||||
type: 'short-input',
|
||||
placeholder: 'category,priority',
|
||||
condition: { field: 'operation', value: 'servicenow_aggregate' },
|
||||
description: 'Comma-separated fields to group results by',
|
||||
},
|
||||
{
|
||||
id: 'avgFields',
|
||||
title: 'Average Fields',
|
||||
type: 'short-input',
|
||||
placeholder: 'reassignment_count',
|
||||
condition: { field: 'operation', value: 'servicenow_aggregate' },
|
||||
description: 'Comma-separated numeric fields to average',
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'sumFields',
|
||||
title: 'Sum Fields',
|
||||
type: 'short-input',
|
||||
placeholder: 'business_duration',
|
||||
condition: { field: 'operation', value: 'servicenow_aggregate' },
|
||||
description: 'Comma-separated numeric fields to sum',
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'minFields',
|
||||
title: 'Min Fields',
|
||||
type: 'short-input',
|
||||
placeholder: 'opened_at',
|
||||
condition: { field: 'operation', value: 'servicenow_aggregate' },
|
||||
description: 'Comma-separated fields to compute the minimum of',
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'maxFields',
|
||||
title: 'Max Fields',
|
||||
type: 'short-input',
|
||||
placeholder: 'closed_at',
|
||||
condition: { field: 'operation', value: 'servicenow_aggregate' },
|
||||
description: 'Comma-separated fields to compute the maximum of',
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'having',
|
||||
title: 'Having',
|
||||
type: 'short-input',
|
||||
placeholder: 'count>5',
|
||||
condition: { field: 'operation', value: 'servicenow_aggregate' },
|
||||
description: 'Filter on aggregate results',
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Attachment record sys_id (list + upload)
|
||||
{
|
||||
id: 'recordSysId',
|
||||
title: 'Record sys_id',
|
||||
type: 'short-input',
|
||||
placeholder: 'sys_id of the record',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['servicenow_list_attachments', 'servicenow_upload_attachment'],
|
||||
},
|
||||
required: true,
|
||||
description: 'sys_id of the record the attachment belongs to',
|
||||
},
|
||||
// List attachments: limit (unique id to avoid sharing the Read Records `limit` value)
|
||||
{
|
||||
id: 'attachmentLimit',
|
||||
title: 'Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '10',
|
||||
condition: { field: 'operation', value: 'servicenow_list_attachments' },
|
||||
description: 'Maximum number of attachments to return',
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Download attachment: attachment sys_id
|
||||
{
|
||||
id: 'attachmentSysId',
|
||||
title: 'Attachment sys_id',
|
||||
type: 'short-input',
|
||||
placeholder: 'sys_id of the attachment',
|
||||
condition: { field: 'operation', value: 'servicenow_download_attachment' },
|
||||
required: true,
|
||||
description: 'sys_id of the attachment to download (from List Attachments)',
|
||||
},
|
||||
// Upload attachment: file name + file
|
||||
{
|
||||
id: 'fileName',
|
||||
title: 'File Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'logs.txt',
|
||||
condition: { field: 'operation', value: 'servicenow_upload_attachment' },
|
||||
required: true,
|
||||
description: 'Name to give the uploaded file',
|
||||
},
|
||||
{
|
||||
id: 'uploadFile',
|
||||
title: 'File',
|
||||
type: 'file-upload',
|
||||
canonicalParamId: 'file',
|
||||
placeholder: 'Upload a file',
|
||||
condition: { field: 'operation', value: 'servicenow_upload_attachment' },
|
||||
mode: 'basic',
|
||||
multiple: false,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'fileReference',
|
||||
title: 'File',
|
||||
type: 'short-input',
|
||||
canonicalParamId: 'file',
|
||||
placeholder: 'Reference a file from previous blocks (e.g., {{block_1.output.file}})',
|
||||
condition: { field: 'operation', value: 'servicenow_upload_attachment' },
|
||||
mode: 'advanced',
|
||||
required: true,
|
||||
},
|
||||
...getTrigger('servicenow_incident_created').subBlocks,
|
||||
...getTrigger('servicenow_incident_updated').subBlocks,
|
||||
...getTrigger('servicenow_change_request_created').subBlocks,
|
||||
@@ -227,20 +370,37 @@ Output: {"state": "2", "assigned_to": "john.doe", "work_notes": "Assigned and st
|
||||
'servicenow_read_record',
|
||||
'servicenow_update_record',
|
||||
'servicenow_delete_record',
|
||||
'servicenow_aggregate',
|
||||
'servicenow_list_attachments',
|
||||
'servicenow_download_attachment',
|
||||
'servicenow_upload_attachment',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => params.operation,
|
||||
params: (params) => {
|
||||
const { operation, fields, ...rest } = params
|
||||
const { operation, fields, file, attachmentLimit, ...rest } = params
|
||||
const isCreateOrUpdate =
|
||||
operation === 'servicenow_create_record' || operation === 'servicenow_update_record'
|
||||
|
||||
if (attachmentLimit != null && attachmentLimit !== '') rest.limit = Number(attachmentLimit)
|
||||
if (rest.limit != null && rest.limit !== '') rest.limit = Number(rest.limit)
|
||||
if (rest.offset != null && rest.offset !== '') rest.offset = Number(rest.offset)
|
||||
|
||||
if (fields && isCreateOrUpdate) {
|
||||
const parsedFields = typeof fields === 'string' ? JSON.parse(fields) : fields
|
||||
return { ...rest, fields: parsedFields }
|
||||
if (operation === 'servicenow_aggregate') {
|
||||
rest.count = rest.count === true || rest.count === 'true'
|
||||
}
|
||||
|
||||
if (operation === 'servicenow_upload_attachment') {
|
||||
const normalizedFile = normalizeFileInput(file, { single: true })
|
||||
return normalizedFile ? { ...rest, file: normalizedFile } : rest
|
||||
}
|
||||
|
||||
if (fields) {
|
||||
if (isCreateOrUpdate) {
|
||||
const parsedFields = typeof fields === 'string' ? JSON.parse(fields) : fields
|
||||
return { ...rest, fields: parsedFields }
|
||||
}
|
||||
return { ...rest, fields }
|
||||
}
|
||||
|
||||
return rest
|
||||
@@ -257,15 +417,39 @@ Output: {"state": "2", "assigned_to": "john.doe", "work_notes": "Assigned and st
|
||||
number: { type: 'string', description: 'Record number' },
|
||||
query: { type: 'string', description: 'Query string' },
|
||||
limit: { type: 'number', description: 'Result limit' },
|
||||
attachmentLimit: {
|
||||
type: 'number',
|
||||
description: 'Max attachments to return (list attachments)',
|
||||
},
|
||||
offset: { type: 'number', description: 'Pagination offset' },
|
||||
fields: { type: 'json', description: 'Fields object or JSON string' },
|
||||
displayValue: { type: 'string', description: 'Display value mode for reference fields' },
|
||||
count: { type: 'boolean', description: 'Return record count (aggregate)' },
|
||||
groupBy: { type: 'string', description: 'Comma-separated fields to group by (aggregate)' },
|
||||
avgFields: { type: 'string', description: 'Comma-separated fields to average (aggregate)' },
|
||||
sumFields: { type: 'string', description: 'Comma-separated fields to sum (aggregate)' },
|
||||
minFields: { type: 'string', description: 'Comma-separated fields to minimize (aggregate)' },
|
||||
maxFields: { type: 'string', description: 'Comma-separated fields to maximize (aggregate)' },
|
||||
having: { type: 'string', description: 'Aggregate result filter (aggregate)' },
|
||||
recordSysId: { type: 'string', description: 'Record sys_id for attachment operations' },
|
||||
attachmentSysId: { type: 'string', description: 'Attachment sys_id to download' },
|
||||
fileName: { type: 'string', description: 'Name of the file to upload' },
|
||||
file: { type: 'json', description: 'File to upload (canonical param)' },
|
||||
},
|
||||
outputs: {
|
||||
record: { type: 'json', description: 'Single ServiceNow record' },
|
||||
records: { type: 'json', description: 'Array of ServiceNow records' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
metadata: { type: 'json', description: 'Operation metadata' },
|
||||
result: { type: 'json', description: 'Aggregate result (stats or grouped array)' },
|
||||
count: { type: 'number', description: 'Aggregate matching record count' },
|
||||
attachments: {
|
||||
type: 'json',
|
||||
description: 'Attachment metadata list (sys_id, file_name, content_type, download_link)',
|
||||
},
|
||||
file: { type: 'file', description: 'Downloaded attachment file' },
|
||||
content: { type: 'string', description: 'Base64-encoded downloaded file content' },
|
||||
attachment: { type: 'json', description: 'Uploaded attachment metadata' },
|
||||
},
|
||||
triggers: {
|
||||
enabled: true,
|
||||
|
||||
@@ -433,12 +433,11 @@ Output: {"Marital_Status_Reference":{"ID":{"attributes":{"wd:type":"Marital_Stat
|
||||
worker: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Worker profile (id, descriptor, primaryWorkEmail, primaryWorkPhone, businessTitle, supervisoryOrganization, hireDate, workerType, isActive)',
|
||||
'Worker profile (id, descriptor, personalData, employmentData, compensationData, organizationData)',
|
||||
},
|
||||
workers: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of worker profiles (id, descriptor, primaryWorkEmail, businessTitle, supervisoryOrganization, hireDate, workerType, isActive)',
|
||||
description: 'Array of worker profiles (id, descriptor, personalData, employmentData)',
|
||||
},
|
||||
total: { type: 'number', description: 'Total count of results' },
|
||||
preHireId: { type: 'string', description: 'Created pre-hire ID' },
|
||||
|
||||
@@ -12,7 +12,7 @@ export const ZeroBounceBlock: BlockConfig<ZeroBounceResponse> = {
|
||||
docsLink: 'https://docs.sim.ai/tools/zerobounce',
|
||||
category: 'tools',
|
||||
integrationType: IntegrationType.Sales,
|
||||
bgColor: '#330D49',
|
||||
bgColor: '#FFFFFF',
|
||||
icon: ZeroBounceIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
|
||||
@@ -4987,7 +4987,7 @@ export function IntercomIcon(props: SVGProps<SVGSVGElement>) {
|
||||
<g>
|
||||
<path
|
||||
d='M221.66,144.56 C221.66,146.9 220.76,149.15 219.17,150.8 C217.57,152.46 215.4,153.39 213.15,153.39 C210.89,153.39 208.72,152.46 207.12,150.8 C205.53,149.15 204.63,146.9 204.63,144.56 L204.63,65.68 C204.63,63.34 205.53,61.09 207.12,59.43 C208.72,57.78 210.89,56.85 213.15,56.85 C215.4,56.85 217.57,57.78 219.17,59.43 C220.76,61.09 221.66,63.34 221.66,65.68 L221.66,144.56 Z M218.7,199.22 C217.58,200.39 185.91,227.75 127.86,227.75 C69.81,227.75 38.35,200.53 37.01,199.36 C36.17,198.63 35.48,197.73 34.97,196.71 C34.47,195.69 34.16,194.59 34.07,193.45 C33.98,192.31 34.1,191.16 34.44,190.07 C34.77,188.98 35.32,187.98 36.03,187.1 C37.5,185.34 39.58,184.25 41.82,184.07 C44.06,183.9 46.28,184.64 47.99,186.16 C48.48,186.52 76.77,210.31 127.79,210.31 C178.81,210.31 207.3,186.37 207.59,186.16 C209.32,184.66 211.54,183.92 213.79,184.1 C216.04,184.27 218.13,185.35 219.62,187.1 C221.06,188.84 221.77,191.1 221.61,193.38 C221.46,195.66 220.44,197.78 218.78,199.29 L218.7,199.22 Z M33.99,65.68 C34.12,63.33 35.13,61.13 36.82,59.55 C38.51,57.98 40.73,57.17 43,57.29 C45.1,57.41 47.08,58.33 48.56,59.88 C50.04,61.43 50.92,63.5 51.02,65.68 L51.02,144.42 C51.02,146.76 50.12,149 48.52,150.66 C46.93,152.31 44.76,153.24 42.5,153.24 C40.24,153.24 38.08,152.31 36.48,150.66 C34.88,149 33.99,146.76 33.99,144.42 L33.99,65.68 Z M76.7,48.16 C76.83,45.81 77.85,43.61 79.54,42.04 C81.22,40.47 83.44,39.65 85.71,39.77 C87.81,39.9 89.79,40.82 91.27,42.37 C92.75,43.92 93.63,45.98 93.73,48.16 L93.73,164.92 C93.73,167.26 92.83,169.51 91.24,171.16 C89.64,172.82 87.47,173.75 85.22,173.75 C82.96,173.75 80.79,172.82 79.2,171.16 C77.6,169.51 76.7,167.26 76.7,164.92 L76.7,48.16 Z M119.63,43.79 C119.63,41.44 120.52,39.2 122.12,37.54 C123.72,35.89 125.88,34.96 128.14,34.96 C130.4,34.96 132.56,35.89 134.16,37.54 C135.76,39.2 136.66,41.44 136.66,43.79 L136.66,170.76 C136.66,173.1 135.76,175.35 134.16,177 C132.56,178.66 130.4,179.59 128.14,179.59 C125.88,179.59 123.72,178.66 122.12,177 C120.52,175.35 119.63,173.1 119.63,170.76 L119.63,43.79 Z M161.85,48.16 C161.85,45.82 162.74,43.58 164.34,41.92 C165.94,40.26 168.1,39.33 170.36,39.33 C172.62,39.33 174.79,40.26 176.38,41.92 C177.98,43.58 178.88,45.82 178.88,48.16 L178.88,164.92 C178.88,167.26 177.98,169.51 176.38,171.16 C174.79,172.82 172.62,173.75 170.36,173.75 C168.1,173.75 165.94,172.82 164.34,171.16 C162.74,169.51 161.85,167.26 161.85,164.92 L161.85,48.16 Z M223.98,0 L32.02,0 C27.83,-0.03 23.69,0.8 19.81,2.44 C15.94,4.07 12.41,6.48 9.44,9.53 C6.47,12.58 4.1,16.21 2.48,20.21 C0.86,24.21 0.02,28.5 0,32.84 L0,229.87 C0.02,234.2 0.86,238.5 2.48,242.49 C4.1,246.49 6.47,250.12 9.44,253.17 C12.41,256.22 15.94,258.64 19.81,260.27 C23.69,261.91 27.83,262.73 32.02,262.71 L223.98,262.71 C228.16,262.73 232.3,261.91 236.17,260.28 C240.04,258.65 243.56,256.24 246.53,253.2 C249.51,250.16 251.87,246.54 253.5,242.55 C255.12,238.55 255.97,234.27 256,229.94 L256,32.84 C255.98,28.51 255.14,24.22 253.52,20.23 C251.91,16.23 249.55,12.61 246.58,9.56 C243.62,6.51 240.1,4.1 236.24,2.46 C232.37,0.82 228.23,-0.02 224.05,0'
|
||||
fill='currentColor'
|
||||
fill='#000000'
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -5028,7 +5028,7 @@ export function MailchimpIcon(props: SVGProps<SVGSVGElement>) {
|
||||
y='0px'
|
||||
viewBox='0 0 230.81 244.96'
|
||||
xmlSpace='preserve'
|
||||
fill='currentColor'
|
||||
fill='#000000'
|
||||
>
|
||||
<g>
|
||||
<g>
|
||||
|
||||
@@ -21,6 +21,7 @@ export * from './pipedrive'
|
||||
export * from './quiver'
|
||||
export * from './sap'
|
||||
export * from './search'
|
||||
export * from './servicenow'
|
||||
export * from './shared'
|
||||
export * from './stagehand'
|
||||
export * from './thinking'
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { z } from 'zod'
|
||||
import { defineRouteContract } from '@/lib/api/contracts/types'
|
||||
import { RawFileInputSchema } from '@/lib/uploads/utils/file-schemas'
|
||||
|
||||
export const servicenowUploadAttachmentBodySchema = z.object({
|
||||
instanceUrl: z.string().min(1, 'Instance URL is required'),
|
||||
username: z.string().min(1, 'Username is required'),
|
||||
password: z.string().min(1, 'Password is required'),
|
||||
tableName: z.string().min(1, 'Table name is required'),
|
||||
recordSysId: z.string().min(1, 'Record sys_id is required'),
|
||||
fileName: z.string().min(1, 'File name is required'),
|
||||
file: RawFileInputSchema.optional().nullable(),
|
||||
})
|
||||
|
||||
export type ServiceNowUploadAttachmentBody = z.input<typeof servicenowUploadAttachmentBodySchema>
|
||||
|
||||
// untyped-response: ServiceNow returns arbitrary attachment metadata wrapped in a success envelope
|
||||
const servicenowToolResponseSchema = z.unknown()
|
||||
|
||||
export const servicenowUploadAttachmentContract = defineRouteContract({
|
||||
method: 'POST',
|
||||
path: '/api/tools/servicenow/upload-attachment',
|
||||
body: servicenowUploadAttachmentBodySchema,
|
||||
response: { mode: 'json', schema: servicenowToolResponseSchema },
|
||||
})
|
||||
@@ -2173,7 +2173,7 @@
|
||||
"name": "Bright Data",
|
||||
"description": "Scrape websites, search engines, and extract structured data",
|
||||
"longDescription": "Integrate Bright Data into the workflow. Scrape any URL with Web Unlocker, search Google and other engines with SERP API, discover web content ranked by intent, or trigger pre-built scrapers for structured data extraction.",
|
||||
"bgColor": "#0F4C81",
|
||||
"bgColor": "#FFFFFF",
|
||||
"iconName": "BrightDataIcon",
|
||||
"docsUrl": "https://docs.sim.ai/tools/brightdata",
|
||||
"operations": [
|
||||
@@ -3370,10 +3370,22 @@
|
||||
"name": "Execute SQL",
|
||||
"description": "Execute a SQL statement against a Databricks SQL warehouse and return results inline. Supports parameterized queries and Unity Catalog."
|
||||
},
|
||||
{
|
||||
"name": "Get Statement",
|
||||
"description": "Poll a SQL statement by its ID to retrieve status and results. Use this after Execute SQL when a query runs longer than the wait timeout."
|
||||
},
|
||||
{
|
||||
"name": "List Warehouses",
|
||||
"description": "List all SQL warehouses in a Databricks workspace including their size, state, and type. Use this to discover the warehouse ID needed for Execute SQL."
|
||||
},
|
||||
{
|
||||
"name": "List Jobs",
|
||||
"description": "List all jobs in a Databricks workspace with optional filtering by name."
|
||||
},
|
||||
{
|
||||
"name": "Get Job",
|
||||
"description": "Get the full definition and settings of a single Databricks job by its job ID."
|
||||
},
|
||||
{
|
||||
"name": "Run Job",
|
||||
"description": "Trigger an existing Databricks job to run immediately with optional job-level or notebook parameters."
|
||||
@@ -3397,9 +3409,13 @@
|
||||
{
|
||||
"name": "List Clusters",
|
||||
"description": "List all clusters in a Databricks workspace including their state, configuration, and resource details."
|
||||
},
|
||||
{
|
||||
"name": "Get Cluster",
|
||||
"description": "Get the state, configuration, and resource details of a single Databricks cluster by its cluster ID."
|
||||
}
|
||||
],
|
||||
"operationCount": 8,
|
||||
"operationCount": 12,
|
||||
"triggers": [],
|
||||
"triggerCount": 0,
|
||||
"authType": "api-key",
|
||||
@@ -3871,12 +3887,36 @@
|
||||
"name": "List Links",
|
||||
"description": "Retrieve a paginated list of short links for the authenticated workspace. Supports filtering by domain, search query, tags, and sorting."
|
||||
},
|
||||
{
|
||||
"name": "Count Links",
|
||||
"description": "Retrieve the number of short links for the authenticated workspace, optionally filtered and grouped by domain, tag, user, or folder."
|
||||
},
|
||||
{
|
||||
"name": "Bulk Create Links",
|
||||
"description": "Create up to 100 short links in a single request. Returns the created links alongside any per-link errors."
|
||||
},
|
||||
{
|
||||
"name": "Bulk Update Links",
|
||||
"description": "Apply the same set of field updates to up to 100 links at once, selected by link IDs or external IDs."
|
||||
},
|
||||
{
|
||||
"name": "Bulk Delete Links",
|
||||
"description": "Delete up to 100 short links in a single request by their link IDs. Non-existing IDs are ignored."
|
||||
},
|
||||
{
|
||||
"name": "Get Analytics",
|
||||
"description": "Retrieve analytics for links including clicks, leads, and sales. Supports filtering by link, time range, and grouping by various dimensions."
|
||||
},
|
||||
{
|
||||
"name": "List Events",
|
||||
"description": "Retrieve a paginated stream of individual click, lead, and sale events for links, with filtering by link, time range, and location."
|
||||
},
|
||||
{
|
||||
"name": "Get QR Code",
|
||||
"description": "Generate a customizable QR code (PNG) for a short link, with control over size, error correction, colors, and margin."
|
||||
}
|
||||
],
|
||||
"operationCount": 7,
|
||||
"operationCount": 13,
|
||||
"triggers": [],
|
||||
"triggerCount": 0,
|
||||
"authType": "api-key",
|
||||
@@ -4170,6 +4210,10 @@
|
||||
"name": "Sales Pointer (People)",
|
||||
"description": "Advanced people search with complex filters for location, company size, seniority, experience, and more."
|
||||
},
|
||||
{
|
||||
"name": "Search Jobs",
|
||||
"description": "Search LinkedIn job postings by keywords with filters for location, job type, workplace type, experience level, and company."
|
||||
},
|
||||
{
|
||||
"name": "Search Posts",
|
||||
"description": "Search LinkedIn posts by keywords with date filtering."
|
||||
@@ -4182,10 +4226,18 @@
|
||||
"name": "Search Post Reactions",
|
||||
"description": "Get reactions on a LinkedIn post with filtering by reaction type."
|
||||
},
|
||||
{
|
||||
"name": "Search Post Reactions (by URL)",
|
||||
"description": "Get reactions on a LinkedIn post by its URL, filtered by reaction type."
|
||||
},
|
||||
{
|
||||
"name": "Search Post Comments",
|
||||
"description": "Get comments on a LinkedIn post."
|
||||
},
|
||||
{
|
||||
"name": "Search Post Comments (by URL)",
|
||||
"description": "Get comments on a LinkedIn post by its URL."
|
||||
},
|
||||
{
|
||||
"name": "Search People Activities",
|
||||
"description": "Get a person"
|
||||
@@ -4207,7 +4259,7 @@
|
||||
"description": "Check your Enrich API credit usage and remaining balance."
|
||||
}
|
||||
],
|
||||
"operationCount": 29,
|
||||
"operationCount": 32,
|
||||
"triggers": [],
|
||||
"triggerCount": 0,
|
||||
"authType": "api-key",
|
||||
@@ -5206,7 +5258,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Get Extensive Calls",
|
||||
"description": "Retrieve detailed call data including trackers, topics, and highlights from Gong."
|
||||
"description": "Retrieve detailed call data including trackers, topics, highlights, and AI spotlight content (brief, outline, key points, call outcome) from Gong."
|
||||
},
|
||||
{
|
||||
"name": "List Users",
|
||||
@@ -5220,6 +5272,14 @@
|
||||
"name": "Aggregate Activity",
|
||||
"description": "Retrieve aggregated activity statistics for users by date range from Gong."
|
||||
},
|
||||
{
|
||||
"name": "Day-by-Day Activity",
|
||||
"description": "Retrieve detailed day-by-day activity (call IDs per activity type) for users by date range from Gong."
|
||||
},
|
||||
{
|
||||
"name": "Aggregate by Period",
|
||||
"description": "Retrieve aggregated user activity grouped into time periods (day, week, month, quarter, year) by date range from Gong."
|
||||
},
|
||||
{
|
||||
"name": "Interaction Stats",
|
||||
"description": "Retrieve interaction statistics for users by date range from Gong. Only includes calls with Whisper enabled."
|
||||
@@ -5265,7 +5325,7 @@
|
||||
"description": "Find all references to a phone number in Gong (calls, email messages, meetings, CRM data, and associated contacts)."
|
||||
}
|
||||
],
|
||||
"operationCount": 19,
|
||||
"operationCount": 21,
|
||||
"triggers": [
|
||||
{
|
||||
"id": "gong_webhook",
|
||||
@@ -7402,7 +7462,7 @@
|
||||
"name": "Intercom",
|
||||
"description": "Manage contacts, companies, conversations, tickets, and messages in Intercom",
|
||||
"longDescription": "Integrate Intercom into the workflow. Can create, get, update, list, search, and delete contacts; create, get, and list companies; get, list, reply, and search conversations; create and get tickets; and create messages.",
|
||||
"bgColor": "#1F8DED",
|
||||
"bgColor": "#FFFFFF",
|
||||
"iconName": "IntercomIcon",
|
||||
"docsUrl": "https://docs.sim.ai/tools/intercom",
|
||||
"operations": [
|
||||
@@ -9025,7 +9085,7 @@
|
||||
"name": "Mailchimp",
|
||||
"description": "Manage audiences, campaigns, and marketing automation in Mailchimp",
|
||||
"longDescription": "Integrate Mailchimp into the workflow. Can manage audiences (lists), list members, campaigns, automation workflows, templates, reports, segments, tags, merge fields, interest categories, landing pages, signup forms, and batch operations.",
|
||||
"bgColor": "#1A1A1A",
|
||||
"bgColor": "#FFE01B",
|
||||
"iconName": "MailchimpIcon",
|
||||
"docsUrl": "https://docs.sim.ai/tools/mailchimp",
|
||||
"operations": [
|
||||
@@ -9684,7 +9744,7 @@
|
||||
"name": "MillionVerifier",
|
||||
"description": "Verify email deliverability and check account credits",
|
||||
"longDescription": "Integrate MillionVerifier to verify email deliverability in real time — classify addresses as valid, invalid, catch-all, disposable, or unknown — and check your remaining verification credits.",
|
||||
"bgColor": "#00B67A",
|
||||
"bgColor": "#FFFFFF",
|
||||
"iconName": "MillionVerifierIcon",
|
||||
"docsUrl": "https://docs.sim.ai/tools/millionverifier",
|
||||
"operations": [
|
||||
@@ -13273,9 +13333,25 @@
|
||||
{
|
||||
"name": "Delete Record",
|
||||
"description": "Delete a record from a ServiceNow table"
|
||||
},
|
||||
{
|
||||
"name": "Aggregate Records",
|
||||
"description": "Compute aggregate statistics (count, sum, average, min, max, group by) over a ServiceNow table"
|
||||
},
|
||||
{
|
||||
"name": "List Attachments",
|
||||
"description": "List the attachments on a ServiceNow record"
|
||||
},
|
||||
{
|
||||
"name": "Download Attachment",
|
||||
"description": "Download an attachment file from ServiceNow by its sys_id"
|
||||
},
|
||||
{
|
||||
"name": "Upload Attachment",
|
||||
"description": "Attach a file to a ServiceNow record"
|
||||
}
|
||||
],
|
||||
"operationCount": 4,
|
||||
"operationCount": 8,
|
||||
"triggers": [
|
||||
{
|
||||
"id": "servicenow_incident_created",
|
||||
@@ -13304,7 +13380,7 @@
|
||||
}
|
||||
],
|
||||
"triggerCount": 5,
|
||||
"authType": "none",
|
||||
"authType": "api-key",
|
||||
"category": "tools",
|
||||
"integrationType": "support",
|
||||
"tags": ["customer-support", "ticketing", "incident-management"]
|
||||
@@ -15591,7 +15667,7 @@
|
||||
"name": "ZeroBounce",
|
||||
"description": "Validate email deliverability and check account credits",
|
||||
"longDescription": "Integrate ZeroBounce to validate email deliverability in real time — detect invalid, catch-all, spamtrap, abuse, and do-not-mail addresses — and check your remaining validation credits.",
|
||||
"bgColor": "#330D49",
|
||||
"bgColor": "#FFFFFF",
|
||||
"iconName": "ZeroBounceIcon",
|
||||
"docsUrl": "https://docs.sim.ai/tools/zerobounce",
|
||||
"operations": [
|
||||
|
||||
@@ -34,7 +34,10 @@ export const cancelRunTool: ToolConfig<DatabricksCancelRunParams, DatabricksCanc
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const host = params.host.replace(/^https?:\/\//, '').replace(/\/$/, '')
|
||||
const host = params.host
|
||||
.trim()
|
||||
.replace(/^https?:\/\//, '')
|
||||
.replace(/\/$/, '')
|
||||
return `https://${host}/api/2.1/jobs/runs/cancel`
|
||||
},
|
||||
method: 'POST',
|
||||
|
||||
@@ -66,7 +66,10 @@ export const executeSqlTool: ToolConfig<DatabricksExecuteSqlParams, DatabricksEx
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const host = params.host.replace(/^https?:\/\//, '').replace(/\/$/, '')
|
||||
const host = params.host
|
||||
.trim()
|
||||
.replace(/^https?:\/\//, '')
|
||||
.replace(/\/$/, '')
|
||||
return `https://${host}/api/2.0/sql/statements/`
|
||||
},
|
||||
method: 'POST',
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import type {
|
||||
DatabricksGetClusterParams,
|
||||
DatabricksGetClusterResponse,
|
||||
} from '@/tools/databricks/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getClusterTool: ToolConfig<DatabricksGetClusterParams, DatabricksGetClusterResponse> =
|
||||
{
|
||||
id: 'databricks_get_cluster',
|
||||
name: 'Databricks Get Cluster',
|
||||
description:
|
||||
'Get the state, configuration, and resource details of a single Databricks cluster by its cluster ID.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
host: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Databricks workspace host (e.g., dbc-abc123.cloud.databricks.com)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Databricks Personal Access Token',
|
||||
},
|
||||
clusterId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the cluster to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const host = params.host
|
||||
.trim()
|
||||
.replace(/^https?:\/\//, '')
|
||||
.replace(/\/$/, '')
|
||||
const url = new URL(`https://${host}/api/2.0/clusters/get`)
|
||||
url.searchParams.set('cluster_id', params.clusterId.trim())
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error?.message || 'Failed to get cluster')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
cluster: {
|
||||
clusterId: data.cluster_id ?? '',
|
||||
clusterName: data.cluster_name ?? '',
|
||||
state: data.state ?? 'UNKNOWN',
|
||||
stateMessage: data.state_message ?? '',
|
||||
creatorUserName: data.creator_user_name ?? '',
|
||||
sparkVersion: data.spark_version ?? '',
|
||||
nodeTypeId: data.node_type_id ?? '',
|
||||
driverNodeTypeId: data.driver_node_type_id ?? '',
|
||||
numWorkers: data.num_workers ?? null,
|
||||
autoscale: data.autoscale
|
||||
? {
|
||||
minWorkers: data.autoscale.min_workers ?? 0,
|
||||
maxWorkers: data.autoscale.max_workers ?? 0,
|
||||
}
|
||||
: null,
|
||||
clusterSource: data.cluster_source ?? '',
|
||||
autoterminationMinutes: data.autotermination_minutes ?? 0,
|
||||
startTime: data.start_time ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
cluster: {
|
||||
type: 'object',
|
||||
description: 'Cluster detail',
|
||||
properties: {
|
||||
clusterId: { type: 'string', description: 'Unique cluster identifier' },
|
||||
clusterName: { type: 'string', description: 'Cluster display name' },
|
||||
state: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Current state (PENDING, RUNNING, RESTARTING, RESIZING, TERMINATING, TERMINATED, ERROR, UNKNOWN)',
|
||||
},
|
||||
stateMessage: { type: 'string', description: 'Human-readable state description' },
|
||||
creatorUserName: { type: 'string', description: 'Email of the cluster creator' },
|
||||
sparkVersion: {
|
||||
type: 'string',
|
||||
description: 'Spark runtime version (e.g., 13.3.x-scala2.12)',
|
||||
},
|
||||
nodeTypeId: { type: 'string', description: 'Worker node type identifier' },
|
||||
driverNodeTypeId: { type: 'string', description: 'Driver node type identifier' },
|
||||
numWorkers: {
|
||||
type: 'number',
|
||||
description: 'Number of worker nodes (for fixed-size clusters)',
|
||||
optional: true,
|
||||
},
|
||||
autoscale: {
|
||||
type: 'object',
|
||||
description: 'Autoscaling configuration (null for fixed-size clusters)',
|
||||
optional: true,
|
||||
properties: {
|
||||
minWorkers: { type: 'number', description: 'Minimum number of workers' },
|
||||
maxWorkers: { type: 'number', description: 'Maximum number of workers' },
|
||||
},
|
||||
},
|
||||
clusterSource: {
|
||||
type: 'string',
|
||||
description: 'Origin (API, UI, JOB, MODELS, PIPELINE, PIPELINE_MAINTENANCE, SQL)',
|
||||
},
|
||||
autoterminationMinutes: {
|
||||
type: 'number',
|
||||
description: 'Minutes of inactivity before auto-termination (0 = disabled)',
|
||||
},
|
||||
startTime: {
|
||||
type: 'number',
|
||||
description: 'Cluster start timestamp (epoch ms)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import type { DatabricksGetJobParams, DatabricksGetJobResponse } from '@/tools/databricks/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getJobTool: ToolConfig<DatabricksGetJobParams, DatabricksGetJobResponse> = {
|
||||
id: 'databricks_get_job',
|
||||
name: 'Databricks Get Job',
|
||||
description: 'Get the full definition and settings of a single Databricks job by its job ID.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
host: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Databricks workspace host (e.g., dbc-abc123.cloud.databricks.com)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Databricks Personal Access Token',
|
||||
},
|
||||
jobId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The canonical identifier of the job to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const host = params.host
|
||||
.trim()
|
||||
.replace(/^https?:\/\//, '')
|
||||
.replace(/\/$/, '')
|
||||
const url = new URL(`https://${host}/api/2.1/jobs/get`)
|
||||
url.searchParams.set('job_id', String(params.jobId))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error?.message || 'Failed to get job')
|
||||
}
|
||||
|
||||
const settings = data.settings ?? {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
jobId: data.job_id ?? 0,
|
||||
name: settings.name ?? '',
|
||||
creatorUserName: data.creator_user_name ?? '',
|
||||
runAsUserName: data.run_as_user_name ?? '',
|
||||
createdTime: data.created_time ?? 0,
|
||||
format: settings.format ?? '',
|
||||
maxConcurrentRuns: settings.max_concurrent_runs ?? 1,
|
||||
timeoutSeconds: settings.timeout_seconds ?? null,
|
||||
schedule: settings.schedule ?? null,
|
||||
tags: settings.tags ?? null,
|
||||
tasks: settings.tasks ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
jobId: { type: 'number', description: 'The job ID' },
|
||||
name: { type: 'string', description: 'Job name' },
|
||||
creatorUserName: { type: 'string', description: 'Email of the job creator' },
|
||||
runAsUserName: { type: 'string', description: 'User the job runs as' },
|
||||
createdTime: { type: 'number', description: 'Job creation timestamp (epoch ms)' },
|
||||
format: { type: 'string', description: 'Job format (SINGLE_TASK or MULTI_TASK)' },
|
||||
maxConcurrentRuns: { type: 'number', description: 'Maximum number of concurrent runs' },
|
||||
timeoutSeconds: {
|
||||
type: 'number',
|
||||
description: 'Job-level timeout in seconds (0 or null means no timeout)',
|
||||
optional: true,
|
||||
},
|
||||
schedule: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Cron schedule configuration (quartz_cron_expression, timezone_id, pause_status)',
|
||||
optional: true,
|
||||
},
|
||||
tags: {
|
||||
type: 'object',
|
||||
description: 'Key-value tags applied to the job',
|
||||
optional: true,
|
||||
},
|
||||
tasks: {
|
||||
type: 'array',
|
||||
description: 'Task definitions for the job (empty for single-task jobs)',
|
||||
items: { type: 'object' },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -42,7 +42,10 @@ export const getRunTool: ToolConfig<DatabricksGetRunParams, DatabricksGetRunResp
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const host = params.host.replace(/^https?:\/\//, '').replace(/\/$/, '')
|
||||
const host = params.host
|
||||
.trim()
|
||||
.replace(/^https?:\/\//, '')
|
||||
.replace(/\/$/, '')
|
||||
const url = new URL(`https://${host}/api/2.1/jobs/runs/get`)
|
||||
url.searchParams.set('run_id', String(params.runId))
|
||||
if (params.includeHistory) url.searchParams.set('include_history', 'true')
|
||||
|
||||
@@ -37,7 +37,10 @@ export const getRunOutputTool: ToolConfig<
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const host = params.host.replace(/^https?:\/\//, '').replace(/\/$/, '')
|
||||
const host = params.host
|
||||
.trim()
|
||||
.replace(/^https?:\/\//, '')
|
||||
.replace(/\/$/, '')
|
||||
return `https://${host}/api/2.1/jobs/runs/get-output?run_id=${params.runId}`
|
||||
},
|
||||
method: 'GET',
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import type {
|
||||
DatabricksExecuteSqlResponse,
|
||||
DatabricksGetStatementParams,
|
||||
} from '@/tools/databricks/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getStatementTool: ToolConfig<
|
||||
DatabricksGetStatementParams,
|
||||
DatabricksExecuteSqlResponse
|
||||
> = {
|
||||
id: 'databricks_get_statement',
|
||||
name: 'Databricks Get Statement',
|
||||
description:
|
||||
'Poll a SQL statement by its ID to retrieve status and results. Use this after Execute SQL when a query runs longer than the wait timeout.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
host: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Databricks workspace host (e.g., dbc-abc123.cloud.databricks.com)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Databricks Personal Access Token',
|
||||
},
|
||||
statementId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the statement to fetch (returned by Execute SQL)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const host = params.host
|
||||
.trim()
|
||||
.replace(/^https?:\/\//, '')
|
||||
.replace(/\/$/, '')
|
||||
return `https://${host}/api/2.0/sql/statements/${params.statementId.trim()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error?.message || 'Failed to get statement')
|
||||
}
|
||||
|
||||
const status = data.status?.state ?? 'UNKNOWN'
|
||||
if (status === 'FAILED') {
|
||||
throw new Error(
|
||||
data.status?.error?.message ||
|
||||
`SQL statement execution failed: ${data.status?.error?.error_code ?? 'UNKNOWN'}`
|
||||
)
|
||||
}
|
||||
|
||||
const columns =
|
||||
data.manifest?.schema?.columns?.map(
|
||||
(col: { name: string; position: number; type_name: string }) => ({
|
||||
name: col.name ?? '',
|
||||
position: col.position ?? 0,
|
||||
typeName: col.type_name ?? '',
|
||||
})
|
||||
) ?? null
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
statementId: data.statement_id ?? '',
|
||||
status,
|
||||
columns,
|
||||
data: data.result?.data_array ?? null,
|
||||
totalRows: data.manifest?.total_row_count ?? null,
|
||||
truncated: data.manifest?.truncated ?? false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
statementId: {
|
||||
type: 'string',
|
||||
description: 'Unique identifier for the statement',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Execution status (SUCCEEDED, PENDING, RUNNING, FAILED, CANCELED, CLOSED)',
|
||||
},
|
||||
columns: {
|
||||
type: 'array',
|
||||
description: 'Column schema of the result set',
|
||||
optional: true,
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Column name' },
|
||||
position: { type: 'number', description: 'Column position (0-based)' },
|
||||
typeName: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Column type (STRING, INT, LONG, DOUBLE, BOOLEAN, TIMESTAMP, DATE, DECIMAL, etc.)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Result rows as a 2D array of strings where each inner array is a row of column values',
|
||||
optional: true,
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'A single row of column values as strings',
|
||||
},
|
||||
},
|
||||
totalRows: {
|
||||
type: 'number',
|
||||
description: 'Total number of rows in the result',
|
||||
optional: true,
|
||||
},
|
||||
truncated: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the result set was truncated due to row_limit or byte_limit',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,17 +1,25 @@
|
||||
import { cancelRunTool } from '@/tools/databricks/cancel_run'
|
||||
import { executeSqlTool } from '@/tools/databricks/execute_sql'
|
||||
import { getClusterTool } from '@/tools/databricks/get_cluster'
|
||||
import { getJobTool } from '@/tools/databricks/get_job'
|
||||
import { getRunTool } from '@/tools/databricks/get_run'
|
||||
import { getRunOutputTool } from '@/tools/databricks/get_run_output'
|
||||
import { getStatementTool } from '@/tools/databricks/get_statement'
|
||||
import { listClustersTool } from '@/tools/databricks/list_clusters'
|
||||
import { listJobsTool } from '@/tools/databricks/list_jobs'
|
||||
import { listRunsTool } from '@/tools/databricks/list_runs'
|
||||
import { listWarehousesTool } from '@/tools/databricks/list_warehouses'
|
||||
import { runJobTool } from '@/tools/databricks/run_job'
|
||||
|
||||
export const databricksExecuteSqlTool = executeSqlTool
|
||||
export const databricksGetStatementTool = getStatementTool
|
||||
export const databricksListJobsTool = listJobsTool
|
||||
export const databricksGetJobTool = getJobTool
|
||||
export const databricksRunJobTool = runJobTool
|
||||
export const databricksGetRunTool = getRunTool
|
||||
export const databricksListRunsTool = listRunsTool
|
||||
export const databricksCancelRunTool = cancelRunTool
|
||||
export const databricksGetRunOutputTool = getRunOutputTool
|
||||
export const databricksListClustersTool = listClustersTool
|
||||
export const databricksGetClusterTool = getClusterTool
|
||||
export const databricksListWarehousesTool = listWarehousesTool
|
||||
|
||||
@@ -25,7 +25,10 @@ export const listClustersTool: ToolConfig<DatabricksBaseParams, DatabricksListCl
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const host = params.host.replace(/^https?:\/\//, '').replace(/\/$/, '')
|
||||
const host = params.host
|
||||
.trim()
|
||||
.replace(/^https?:\/\//, '')
|
||||
.replace(/\/$/, '')
|
||||
return `https://${host}/api/2.0/clusters/list`
|
||||
},
|
||||
method: 'GET',
|
||||
|
||||
@@ -48,7 +48,10 @@ export const listJobsTool: ToolConfig<DatabricksListJobsParams, DatabricksListJo
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const host = params.host.replace(/^https?:\/\//, '').replace(/\/$/, '')
|
||||
const host = params.host
|
||||
.trim()
|
||||
.replace(/^https?:\/\//, '')
|
||||
.replace(/\/$/, '')
|
||||
const url = new URL(`https://${host}/api/2.1/jobs/list`)
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
if (params.offset) url.searchParams.set('offset', String(params.offset))
|
||||
|
||||
@@ -73,7 +73,10 @@ export const listRunsTool: ToolConfig<DatabricksListRunsParams, DatabricksListRu
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const host = params.host.replace(/^https?:\/\//, '').replace(/\/$/, '')
|
||||
const host = params.host
|
||||
.trim()
|
||||
.replace(/^https?:\/\//, '')
|
||||
.replace(/\/$/, '')
|
||||
const url = new URL(`https://${host}/api/2.1/jobs/runs/list`)
|
||||
if (params.jobId) url.searchParams.set('job_id', String(params.jobId))
|
||||
if (params.activeOnly) url.searchParams.set('active_only', 'true')
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import type {
|
||||
DatabricksBaseParams,
|
||||
DatabricksListWarehousesResponse,
|
||||
} from '@/tools/databricks/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listWarehousesTool: ToolConfig<
|
||||
DatabricksBaseParams,
|
||||
DatabricksListWarehousesResponse
|
||||
> = {
|
||||
id: 'databricks_list_warehouses',
|
||||
name: 'Databricks List Warehouses',
|
||||
description:
|
||||
'List all SQL warehouses in a Databricks workspace including their size, state, and type. Use this to discover the warehouse ID needed for Execute SQL.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
host: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Databricks workspace host (e.g., dbc-abc123.cloud.databricks.com)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Databricks Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const host = params.host
|
||||
.trim()
|
||||
.replace(/^https?:\/\//, '')
|
||||
.replace(/\/$/, '')
|
||||
return `https://${host}/api/2.0/sql/warehouses`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error?.message || 'Failed to list warehouses')
|
||||
}
|
||||
|
||||
const warehouses = (data.warehouses ?? []).map(
|
||||
(warehouse: {
|
||||
id?: string
|
||||
name?: string
|
||||
cluster_size?: string
|
||||
state?: string
|
||||
warehouse_type?: string
|
||||
creator_name?: string
|
||||
auto_stop_mins?: number
|
||||
num_clusters?: number
|
||||
min_num_clusters?: number
|
||||
max_num_clusters?: number
|
||||
num_active_sessions?: number
|
||||
enable_serverless_compute?: boolean
|
||||
}) => ({
|
||||
warehouseId: warehouse.id ?? '',
|
||||
name: warehouse.name ?? '',
|
||||
clusterSize: warehouse.cluster_size ?? '',
|
||||
state: warehouse.state ?? 'UNKNOWN',
|
||||
warehouseType: warehouse.warehouse_type ?? '',
|
||||
creatorName: warehouse.creator_name ?? '',
|
||||
autoStopMinutes: warehouse.auto_stop_mins ?? 0,
|
||||
numClusters: warehouse.num_clusters ?? 0,
|
||||
minNumClusters: warehouse.min_num_clusters ?? 0,
|
||||
maxNumClusters: warehouse.max_num_clusters ?? 0,
|
||||
numActiveSessions: warehouse.num_active_sessions ?? 0,
|
||||
enableServerlessCompute: warehouse.enable_serverless_compute ?? false,
|
||||
})
|
||||
)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
warehouses,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
warehouses: {
|
||||
type: 'array',
|
||||
description: 'List of SQL warehouses in the workspace',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
warehouseId: { type: 'string', description: 'Unique warehouse identifier' },
|
||||
name: { type: 'string', description: 'Warehouse display name' },
|
||||
clusterSize: {
|
||||
type: 'string',
|
||||
description: 'Warehouse size (e.g., 2X-Small, Small, Medium, Large)',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
description: 'Current state (STARTING, RUNNING, STOPPING, STOPPED, DELETING, DELETED)',
|
||||
},
|
||||
warehouseType: { type: 'string', description: 'Warehouse type (CLASSIC, PRO)' },
|
||||
creatorName: { type: 'string', description: 'Email of the warehouse creator' },
|
||||
autoStopMinutes: {
|
||||
type: 'number',
|
||||
description: 'Minutes of inactivity before auto-stop (0 = disabled)',
|
||||
},
|
||||
numClusters: { type: 'number', description: 'Current number of running clusters' },
|
||||
minNumClusters: { type: 'number', description: 'Minimum cluster count for scaling' },
|
||||
maxNumClusters: { type: 'number', description: 'Maximum cluster count for scaling' },
|
||||
numActiveSessions: { type: 'number', description: 'Number of active sessions' },
|
||||
enableServerlessCompute: {
|
||||
type: 'boolean',
|
||||
description: 'Whether serverless compute is enabled',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -50,7 +50,10 @@ export const runJobTool: ToolConfig<DatabricksRunJobParams, DatabricksRunJobResp
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const host = params.host.replace(/^https?:\/\//, '').replace(/\/$/, '')
|
||||
const host = params.host
|
||||
.trim()
|
||||
.replace(/^https?:\/\//, '')
|
||||
.replace(/\/$/, '')
|
||||
return `https://${host}/api/2.1/jobs/run-now`
|
||||
},
|
||||
method: 'POST',
|
||||
|
||||
@@ -27,6 +27,11 @@ export interface DatabricksExecuteSqlResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
/** Get Statement (poll an async SQL statement by its ID) */
|
||||
export interface DatabricksGetStatementParams extends DatabricksBaseParams {
|
||||
statementId: string
|
||||
}
|
||||
|
||||
/** List Jobs */
|
||||
export interface DatabricksListJobsParams extends DatabricksBaseParams {
|
||||
limit?: number
|
||||
@@ -65,6 +70,27 @@ export interface DatabricksRunJobResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
/** Get Job */
|
||||
export interface DatabricksGetJobParams extends DatabricksBaseParams {
|
||||
jobId: number
|
||||
}
|
||||
|
||||
export interface DatabricksGetJobResponse extends ToolResponse {
|
||||
output: {
|
||||
jobId: number
|
||||
name: string
|
||||
creatorUserName: string
|
||||
runAsUserName: string
|
||||
createdTime: number
|
||||
format: string
|
||||
maxConcurrentRuns: number
|
||||
timeoutSeconds: number | null
|
||||
schedule: Record<string, unknown> | null
|
||||
tags: Record<string, unknown> | null
|
||||
tasks: Array<Record<string, unknown>>
|
||||
}
|
||||
}
|
||||
|
||||
/** Get Run */
|
||||
export interface DatabricksGetRunParams extends DatabricksBaseParams {
|
||||
runId: number
|
||||
@@ -158,23 +184,57 @@ export interface DatabricksGetRunOutputResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
/** Shared cluster shape returned by list_clusters and get_cluster */
|
||||
export interface DatabricksCluster {
|
||||
clusterId: string
|
||||
clusterName: string
|
||||
state: string
|
||||
stateMessage: string
|
||||
creatorUserName: string
|
||||
sparkVersion: string
|
||||
nodeTypeId: string
|
||||
driverNodeTypeId: string
|
||||
numWorkers: number | null
|
||||
autoscale: { minWorkers: number; maxWorkers: number } | null
|
||||
clusterSource: string
|
||||
autoterminationMinutes: number
|
||||
startTime: number | null
|
||||
}
|
||||
|
||||
/** List Clusters */
|
||||
export interface DatabricksListClustersResponse extends ToolResponse {
|
||||
output: {
|
||||
clusters: Array<{
|
||||
clusterId: string
|
||||
clusterName: string
|
||||
clusters: DatabricksCluster[]
|
||||
}
|
||||
}
|
||||
|
||||
/** Get Cluster */
|
||||
export interface DatabricksGetClusterParams extends DatabricksBaseParams {
|
||||
clusterId: string
|
||||
}
|
||||
|
||||
export interface DatabricksGetClusterResponse extends ToolResponse {
|
||||
output: {
|
||||
cluster: DatabricksCluster
|
||||
}
|
||||
}
|
||||
|
||||
/** List Warehouses */
|
||||
export interface DatabricksListWarehousesResponse extends ToolResponse {
|
||||
output: {
|
||||
warehouses: Array<{
|
||||
warehouseId: string
|
||||
name: string
|
||||
clusterSize: string
|
||||
state: string
|
||||
stateMessage: string
|
||||
creatorUserName: string
|
||||
sparkVersion: string
|
||||
nodeTypeId: string
|
||||
driverNodeTypeId: string
|
||||
numWorkers: number | null
|
||||
autoscale: { minWorkers: number; maxWorkers: number } | null
|
||||
clusterSource: string
|
||||
autoterminationMinutes: number
|
||||
startTime: number | null
|
||||
warehouseType: string
|
||||
creatorName: string
|
||||
autoStopMinutes: number
|
||||
numClusters: number
|
||||
minNumClusters: number
|
||||
maxNumClusters: number
|
||||
numActiveSessions: number
|
||||
enableServerlessCompute: boolean
|
||||
}>
|
||||
}
|
||||
}
|
||||
@@ -184,8 +244,11 @@ export type DatabricksResponse =
|
||||
| DatabricksExecuteSqlResponse
|
||||
| DatabricksListJobsResponse
|
||||
| DatabricksRunJobResponse
|
||||
| DatabricksGetJobResponse
|
||||
| DatabricksGetRunResponse
|
||||
| DatabricksListRunsResponse
|
||||
| DatabricksCancelRunResponse
|
||||
| DatabricksGetRunOutputResponse
|
||||
| DatabricksListClustersResponse
|
||||
| DatabricksGetClusterResponse
|
||||
| DatabricksListWarehousesResponse
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { DubBulkCreateLinksParams, DubBulkCreateLinksResponse } from '@/tools/dub/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const bulkCreateLinksTool: ToolConfig<DubBulkCreateLinksParams, DubBulkCreateLinksResponse> =
|
||||
{
|
||||
id: 'dub_bulk_create_links',
|
||||
name: 'Dub Bulk Create Links',
|
||||
description:
|
||||
'Create up to 100 short links in a single request. Returns the created links alongside any per-link errors.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dub API key',
|
||||
},
|
||||
links: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of link objects to create. Each object requires a "url" and may include domain, key, tagIds, and other link fields (max 100).',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.dub.co/links/bulk',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
body: (params) => {
|
||||
const links = typeof params.links === 'string' ? JSON.parse(params.links) : params.links
|
||||
return Array.isArray(links) ? links : [links]
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to bulk create links')
|
||||
}
|
||||
|
||||
const results = Array.isArray(data) ? (data as Record<string, unknown>[]) : []
|
||||
const created = results.filter((item) => !item.error)
|
||||
const errors = results.filter((item) => item.error)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
created,
|
||||
errors,
|
||||
count: created.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
created: {
|
||||
type: 'json',
|
||||
description: 'Array of successfully created link objects',
|
||||
},
|
||||
errors: {
|
||||
type: 'json',
|
||||
description: 'Array of per-link errors ({ link, error, code }) for links that failed',
|
||||
},
|
||||
count: { type: 'number', description: 'Number of links successfully created' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import type { DubBulkDeleteLinksParams, DubBulkDeleteLinksResponse } from '@/tools/dub/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const bulkDeleteLinksTool: ToolConfig<DubBulkDeleteLinksParams, DubBulkDeleteLinksResponse> =
|
||||
{
|
||||
id: 'dub_bulk_delete_links',
|
||||
name: 'Dub Bulk Delete Links',
|
||||
description:
|
||||
'Delete up to 100 short links in a single request by their link IDs. Non-existing IDs are ignored.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dub API key',
|
||||
},
|
||||
linkIds: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated link IDs to delete (max 100)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.dub.co/links/bulk')
|
||||
const ids = params.linkIds
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter(Boolean)
|
||||
url.searchParams.set('linkIds', ids.join(','))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to bulk delete links')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deletedCount: data.deletedCount ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deletedCount: { type: 'number', description: 'Number of links that were deleted' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import type { DubBulkUpdateLinksParams, DubBulkUpdateLinksResponse } from '@/tools/dub/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const bulkUpdateLinksTool: ToolConfig<DubBulkUpdateLinksParams, DubBulkUpdateLinksResponse> =
|
||||
{
|
||||
id: 'dub_bulk_update_links',
|
||||
name: 'Dub Bulk Update Links',
|
||||
description:
|
||||
'Apply the same set of field updates to up to 100 links at once, selected by link IDs or external IDs.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dub API key',
|
||||
},
|
||||
linkIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Comma-separated link IDs to update (max 100, takes precedence over externalIds)',
|
||||
},
|
||||
externalIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated external IDs to update (max 100)',
|
||||
},
|
||||
data: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON object of fields to apply to every selected link (e.g. { "archived": true, "tagIds": ["..."] })',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.dub.co/links/bulk',
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
body: (params) => {
|
||||
const data = typeof params.data === 'string' ? JSON.parse(params.data) : params.data
|
||||
const linkIds = params.linkIds
|
||||
? params.linkIds
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter(Boolean)
|
||||
: []
|
||||
const externalIds = params.externalIds
|
||||
? params.externalIds
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter(Boolean)
|
||||
: []
|
||||
if (linkIds.length === 0 && externalIds.length === 0) {
|
||||
throw new Error(
|
||||
'Bulk Update Links requires at least one Link ID or External ID to select which links to update.'
|
||||
)
|
||||
}
|
||||
const body: Record<string, unknown> = { data: data ?? {} }
|
||||
if (linkIds.length > 0) {
|
||||
body.linkIds = linkIds
|
||||
} else {
|
||||
body.externalIds = externalIds
|
||||
}
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to bulk update links')
|
||||
}
|
||||
|
||||
const updated = Array.isArray(data) ? (data as Record<string, unknown>[]) : []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
updated,
|
||||
count: updated.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
updated: {
|
||||
type: 'json',
|
||||
description: 'Array of updated link objects',
|
||||
},
|
||||
count: { type: 'number', description: 'Number of links updated' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import type { DubGetEventsParams, DubGetEventsResponse } from '@/tools/dub/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getEventsTool: ToolConfig<DubGetEventsParams, DubGetEventsResponse> = {
|
||||
id: 'dub_get_events',
|
||||
name: 'Dub List Events',
|
||||
description:
|
||||
'Retrieve a paginated stream of individual click, lead, and sale events for links, with filtering by link, time range, and location.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dub API key',
|
||||
},
|
||||
event: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event type: clicks (default), leads, or sales',
|
||||
},
|
||||
linkId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by link ID',
|
||||
},
|
||||
externalId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by external ID (prefix with ext_)',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by domain',
|
||||
},
|
||||
interval: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Time interval: 24h (default), 7d, 30d, 90d, 1y, mtd, qtd, ytd, or all',
|
||||
},
|
||||
start: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Start date/time in ISO 8601 format (overrides interval)',
|
||||
},
|
||||
end: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End date/time in ISO 8601 format (defaults to now)',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by country (ISO 3166-1 alpha-2 code)',
|
||||
},
|
||||
timezone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'IANA timezone for event timestamps (defaults to UTC)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (default: 1)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of events per page (default: 100, max: 1000)',
|
||||
},
|
||||
sortOrder: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order: desc (default) or asc',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.dub.co/events')
|
||||
if (params.event) url.searchParams.set('event', params.event)
|
||||
if (params.linkId) url.searchParams.set('linkId', params.linkId)
|
||||
if (params.externalId) url.searchParams.set('externalId', params.externalId)
|
||||
if (params.domain) url.searchParams.set('domain', params.domain)
|
||||
if (params.interval) url.searchParams.set('interval', params.interval)
|
||||
if (params.start) url.searchParams.set('start', params.start)
|
||||
if (params.end) url.searchParams.set('end', params.end)
|
||||
if (params.country) url.searchParams.set('country', params.country)
|
||||
if (params.timezone) url.searchParams.set('timezone', params.timezone)
|
||||
if (params.page) url.searchParams.set('page', String(params.page))
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to list events')
|
||||
}
|
||||
|
||||
const events = Array.isArray(data) ? (data as Record<string, unknown>[]) : []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
events,
|
||||
count: events.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
events: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of event objects (event, timestamp, click, link, and customer/sale data when applicable)',
|
||||
},
|
||||
count: { type: 'number', description: 'Number of events returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import type { DubGetLinksCountParams, DubGetLinksCountResponse } from '@/tools/dub/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getLinksCountTool: ToolConfig<DubGetLinksCountParams, DubGetLinksCountResponse> = {
|
||||
id: 'dub_get_links_count',
|
||||
name: 'Dub Count Links',
|
||||
description:
|
||||
'Retrieve the number of short links for the authenticated workspace, optionally filtered and grouped by domain, tag, user, or folder.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dub API key',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by domain',
|
||||
},
|
||||
search: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search query matched against the short link slug and destination URL',
|
||||
},
|
||||
tagIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated tag IDs to filter by',
|
||||
},
|
||||
tagNames: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated tag names to filter by (case-insensitive)',
|
||||
},
|
||||
folderId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by folder ID',
|
||||
},
|
||||
showArchived: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to include archived links (defaults to false)',
|
||||
},
|
||||
groupBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group counts by: domain, tagId, userId, or folderId',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.dub.co/links/count')
|
||||
if (params.domain) url.searchParams.set('domain', params.domain)
|
||||
if (params.search) url.searchParams.set('search', params.search)
|
||||
if (params.tagIds) url.searchParams.set('tagIds', params.tagIds)
|
||||
if (params.tagNames) url.searchParams.set('tagNames', params.tagNames)
|
||||
if (params.folderId) url.searchParams.set('folderId', params.folderId)
|
||||
if (params.showArchived !== undefined)
|
||||
url.searchParams.set('showArchived', String(params.showArchived))
|
||||
if (params.groupBy) url.searchParams.set('groupBy', params.groupBy)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to count links')
|
||||
}
|
||||
|
||||
if (typeof data === 'number') {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
count: data,
|
||||
groups: null,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const groups = Array.isArray(data) ? (data as Record<string, unknown>[]) : []
|
||||
const count = groups.reduce((sum, group) => sum + (Number(group.count) || 0), 0)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
count,
|
||||
groups,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
count: { type: 'number', description: 'Total number of links matching the filters' },
|
||||
groups: {
|
||||
type: 'json',
|
||||
description: 'Per-group counts when groupBy is set (e.g. [{ domain, count }])',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import type { DubGetQrCodeParams, DubGetQrCodeResponse } from '@/tools/dub/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getQrCodeTool: ToolConfig<DubGetQrCodeParams, DubGetQrCodeResponse> = {
|
||||
id: 'dub_get_qr_code',
|
||||
name: 'Dub Get QR Code',
|
||||
description:
|
||||
'Generate a customizable QR code (PNG) for a short link, with control over size, error correction, colors, and margin.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Dub API key',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The short link URL to encode in the QR code',
|
||||
},
|
||||
size: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'QR code size in pixels (default: 600)',
|
||||
},
|
||||
level: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Error correction level: L (default), M, Q, or H',
|
||||
},
|
||||
fgColor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Foreground color in hex (default: #000000)',
|
||||
},
|
||||
bgColor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Background color in hex (default: #FFFFFF)',
|
||||
},
|
||||
hideLogo: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to hide the logo in the center of the QR code (default: false)',
|
||||
},
|
||||
margin: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Margin (quiet zone) around the QR code (default: 2)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.dub.co/qr')
|
||||
url.searchParams.set('url', params.url.trim())
|
||||
if (params.size !== undefined) url.searchParams.set('size', String(params.size))
|
||||
if (params.level) url.searchParams.set('level', params.level)
|
||||
if (params.fgColor) url.searchParams.set('fgColor', params.fgColor)
|
||||
if (params.bgColor) url.searchParams.set('bgColor', params.bgColor)
|
||||
if (params.hideLogo !== undefined) url.searchParams.set('hideLogo', String(params.hideLogo))
|
||||
if (params.margin !== undefined) url.searchParams.set('margin', String(params.margin))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'image/png',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
let message = errorText || `Failed to generate QR code: ${response.status}`
|
||||
try {
|
||||
const parsed = JSON.parse(errorText)
|
||||
message = parsed.error?.message || parsed.error || message
|
||||
} catch {
|
||||
// Non-JSON error body; use the raw text
|
||||
}
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer()
|
||||
const buffer = Buffer.from(arrayBuffer)
|
||||
const mimeType = response.headers.get('content-type') || 'image/png'
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
file: {
|
||||
name: 'qrcode.png',
|
||||
mimeType,
|
||||
data: buffer.toString('base64'),
|
||||
size: buffer.length,
|
||||
},
|
||||
content: buffer.toString('base64'),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
file: {
|
||||
type: 'file',
|
||||
description: 'Generated QR code image stored in execution files',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Base64-encoded PNG image data',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
import { bulkCreateLinksTool } from '@/tools/dub/bulk_create_links'
|
||||
import { bulkDeleteLinksTool } from '@/tools/dub/bulk_delete_links'
|
||||
import { bulkUpdateLinksTool } from '@/tools/dub/bulk_update_links'
|
||||
import { createLinkTool } from '@/tools/dub/create_link'
|
||||
import { deleteLinkTool } from '@/tools/dub/delete_link'
|
||||
import { getAnalyticsTool } from '@/tools/dub/get_analytics'
|
||||
import { getEventsTool } from '@/tools/dub/get_events'
|
||||
import { getLinkTool } from '@/tools/dub/get_link'
|
||||
import { getLinksCountTool } from '@/tools/dub/get_links_count'
|
||||
import { getQrCodeTool } from '@/tools/dub/get_qr_code'
|
||||
import { listLinksTool } from '@/tools/dub/list_links'
|
||||
import { updateLinkTool } from '@/tools/dub/update_link'
|
||||
import { upsertLinkTool } from '@/tools/dub/upsert_link'
|
||||
@@ -13,3 +19,9 @@ export const dubUpsertLinkTool = upsertLinkTool
|
||||
export const dubDeleteLinkTool = deleteLinkTool
|
||||
export const dubListLinksTool = listLinksTool
|
||||
export const dubGetAnalyticsTool = getAnalyticsTool
|
||||
export const dubGetLinksCountTool = getLinksCountTool
|
||||
export const dubGetEventsTool = getEventsTool
|
||||
export const dubBulkCreateLinksTool = bulkCreateLinksTool
|
||||
export const dubBulkUpdateLinksTool = bulkUpdateLinksTool
|
||||
export const dubBulkDeleteLinksTool = bulkDeleteLinksTool
|
||||
export const dubGetQrCodeTool = getQrCodeTool
|
||||
|
||||
@@ -39,18 +39,6 @@ export const listLinksTool: ToolConfig<DubListLinksParams, DubListLinksResponse>
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to include archived links (defaults to false)',
|
||||
},
|
||||
sortBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort by field: createdAt, clicks, saleAmount, or lastClicked',
|
||||
},
|
||||
sortOrder: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order: asc or desc',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
@@ -73,8 +61,6 @@ export const listLinksTool: ToolConfig<DubListLinksParams, DubListLinksResponse>
|
||||
if (params.tagIds) url.searchParams.set('tagIds', params.tagIds)
|
||||
if (params.showArchived !== undefined)
|
||||
url.searchParams.set('showArchived', String(params.showArchived))
|
||||
if (params.sortBy) url.searchParams.set('sortBy', params.sortBy)
|
||||
if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder)
|
||||
if (params.page) url.searchParams.set('page', String(params.page))
|
||||
if (params.pageSize) url.searchParams.set('pageSize', String(params.pageSize))
|
||||
return url.toString()
|
||||
|
||||
+102
-2
@@ -81,8 +81,6 @@ export interface DubListLinksParams extends DubBaseParams {
|
||||
search?: string
|
||||
tagIds?: string
|
||||
showArchived?: boolean
|
||||
sortBy?: string
|
||||
sortOrder?: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}
|
||||
@@ -100,6 +98,55 @@ export interface DubGetAnalyticsParams extends DubBaseParams {
|
||||
timezone?: string
|
||||
}
|
||||
|
||||
export interface DubGetLinksCountParams extends DubBaseParams {
|
||||
domain?: string
|
||||
search?: string
|
||||
tagIds?: string
|
||||
tagNames?: string
|
||||
folderId?: string
|
||||
showArchived?: boolean
|
||||
groupBy?: string
|
||||
}
|
||||
|
||||
export interface DubGetEventsParams extends DubBaseParams {
|
||||
event?: string
|
||||
linkId?: string
|
||||
externalId?: string
|
||||
domain?: string
|
||||
interval?: string
|
||||
start?: string
|
||||
end?: string
|
||||
country?: string
|
||||
timezone?: string
|
||||
page?: number
|
||||
limit?: number
|
||||
sortOrder?: string
|
||||
}
|
||||
|
||||
export interface DubBulkCreateLinksParams extends DubBaseParams {
|
||||
links: unknown
|
||||
}
|
||||
|
||||
export interface DubBulkUpdateLinksParams extends DubBaseParams {
|
||||
linkIds?: string
|
||||
externalIds?: string
|
||||
data: unknown
|
||||
}
|
||||
|
||||
export interface DubBulkDeleteLinksParams extends DubBaseParams {
|
||||
linkIds: string
|
||||
}
|
||||
|
||||
export interface DubGetQrCodeParams extends DubBaseParams {
|
||||
url: string
|
||||
size?: number
|
||||
level?: string
|
||||
fgColor?: string
|
||||
bgColor?: string
|
||||
hideLogo?: boolean
|
||||
margin?: number
|
||||
}
|
||||
|
||||
interface DubLink {
|
||||
id: string
|
||||
domain: string
|
||||
@@ -165,6 +212,53 @@ export interface DubGetAnalyticsResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export interface DubGetLinksCountResponse extends ToolResponse {
|
||||
output: {
|
||||
count: number
|
||||
groups: Record<string, unknown>[] | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface DubGetEventsResponse extends ToolResponse {
|
||||
output: {
|
||||
events: Record<string, unknown>[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface DubBulkCreateLinksResponse extends ToolResponse {
|
||||
output: {
|
||||
created: Record<string, unknown>[]
|
||||
errors: Record<string, unknown>[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface DubBulkUpdateLinksResponse extends ToolResponse {
|
||||
output: {
|
||||
updated: Record<string, unknown>[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface DubBulkDeleteLinksResponse extends ToolResponse {
|
||||
output: {
|
||||
deletedCount: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface DubGetQrCodeResponse extends ToolResponse {
|
||||
output: {
|
||||
file: {
|
||||
name: string
|
||||
mimeType: string
|
||||
data: string
|
||||
size: number
|
||||
}
|
||||
content: string
|
||||
}
|
||||
}
|
||||
|
||||
export type DubResponse =
|
||||
| DubCreateLinkResponse
|
||||
| DubGetLinkResponse
|
||||
@@ -173,3 +267,9 @@ export type DubResponse =
|
||||
| DubDeleteLinkResponse
|
||||
| DubListLinksResponse
|
||||
| DubGetAnalyticsResponse
|
||||
| DubGetLinksCountResponse
|
||||
| DubGetEventsResponse
|
||||
| DubBulkCreateLinksResponse
|
||||
| DubBulkUpdateLinksResponse
|
||||
| DubBulkDeleteLinksResponse
|
||||
| DubGetQrCodeResponse
|
||||
|
||||
@@ -49,8 +49,7 @@ export const searchTool: ToolConfig<DuckDuckGoSearchParams, DuckDuckGoSearchResp
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
// Map related topics
|
||||
const relatedTopics = (data.RelatedTopics || []).map((topic: any) => ({
|
||||
const mapTopic = (topic: any) => ({
|
||||
FirstURL: topic.FirstURL,
|
||||
Text: topic.Text,
|
||||
Result: topic.Result,
|
||||
@@ -61,7 +60,16 @@ export const searchTool: ToolConfig<DuckDuckGoSearchParams, DuckDuckGoSearchResp
|
||||
Width: topic.Icon.Width,
|
||||
}
|
||||
: undefined,
|
||||
}))
|
||||
})
|
||||
|
||||
/**
|
||||
* RelatedTopics may contain nested disambiguation groups of the shape
|
||||
* `{ Name, Topics: [...] }`. Flatten those so the actual topics are
|
||||
* surfaced instead of empty objects.
|
||||
*/
|
||||
const relatedTopics = (data.RelatedTopics || []).flatMap((topic: any) =>
|
||||
Array.isArray(topic.Topics) ? topic.Topics.map(mapTopic) : [mapTopic(topic)]
|
||||
)
|
||||
|
||||
// Map results (external links)
|
||||
const results = (data.Results || []).map((result: any) => ({
|
||||
@@ -85,10 +93,14 @@ export const searchTool: ToolConfig<DuckDuckGoSearchParams, DuckDuckGoSearchResp
|
||||
abstractText: data.AbstractText || '',
|
||||
abstractSource: data.AbstractSource || '',
|
||||
abstractURL: data.AbstractURL || '',
|
||||
definition: data.Definition || '',
|
||||
definitionSource: data.DefinitionSource || '',
|
||||
definitionURL: data.DefinitionURL || '',
|
||||
image: data.Image || '',
|
||||
answer: data.Answer || '',
|
||||
answerType: data.AnswerType || '',
|
||||
type: data.Type || '',
|
||||
redirect: data.Redirect || '',
|
||||
relatedTopics,
|
||||
results,
|
||||
},
|
||||
@@ -116,6 +128,18 @@ export const searchTool: ToolConfig<DuckDuckGoSearchParams, DuckDuckGoSearchResp
|
||||
type: 'string',
|
||||
description: 'URL to the source of the abstract',
|
||||
},
|
||||
definition: {
|
||||
type: 'string',
|
||||
description: 'Dictionary-style definition if available',
|
||||
},
|
||||
definitionSource: {
|
||||
type: 'string',
|
||||
description: 'The source of the definition',
|
||||
},
|
||||
definitionURL: {
|
||||
type: 'string',
|
||||
description: 'URL to the source of the definition',
|
||||
},
|
||||
image: {
|
||||
type: 'string',
|
||||
description: 'URL to an image related to the topic',
|
||||
@@ -133,6 +157,10 @@ export const searchTool: ToolConfig<DuckDuckGoSearchParams, DuckDuckGoSearchResp
|
||||
description:
|
||||
'Response type: A (article), D (disambiguation), C (category), N (name), E (exclusive)',
|
||||
},
|
||||
redirect: {
|
||||
type: 'string',
|
||||
description: '!bang redirect URL, populated only for bang queries',
|
||||
},
|
||||
relatedTopics: {
|
||||
type: 'array',
|
||||
description: 'Array of related topics with URLs and descriptions',
|
||||
|
||||
@@ -14,8 +14,8 @@ interface DuckDuckGoRelatedTopic {
|
||||
Result?: string
|
||||
Icon?: {
|
||||
URL?: string
|
||||
Height?: string
|
||||
Width?: string
|
||||
Height?: number | string
|
||||
Width?: number | string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ interface DuckDuckGoResult {
|
||||
Result?: string
|
||||
Icon?: {
|
||||
URL?: string
|
||||
Height?: string
|
||||
Width?: string
|
||||
Height?: number | string
|
||||
Width?: number | string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +36,14 @@ interface DuckDuckGoSearchOutput {
|
||||
abstractText: string
|
||||
abstractSource: string
|
||||
abstractURL: string
|
||||
definition: string
|
||||
definitionSource: string
|
||||
definitionURL: string
|
||||
image: string
|
||||
answer: string
|
||||
answerType: string
|
||||
type: string
|
||||
redirect: string
|
||||
relatedTopics: DuckDuckGoRelatedTopic[]
|
||||
results: DuckDuckGoResult[]
|
||||
}
|
||||
|
||||
@@ -19,11 +19,14 @@ import { salesPointerPeopleTool } from '@/tools/enrich/sales_pointer_people'
|
||||
import { searchCompanyTool } from '@/tools/enrich/search_company'
|
||||
import { searchCompanyActivitiesTool } from '@/tools/enrich/search_company_activities'
|
||||
import { searchCompanyEmployeesTool } from '@/tools/enrich/search_company_employees'
|
||||
import { searchJobsTool } from '@/tools/enrich/search_jobs'
|
||||
import { searchLogoTool } from '@/tools/enrich/search_logo'
|
||||
import { searchPeopleTool } from '@/tools/enrich/search_people'
|
||||
import { searchPeopleActivitiesTool } from '@/tools/enrich/search_people_activities'
|
||||
import { searchPostCommentsTool } from '@/tools/enrich/search_post_comments'
|
||||
import { searchPostCommentsByUrlTool } from '@/tools/enrich/search_post_comments_by_url'
|
||||
import { searchPostReactionsTool } from '@/tools/enrich/search_post_reactions'
|
||||
import { searchPostReactionsByUrlTool } from '@/tools/enrich/search_post_reactions_by_url'
|
||||
import { searchPostsTool } from '@/tools/enrich/search_posts'
|
||||
import { searchSimilarCompaniesTool } from '@/tools/enrich/search_similar_companies'
|
||||
import { verifyEmailTool } from '@/tools/enrich/verify_email'
|
||||
@@ -52,7 +55,10 @@ export const enrichSalesPointerPeopleTool = salesPointerPeopleTool
|
||||
export const enrichSearchPostsTool = searchPostsTool
|
||||
export const enrichGetPostDetailsTool = getPostDetailsTool
|
||||
export const enrichSearchPostReactionsTool = searchPostReactionsTool
|
||||
export const enrichSearchPostReactionsByUrlTool = searchPostReactionsByUrlTool
|
||||
export const enrichSearchPostCommentsTool = searchPostCommentsTool
|
||||
export const enrichSearchPostCommentsByUrlTool = searchPostCommentsByUrlTool
|
||||
export const enrichSearchJobsTool = searchJobsTool
|
||||
export const enrichSearchPeopleActivitiesTool = searchPeopleActivitiesTool
|
||||
export const enrichSearchCompanyActivitiesTool = searchCompanyActivitiesTool
|
||||
export const enrichReverseHashLookupTool = reverseHashLookupTool
|
||||
|
||||
@@ -30,8 +30,8 @@ export const linkedInToPersonalEmailTool: ToolConfig<
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.enrich.so/v2/api/linkedin-to-email')
|
||||
url.searchParams.append('linkedin_profile', params.linkedinProfile.trim())
|
||||
const url = new URL('https://api.enrich.so/v1/api/find-personal-email')
|
||||
url.searchParams.append('profile_url', params.linkedinProfile.trim())
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
@@ -44,6 +44,19 @@ export const linkedInToPersonalEmailTool: ToolConfig<
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
// Handle queued response (202)
|
||||
if (data.status === 'in_progress' || data.message?.includes('queued')) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
email: null,
|
||||
found: false,
|
||||
status: 'in_progress',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const resultData = data.data ?? data
|
||||
|
||||
return {
|
||||
@@ -51,7 +64,7 @@ export const linkedInToPersonalEmailTool: ToolConfig<
|
||||
output: {
|
||||
email: resultData.email ?? resultData.personal_email ?? null,
|
||||
found: resultData.found ?? Boolean(resultData.email ?? resultData.personal_email),
|
||||
status: resultData.status ?? null,
|
||||
status: resultData.status ?? 'completed',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import type { EnrichSearchJobsParams, EnrichSearchJobsResponse } from '@/tools/enrich/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const searchJobsTool: ToolConfig<EnrichSearchJobsParams, EnrichSearchJobsResponse> = {
|
||||
id: 'enrich_search_jobs',
|
||||
name: 'Enrich Search Jobs',
|
||||
description:
|
||||
'Search LinkedIn job postings by keywords with filters for location, job type, workplace type, experience level, and company.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Enrich API key',
|
||||
},
|
||||
keywords: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search keywords (e.g., "software engineer")',
|
||||
},
|
||||
location: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Location filter (e.g., London)',
|
||||
},
|
||||
jobTypes: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated job types (e.g., "full time, part time")',
|
||||
},
|
||||
workplaceTypes: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated workplace types (e.g., "on site, remote")',
|
||||
},
|
||||
experienceLevels: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated experience levels (e.g., "internship, associate")',
|
||||
},
|
||||
companyIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated LinkedIn company IDs to filter by (e.g., "2048, 3050")',
|
||||
},
|
||||
timePosted: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Time filter (e.g., past_24hrs, past_week, past_month)',
|
||||
},
|
||||
start: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of records to skip for pagination (default: 0)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.enrich.so/v1/api/search-jobs')
|
||||
url.searchParams.append('keywords', params.keywords.trim())
|
||||
if (params.location) url.searchParams.append('location', params.location.trim())
|
||||
if (params.jobTypes) url.searchParams.append('jobTypes', params.jobTypes)
|
||||
if (params.workplaceTypes) url.searchParams.append('workplaceTypes', params.workplaceTypes)
|
||||
if (params.experienceLevels) {
|
||||
url.searchParams.append('experienceLevels', params.experienceLevels)
|
||||
}
|
||||
if (params.companyIds) url.searchParams.append('companyIds', params.companyIds)
|
||||
if (params.timePosted) url.searchParams.append('timePosted', params.timePosted)
|
||||
if (params.start !== undefined) url.searchParams.append('start', String(params.start))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
const rawJobs = data.data ?? data.jobs ?? (Array.isArray(data) ? data : [])
|
||||
|
||||
const jobs = rawJobs.map((job: any) => ({
|
||||
title: job.title ?? null,
|
||||
companyName: job.company_name ?? job.companyName ?? null,
|
||||
companyLink: job.company_link ?? job.companyLink ?? null,
|
||||
companyLogo: job.company_logo_url ?? job.company_logo ?? job.companyLogo ?? null,
|
||||
location: job.location ?? null,
|
||||
url: job.url ?? job.job_url ?? job.jobUrl ?? null,
|
||||
postedDate: job.posted_date ?? job.posting_date ?? job.postedDate ?? null,
|
||||
postedTimestamp:
|
||||
job.posted_timestamp ??
|
||||
job.posting_timestamp ??
|
||||
job.timestamp ??
|
||||
job.postedTimestamp ??
|
||||
null,
|
||||
hiringStatus: job.hiring_status ?? job.hiringStatus ?? null,
|
||||
criteria: job.criteria ?? job.employment_criteria ?? job.employmentCriteria ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
count: data.count ?? jobs.length,
|
||||
jobs,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
count: {
|
||||
type: 'number',
|
||||
description: 'Number of job postings returned',
|
||||
},
|
||||
jobs: {
|
||||
type: 'array',
|
||||
description: 'Job postings',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string', description: 'Job title' },
|
||||
companyName: { type: 'string', description: 'Hiring company name' },
|
||||
companyLink: { type: 'string', description: 'Company LinkedIn URL' },
|
||||
companyLogo: { type: 'string', description: 'Company logo URL' },
|
||||
location: { type: 'string', description: 'Job location' },
|
||||
url: { type: 'string', description: 'Job posting URL' },
|
||||
postedDate: { type: 'string', description: 'Date the job was posted' },
|
||||
postedTimestamp: { type: 'string', description: 'Timestamp the job was posted' },
|
||||
hiringStatus: { type: 'string', description: 'Hiring status' },
|
||||
criteria: {
|
||||
type: 'object',
|
||||
description: 'Employment criteria (seniority, type, function)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import type {
|
||||
EnrichSearchPostCommentsByUrlParams,
|
||||
EnrichSearchPostCommentsResponse,
|
||||
} from '@/tools/enrich/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const searchPostCommentsByUrlTool: ToolConfig<
|
||||
EnrichSearchPostCommentsByUrlParams,
|
||||
EnrichSearchPostCommentsResponse
|
||||
> = {
|
||||
id: 'enrich_search_post_comments_by_url',
|
||||
name: 'Enrich Search Post Comments by URL',
|
||||
description: 'Get comments on a LinkedIn post by its URL.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Enrich API key',
|
||||
},
|
||||
postUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'LinkedIn post URL (e.g., https://www.linkedin.com/posts/...)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (starts at 1, default: 1)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.enrich.so/v1/api/search-comment-by-url')
|
||||
url.searchParams.append('post_url', params.postUrl.trim())
|
||||
if (params.page !== undefined) {
|
||||
url.searchParams.append('page', String(params.page))
|
||||
}
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
const resultData = data.data ?? {}
|
||||
|
||||
const comments =
|
||||
resultData.data?.map((comment: any) => ({
|
||||
activityId: comment.activity_id ?? null,
|
||||
commentary: comment.commentary ?? null,
|
||||
linkedInUrl: comment.li_url ?? null,
|
||||
commenter: {
|
||||
profileId: comment.commenter?.profile_id ?? null,
|
||||
firstName: comment.commenter?.first_name ?? null,
|
||||
lastName: comment.commenter?.last_name ?? null,
|
||||
subTitle: comment.commenter?.sub_title ?? comment.commenter?.subTitle ?? null,
|
||||
profilePicture:
|
||||
comment.commenter?.profile_picture ?? comment.commenter?.profilePicture ?? null,
|
||||
backgroundImage:
|
||||
comment.commenter?.background_image ?? comment.commenter?.backgroundImage ?? null,
|
||||
entityUrn: comment.commenter?.entity_urn ?? comment.commenter?.entityUrn ?? null,
|
||||
objectUrn: comment.commenter?.object_urn ?? comment.commenter?.objectUrn ?? null,
|
||||
profileType: comment.commenter?.profile_type ?? comment.commenter?.profileType ?? null,
|
||||
},
|
||||
reactionBreakdown: {
|
||||
likes: comment.reaction_breakdown?.likes ?? 0,
|
||||
empathy: comment.reaction_breakdown?.empathy ?? 0,
|
||||
other: comment.reaction_breakdown?.other ?? 0,
|
||||
},
|
||||
})) ?? []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
page: resultData.page ?? 1,
|
||||
totalPage: resultData.total_page ?? 1,
|
||||
count: resultData.num ?? comments.length,
|
||||
comments,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
page: {
|
||||
type: 'number',
|
||||
description: 'Current page number',
|
||||
},
|
||||
totalPage: {
|
||||
type: 'number',
|
||||
description: 'Total number of pages',
|
||||
},
|
||||
count: {
|
||||
type: 'number',
|
||||
description: 'Number of comments returned',
|
||||
},
|
||||
comments: {
|
||||
type: 'array',
|
||||
description: 'Comments',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
activityId: { type: 'string', description: 'Comment activity ID' },
|
||||
commentary: { type: 'string', description: 'Comment text' },
|
||||
linkedInUrl: { type: 'string', description: 'Link to comment' },
|
||||
commenter: {
|
||||
type: 'object',
|
||||
description: 'Commenter info',
|
||||
properties: {
|
||||
profileId: { type: 'string', description: 'Profile ID' },
|
||||
firstName: { type: 'string', description: 'First name' },
|
||||
lastName: { type: 'string', description: 'Last name' },
|
||||
subTitle: { type: 'string', description: 'Subtitle/headline' },
|
||||
profilePicture: { type: 'string', description: 'Profile picture URL' },
|
||||
backgroundImage: { type: 'string', description: 'Background image URL' },
|
||||
entityUrn: { type: 'string', description: 'Entity URN' },
|
||||
objectUrn: { type: 'string', description: 'Object URN' },
|
||||
profileType: { type: 'string', description: 'Profile type' },
|
||||
},
|
||||
},
|
||||
reactionBreakdown: {
|
||||
type: 'object',
|
||||
description: 'Reactions on the comment',
|
||||
properties: {
|
||||
likes: { type: 'number', description: 'Number of likes' },
|
||||
empathy: { type: 'number', description: 'Number of empathy reactions' },
|
||||
other: { type: 'number', description: 'Number of other reactions' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import type {
|
||||
EnrichSearchPostReactionsByUrlParams,
|
||||
EnrichSearchPostReactionsResponse,
|
||||
} from '@/tools/enrich/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const searchPostReactionsByUrlTool: ToolConfig<
|
||||
EnrichSearchPostReactionsByUrlParams,
|
||||
EnrichSearchPostReactionsResponse
|
||||
> = {
|
||||
id: 'enrich_search_post_reactions_by_url',
|
||||
name: 'Enrich Search Post Reactions by URL',
|
||||
description: 'Get reactions on a LinkedIn post by its URL, filtered by reaction type.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Enrich API key',
|
||||
},
|
||||
postUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'LinkedIn post URL (e.g., https://www.linkedin.com/posts/...)',
|
||||
},
|
||||
reactionType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Reaction type filter: all, like, love, celebrate, insightful, or funny (default: all)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (starts at 1)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.enrich.so/v1/api/search-reaction-by-url')
|
||||
url.searchParams.append('post_url', params.postUrl.trim())
|
||||
url.searchParams.append('reaction_type', params.reactionType)
|
||||
url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
const resultData = data.data ?? {}
|
||||
|
||||
const reactions =
|
||||
resultData.data?.map((reaction: any) => ({
|
||||
reactionType: reaction.reaction_type ?? '',
|
||||
reactor: {
|
||||
name: reaction.reactor?.name ?? null,
|
||||
subTitle: reaction.reactor?.sub_title ?? null,
|
||||
profileId: reaction.reactor?.profile_id ?? null,
|
||||
profilePicture: reaction.reactor?.profile_picture ?? null,
|
||||
linkedInUrl: reaction.reactor?.li_url ?? null,
|
||||
},
|
||||
})) ?? []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
page: resultData.page ?? 1,
|
||||
totalPage: resultData.total_page ?? 1,
|
||||
count: resultData.num ?? reactions.length,
|
||||
reactions,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
page: {
|
||||
type: 'number',
|
||||
description: 'Current page number',
|
||||
},
|
||||
totalPage: {
|
||||
type: 'number',
|
||||
description: 'Total number of pages',
|
||||
},
|
||||
count: {
|
||||
type: 'number',
|
||||
description: 'Number of reactions returned',
|
||||
},
|
||||
reactions: {
|
||||
type: 'array',
|
||||
description: 'Reactions',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
reactionType: { type: 'string', description: 'Type of reaction' },
|
||||
reactor: {
|
||||
type: 'object',
|
||||
description: 'Person who reacted',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Name' },
|
||||
subTitle: { type: 'string', description: 'Job title' },
|
||||
profileId: { type: 'string', description: 'Profile ID' },
|
||||
profilePicture: { type: 'string', description: 'Profile picture URL' },
|
||||
linkedInUrl: { type: 'string', description: 'LinkedIn URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -740,3 +740,43 @@ export interface EnrichSearchLogoResponse extends ToolResponse {
|
||||
domain: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface EnrichSearchJobsParams extends EnrichBaseParams {
|
||||
keywords: string
|
||||
location?: string
|
||||
jobTypes?: string
|
||||
workplaceTypes?: string
|
||||
experienceLevels?: string
|
||||
companyIds?: string
|
||||
timePosted?: string
|
||||
start?: number
|
||||
}
|
||||
|
||||
export interface EnrichSearchJobsResponse extends ToolResponse {
|
||||
output: {
|
||||
count: number
|
||||
jobs: Array<{
|
||||
title: string | null
|
||||
companyName: string | null
|
||||
companyLink: string | null
|
||||
companyLogo: string | null
|
||||
location: string | null
|
||||
url: string | null
|
||||
postedDate: string | null
|
||||
postedTimestamp: string | null
|
||||
hiringStatus: string | null
|
||||
criteria: Record<string, unknown> | null
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export interface EnrichSearchPostReactionsByUrlParams extends EnrichBaseParams {
|
||||
postUrl: string
|
||||
reactionType: 'all' | 'like' | 'love' | 'celebrate' | 'insightful' | 'funny'
|
||||
page: number
|
||||
}
|
||||
|
||||
export interface EnrichSearchPostCommentsByUrlParams extends EnrichBaseParams {
|
||||
postUrl: string
|
||||
page?: number
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ export const firefliesCreateBiteTool: ToolConfig<
|
||||
$summary: String
|
||||
) {
|
||||
createBite(
|
||||
transcript_Id: $transcriptId
|
||||
transcript_id: $transcriptId
|
||||
start_time: $startTime
|
||||
end_time: $endTime
|
||||
name: $name
|
||||
|
||||
@@ -49,7 +49,12 @@ export const firefliesDeleteTranscriptTool: ToolConfig<
|
||||
query: `
|
||||
mutation DeleteTranscript($id: String!) {
|
||||
deleteTranscript(id: $id) {
|
||||
success
|
||||
id
|
||||
title
|
||||
date
|
||||
duration
|
||||
host_email
|
||||
organizer_email
|
||||
}
|
||||
}
|
||||
`,
|
||||
@@ -71,11 +76,27 @@ export const firefliesDeleteTranscriptTool: ToolConfig<
|
||||
}
|
||||
}
|
||||
|
||||
const result = data.data?.deleteTranscript
|
||||
const deleted = data.data?.deleteTranscript
|
||||
if (!deleted) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to delete transcript',
|
||||
output: { success: false },
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: result?.success ?? false,
|
||||
success: true,
|
||||
output: {
|
||||
success: result?.success ?? false,
|
||||
success: true,
|
||||
transcript: {
|
||||
id: deleted.id,
|
||||
title: deleted.title ?? null,
|
||||
date: deleted.date ?? null,
|
||||
duration: deleted.duration ?? null,
|
||||
host_email: deleted.host_email ?? null,
|
||||
organizer_email: deleted.organizer_email ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -85,5 +106,18 @@ export const firefliesDeleteTranscriptTool: ToolConfig<
|
||||
type: 'boolean',
|
||||
description: 'Whether the transcript was successfully deleted',
|
||||
},
|
||||
transcript: {
|
||||
type: 'object',
|
||||
description: 'The deleted transcript',
|
||||
optional: true,
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Transcript ID' },
|
||||
title: { type: 'string', description: 'Meeting title' },
|
||||
date: { type: 'number', description: 'Meeting timestamp' },
|
||||
duration: { type: 'number', description: 'Meeting duration' },
|
||||
host_email: { type: 'string', description: 'Host email address' },
|
||||
organizer_email: { type: 'string', description: 'Organizer email address' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -229,6 +229,14 @@ export interface FirefliesUploadAudioResponse extends ToolResponse {
|
||||
export interface FirefliesDeleteTranscriptResponse extends ToolResponse {
|
||||
output: {
|
||||
success?: boolean
|
||||
transcript?: {
|
||||
id: string
|
||||
title: string | null
|
||||
date: number | null
|
||||
duration: number | null
|
||||
host_email: string | null
|
||||
organizer_email: string | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
import type { GongAggregateByPeriodParams, GongAggregateByPeriodResponse } from '@/tools/gong/types'
|
||||
import { getGongErrorMessage, parseGongIdList } from '@/tools/gong/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const aggregateByPeriodTool: ToolConfig<
|
||||
GongAggregateByPeriodParams,
|
||||
GongAggregateByPeriodResponse
|
||||
> = {
|
||||
id: 'gong_aggregate_by_period',
|
||||
name: 'Gong Aggregate by Period',
|
||||
description:
|
||||
'Retrieve aggregated user activity grouped into time periods (day, week, month, quarter, year) by date range from Gong.',
|
||||
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',
|
||||
},
|
||||
aggregationPeriod: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Calendar period to group activity by: DAY, WEEK, MONTH, QUARTER, or YEAR (week starts Monday)',
|
||||
},
|
||||
userIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of Gong user IDs (up to 20 digits each)',
|
||||
},
|
||||
fromDate: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Start date in YYYY-MM-DD format (inclusive, in company timezone)',
|
||||
},
|
||||
toDate: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'End date in YYYY-MM-DD format (exclusive, in company timezone, cannot exceed current day)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from a previous response',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.gong.io/v2/stats/activity/aggregate-by-period',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Basic ${btoa(`${params.accessKey}:${params.accessKeySecret}`)}`,
|
||||
}),
|
||||
body: (params) => {
|
||||
const filter: Record<string, unknown> = {
|
||||
fromDate: params.fromDate.trim(),
|
||||
toDate: params.toDate.trim(),
|
||||
}
|
||||
const userIds = parseGongIdList(params.userIds)
|
||||
if (userIds) filter.userIds = userIds
|
||||
const body: Record<string, unknown> = {
|
||||
aggregationPeriod: params.aggregationPeriod.trim(),
|
||||
filter,
|
||||
}
|
||||
if (params.cursor?.trim()) body.cursor = params.cursor.trim()
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(getGongErrorMessage(data, 'Failed to get aggregate-by-period activity'))
|
||||
}
|
||||
const usersAggregateActivity = (data.usersAggregateActivity ?? []).map(
|
||||
(ua: Record<string, unknown>) => ({
|
||||
userId: ua.userId ?? '',
|
||||
userEmailAddress: ua.userEmailAddress ?? null,
|
||||
userAggregateActivity: (
|
||||
(ua.userAggregateActivity as Record<string, unknown>[] | undefined) ?? []
|
||||
).map((period: Record<string, unknown>) => ({
|
||||
fromDate: period.fromDate ?? null,
|
||||
toDate: period.toDate ?? null,
|
||||
callsAsHost: period.callsAsHost ?? null,
|
||||
callsAttended: period.callsAttended ?? null,
|
||||
callsGaveFeedback: period.callsGaveFeedback ?? null,
|
||||
callsReceivedFeedback: period.callsReceivedFeedback ?? null,
|
||||
callsRequestedFeedback: period.callsRequestedFeedback ?? null,
|
||||
callsScorecardsFilled: period.callsScorecardsFilled ?? null,
|
||||
callsScorecardsReceived: period.callsScorecardsReceived ?? null,
|
||||
ownCallsListenedTo: period.ownCallsListenedTo ?? null,
|
||||
othersCallsListenedTo: period.othersCallsListenedTo ?? null,
|
||||
callsSharedInternally: period.callsSharedInternally ?? null,
|
||||
callsSharedExternally: period.callsSharedExternally ?? null,
|
||||
callsCommentsGiven: period.callsCommentsGiven ?? null,
|
||||
callsCommentsReceived: period.callsCommentsReceived ?? null,
|
||||
callsMarkedAsFeedbackGiven: period.callsMarkedAsFeedbackGiven ?? null,
|
||||
callsMarkedAsFeedbackReceived: period.callsMarkedAsFeedbackReceived ?? null,
|
||||
})),
|
||||
})
|
||||
)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
requestId: data.requestId ?? null,
|
||||
usersAggregateActivity,
|
||||
cursor: data.records?.cursor ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
requestId: {
|
||||
type: 'string',
|
||||
description: 'A Gong request reference ID for troubleshooting purposes',
|
||||
optional: true,
|
||||
},
|
||||
usersAggregateActivity: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Aggregated activity per user, one item per consecutive time period in the range',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', description: "Gong's unique numeric identifier for the user" },
|
||||
userEmailAddress: { type: 'string', description: 'Email address of the Gong user' },
|
||||
userAggregateActivity: {
|
||||
type: 'array',
|
||||
description: 'Activity counts per time period',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fromDate: { type: 'string', description: 'Start of the period (ISO-8601)' },
|
||||
toDate: { type: 'string', description: 'End of the period (ISO-8601)' },
|
||||
callsAsHost: { type: 'number', description: 'Calls the user hosted' },
|
||||
callsAttended: {
|
||||
type: 'number',
|
||||
description: 'Calls the user attended (not host)',
|
||||
},
|
||||
callsGaveFeedback: {
|
||||
type: 'number',
|
||||
description: 'Calls the user gave feedback on',
|
||||
},
|
||||
callsReceivedFeedback: {
|
||||
type: 'number',
|
||||
description: 'Calls the user received feedback on',
|
||||
},
|
||||
callsRequestedFeedback: {
|
||||
type: 'number',
|
||||
description: 'Calls the user requested feedback on',
|
||||
},
|
||||
callsScorecardsFilled: {
|
||||
type: 'number',
|
||||
description: 'Scorecards the user completed',
|
||||
},
|
||||
callsScorecardsReceived: {
|
||||
type: 'number',
|
||||
description: "Calls where someone filled a scorecard on the user's calls",
|
||||
},
|
||||
ownCallsListenedTo: {
|
||||
type: 'number',
|
||||
description: "The user's own calls the user listened to",
|
||||
},
|
||||
othersCallsListenedTo: {
|
||||
type: 'number',
|
||||
description: "Other users' calls the user listened to",
|
||||
},
|
||||
callsSharedInternally: {
|
||||
type: 'number',
|
||||
description: 'Calls the user shared internally',
|
||||
},
|
||||
callsSharedExternally: {
|
||||
type: 'number',
|
||||
description: 'Calls the user shared externally',
|
||||
},
|
||||
callsCommentsGiven: {
|
||||
type: 'number',
|
||||
description: 'Calls the user commented on',
|
||||
},
|
||||
callsCommentsReceived: {
|
||||
type: 'number',
|
||||
description: "Calls where the user's calls received a comment",
|
||||
},
|
||||
callsMarkedAsFeedbackGiven: {
|
||||
type: 'number',
|
||||
description: 'Calls the user marked as reviewed',
|
||||
},
|
||||
callsMarkedAsFeedbackReceived: {
|
||||
type: 'number',
|
||||
description: "The user's calls marked as reviewed by others",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
description: 'Pagination cursor for the next page',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
import type { GongDayByDayActivityParams, GongDayByDayActivityResponse } from '@/tools/gong/types'
|
||||
import { getGongErrorMessage, parseGongIdList } from '@/tools/gong/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const dayByDayActivityTool: ToolConfig<
|
||||
GongDayByDayActivityParams,
|
||||
GongDayByDayActivityResponse
|
||||
> = {
|
||||
id: 'gong_day_by_day_activity',
|
||||
name: 'Gong Day-by-Day Activity',
|
||||
description:
|
||||
'Retrieve detailed day-by-day activity (call IDs per activity type) for users by date range from Gong.',
|
||||
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',
|
||||
},
|
||||
userIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of Gong user IDs (up to 20 digits each)',
|
||||
},
|
||||
fromDate: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Start date in YYYY-MM-DD format (inclusive, in company timezone)',
|
||||
},
|
||||
toDate: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'End date in YYYY-MM-DD format (exclusive, in company timezone, cannot exceed current day)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor from a previous response',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.gong.io/v2/stats/activity/day-by-day',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Basic ${btoa(`${params.accessKey}:${params.accessKeySecret}`)}`,
|
||||
}),
|
||||
body: (params) => {
|
||||
const filter: Record<string, unknown> = {
|
||||
fromDate: params.fromDate.trim(),
|
||||
toDate: params.toDate.trim(),
|
||||
}
|
||||
const userIds = parseGongIdList(params.userIds)
|
||||
if (userIds) filter.userIds = userIds
|
||||
const body: Record<string, unknown> = { filter }
|
||||
if (params.cursor?.trim()) body.cursor = params.cursor.trim()
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(getGongErrorMessage(data, 'Failed to get day-by-day activity'))
|
||||
}
|
||||
const usersDetailedActivities = (data.usersDetailedActivities ?? []).map(
|
||||
(ud: Record<string, unknown>) => ({
|
||||
userId: ud.userId ?? '',
|
||||
userEmailAddress: ud.userEmailAddress ?? null,
|
||||
userDailyActivityStats: (
|
||||
(ud.userDailyActivityStats as Record<string, unknown>[] | undefined) ?? []
|
||||
).map((day: Record<string, unknown>) => ({
|
||||
fromDate: day.fromDate ?? null,
|
||||
toDate: day.toDate ?? null,
|
||||
callsAsHost: day.callsAsHost ?? [],
|
||||
callsAttended: day.callsAttended ?? [],
|
||||
callsGaveFeedback: day.callsGaveFeedback ?? [],
|
||||
callsReceivedFeedback: day.callsReceivedFeedback ?? [],
|
||||
callsRequestedFeedback: day.callsRequestedFeedback ?? [],
|
||||
callsScorecardsFilled: day.callsScorecardsFilled ?? [],
|
||||
callsScorecardsReceived: day.callsScorecardsReceived ?? [],
|
||||
ownCallsListenedTo: day.ownCallsListenedTo ?? [],
|
||||
othersCallsListenedTo: day.othersCallsListenedTo ?? [],
|
||||
callsSharedInternally: day.callsSharedInternally ?? [],
|
||||
callsSharedExternally: day.callsSharedExternally ?? [],
|
||||
callsCommentsGiven: day.callsCommentsGiven ?? [],
|
||||
callsCommentsReceived: day.callsCommentsReceived ?? [],
|
||||
callsMarkedAsFeedbackGiven: day.callsMarkedAsFeedbackGiven ?? [],
|
||||
callsMarkedAsFeedbackReceived: day.callsMarkedAsFeedbackReceived ?? [],
|
||||
})),
|
||||
})
|
||||
)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
requestId: data.requestId ?? null,
|
||||
usersDetailedActivities,
|
||||
cursor: data.records?.cursor ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
requestId: {
|
||||
type: 'string',
|
||||
description: 'A Gong request reference ID for troubleshooting purposes',
|
||||
optional: true,
|
||||
},
|
||||
usersDetailedActivities: {
|
||||
type: 'array',
|
||||
description: 'Day-by-day activity per user, with call IDs grouped by activity type',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', description: "Gong's unique numeric identifier for the user" },
|
||||
userEmailAddress: { type: 'string', description: 'Email address of the Gong user' },
|
||||
userDailyActivityStats: {
|
||||
type: 'array',
|
||||
description: 'One record per day in the date range',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
fromDate: { type: 'string', description: 'Start of the day (ISO-8601)' },
|
||||
toDate: { type: 'string', description: 'End of the day (ISO-8601)' },
|
||||
callsAsHost: { type: 'array', description: 'IDs of calls the user hosted' },
|
||||
callsAttended: {
|
||||
type: 'array',
|
||||
description: 'IDs of calls the user attended (not host)',
|
||||
},
|
||||
callsGaveFeedback: {
|
||||
type: 'array',
|
||||
description: 'IDs of calls the user gave feedback on',
|
||||
},
|
||||
callsReceivedFeedback: {
|
||||
type: 'array',
|
||||
description: 'IDs of calls the user received feedback on',
|
||||
},
|
||||
callsRequestedFeedback: {
|
||||
type: 'array',
|
||||
description: 'IDs of calls the user requested feedback on',
|
||||
},
|
||||
callsScorecardsFilled: {
|
||||
type: 'array',
|
||||
description: 'IDs of calls the user filled scorecards on',
|
||||
},
|
||||
callsScorecardsReceived: {
|
||||
type: 'array',
|
||||
description: "IDs of the user's calls that received a scorecard",
|
||||
},
|
||||
ownCallsListenedTo: {
|
||||
type: 'array',
|
||||
description: "IDs of the user's own calls the user listened to",
|
||||
},
|
||||
othersCallsListenedTo: {
|
||||
type: 'array',
|
||||
description: "IDs of other users' calls the user listened to",
|
||||
},
|
||||
callsSharedInternally: {
|
||||
type: 'array',
|
||||
description: 'IDs of calls the user shared internally',
|
||||
},
|
||||
callsSharedExternally: {
|
||||
type: 'array',
|
||||
description: 'IDs of calls the user shared externally',
|
||||
},
|
||||
callsCommentsGiven: {
|
||||
type: 'array',
|
||||
description: 'IDs of calls the user commented on',
|
||||
},
|
||||
callsCommentsReceived: {
|
||||
type: 'array',
|
||||
description: "IDs of the user's calls that received a comment",
|
||||
},
|
||||
callsMarkedAsFeedbackGiven: {
|
||||
type: 'array',
|
||||
description: 'IDs of calls the user marked as reviewed',
|
||||
},
|
||||
callsMarkedAsFeedbackReceived: {
|
||||
type: 'array',
|
||||
description: "IDs of the user's calls marked as reviewed by others",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
description: 'Pagination cursor for the next page',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -8,7 +8,8 @@ export const getExtensiveCallsTool: ToolConfig<
|
||||
> = {
|
||||
id: 'gong_get_extensive_calls',
|
||||
name: 'Gong Get Extensive Calls',
|
||||
description: 'Retrieve detailed call data including trackers, topics, and highlights from Gong.',
|
||||
description:
|
||||
'Retrieve detailed call data including trackers, topics, highlights, and AI spotlight content (brief, outline, key points, call outcome) from Gong.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
@@ -89,6 +90,10 @@ export const getExtensiveCallsTool: ToolConfig<
|
||||
trackers: true,
|
||||
trackerOccurrences: true,
|
||||
highlights: true,
|
||||
brief: true,
|
||||
outline: true,
|
||||
keyPoints: true,
|
||||
callOutcome: true,
|
||||
},
|
||||
collaboration: { publicComments: true },
|
||||
interaction: {
|
||||
@@ -194,6 +199,45 @@ export const getExtensiveCallsTool: ToolConfig<
|
||||
type: 'object',
|
||||
description: 'Call content data',
|
||||
properties: {
|
||||
brief: {
|
||||
type: 'string',
|
||||
description: 'AI-generated brief summary of the call (Call Spotlight)',
|
||||
},
|
||||
outline: {
|
||||
type: 'array',
|
||||
description: 'AI-generated call outline sections',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
section: { type: 'string', description: 'Outline section name' },
|
||||
startTime: {
|
||||
type: 'number',
|
||||
description: 'Section start in seconds from call start',
|
||||
},
|
||||
duration: { type: 'number', description: 'Section duration in seconds' },
|
||||
items: { type: 'array', description: 'Bullet items within the section' },
|
||||
},
|
||||
},
|
||||
},
|
||||
keyPoints: {
|
||||
type: 'array',
|
||||
description: 'AI-generated key points of the call',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
text: { type: 'string', description: 'Key point text' },
|
||||
},
|
||||
},
|
||||
},
|
||||
callOutcome: {
|
||||
type: 'object',
|
||||
description: 'AI-determined call outcome (Call Spotlight)',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Outcome category ID' },
|
||||
category: { type: 'string', description: 'Outcome category name' },
|
||||
name: { type: 'string', description: 'Outcome name' },
|
||||
},
|
||||
},
|
||||
structure: {
|
||||
type: 'array',
|
||||
description: 'Call agenda parts',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { aggregateActivityTool } from '@/tools/gong/aggregate_activity'
|
||||
import { aggregateByPeriodTool } from '@/tools/gong/aggregate_by_period'
|
||||
import { answeredScorecardsTool } from '@/tools/gong/answered_scorecards'
|
||||
import { createCallTool } from '@/tools/gong/create_call'
|
||||
import { dayByDayActivityTool } from '@/tools/gong/day_by_day_activity'
|
||||
import { getCallTool } from '@/tools/gong/get_call'
|
||||
import { getCallTranscriptTool } from '@/tools/gong/get_call_transcript'
|
||||
import { getCoachingTool } from '@/tools/gong/get_coaching'
|
||||
@@ -26,6 +28,8 @@ export const gongGetExtensiveCallsTool = getExtensiveCallsTool
|
||||
export const gongListUsersTool = listUsersTool
|
||||
export const gongGetUserTool = getUserTool
|
||||
export const gongAggregateActivityTool = aggregateActivityTool
|
||||
export const gongDayByDayActivityTool = dayByDayActivityTool
|
||||
export const gongAggregateByPeriodTool = aggregateByPeriodTool
|
||||
export const gongInteractionStatsTool = interactionStatsTool
|
||||
export const gongAnsweredScorecardsTool = answeredScorecardsTool
|
||||
export const gongListLibraryFoldersTool = listLibraryFoldersTool
|
||||
|
||||
@@ -297,6 +297,91 @@ export interface GongInteractionStatsResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
/** Day-by-Day Activity */
|
||||
export interface GongDayByDayActivityParams extends GongBaseParams {
|
||||
userIds?: string
|
||||
fromDate: string
|
||||
toDate: string
|
||||
cursor?: string
|
||||
}
|
||||
|
||||
interface GongDailyActivity {
|
||||
fromDate: string | null
|
||||
toDate: string | null
|
||||
callsAsHost: string[]
|
||||
callsAttended: string[]
|
||||
callsGaveFeedback: string[]
|
||||
callsReceivedFeedback: string[]
|
||||
callsRequestedFeedback: string[]
|
||||
callsScorecardsFilled: string[]
|
||||
callsScorecardsReceived: string[]
|
||||
ownCallsListenedTo: string[]
|
||||
othersCallsListenedTo: string[]
|
||||
callsSharedInternally: string[]
|
||||
callsSharedExternally: string[]
|
||||
callsCommentsGiven: string[]
|
||||
callsCommentsReceived: string[]
|
||||
callsMarkedAsFeedbackGiven: string[]
|
||||
callsMarkedAsFeedbackReceived: string[]
|
||||
}
|
||||
|
||||
interface GongUserDayByDayActivity {
|
||||
userId: string
|
||||
userEmailAddress: string | null
|
||||
userDailyActivityStats: GongDailyActivity[]
|
||||
}
|
||||
|
||||
export interface GongDayByDayActivityResponse extends ToolResponse {
|
||||
output: {
|
||||
requestId: string | null
|
||||
usersDetailedActivities: GongUserDayByDayActivity[]
|
||||
cursor: string | null
|
||||
}
|
||||
}
|
||||
|
||||
/** Aggregate by Period */
|
||||
export interface GongAggregateByPeriodParams extends GongBaseParams {
|
||||
aggregationPeriod: string
|
||||
userIds?: string
|
||||
fromDate: string
|
||||
toDate: string
|
||||
cursor?: string
|
||||
}
|
||||
|
||||
interface GongPeriodActivity {
|
||||
fromDate: string | null
|
||||
toDate: string | null
|
||||
callsAsHost: number | null
|
||||
callsAttended: number | null
|
||||
callsGaveFeedback: number | null
|
||||
callsReceivedFeedback: number | null
|
||||
callsRequestedFeedback: number | null
|
||||
callsScorecardsFilled: number | null
|
||||
callsScorecardsReceived: number | null
|
||||
ownCallsListenedTo: number | null
|
||||
othersCallsListenedTo: number | null
|
||||
callsSharedInternally: number | null
|
||||
callsSharedExternally: number | null
|
||||
callsCommentsGiven: number | null
|
||||
callsCommentsReceived: number | null
|
||||
callsMarkedAsFeedbackGiven: number | null
|
||||
callsMarkedAsFeedbackReceived: number | null
|
||||
}
|
||||
|
||||
interface GongUserAggregateByPeriod {
|
||||
userId: string
|
||||
userEmailAddress: string | null
|
||||
userAggregateActivity: GongPeriodActivity[]
|
||||
}
|
||||
|
||||
export interface GongAggregateByPeriodResponse extends ToolResponse {
|
||||
output: {
|
||||
requestId: string | null
|
||||
usersAggregateActivity: GongUserAggregateByPeriod[]
|
||||
cursor: string | null
|
||||
}
|
||||
}
|
||||
|
||||
/** Answered Scorecards */
|
||||
export interface GongAnsweredScorecardsParams extends GongBaseParams {
|
||||
callFromDate?: string
|
||||
@@ -620,6 +705,8 @@ export type GongResponse =
|
||||
| GongListUsersResponse
|
||||
| GongGetUserResponse
|
||||
| GongAggregateActivityResponse
|
||||
| GongDayByDayActivityResponse
|
||||
| GongAggregateByPeriodResponse
|
||||
| GongInteractionStatsResponse
|
||||
| GongAnsweredScorecardsResponse
|
||||
| GongListLibraryFoldersResponse
|
||||
|
||||
@@ -47,7 +47,7 @@ export const oktaActivateUserTool: ToolConfig<OktaActivateUserParams, OktaActiva
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const sendEmail = params.sendEmail ?? true
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId)}/lifecycle/activate?sendEmail=${sendEmail}`
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/lifecycle/activate?sendEmail=${sendEmail}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
|
||||
@@ -48,7 +48,7 @@ export const oktaAddUserToGroupTool: ToolConfig<
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId)}/users/${encodeURIComponent(params.userId)}`
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId.trim())}/users/${encodeURIComponent(params.userId.trim())}`
|
||||
},
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
|
||||
@@ -50,7 +50,7 @@ export const oktaDeactivateUserTool: ToolConfig<
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const sendEmail = params.sendEmail === true
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId)}/lifecycle/deactivate?sendEmail=${sendEmail}`
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/lifecycle/deactivate?sendEmail=${sendEmail}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
|
||||
@@ -40,7 +40,7 @@ export const oktaDeleteGroupTool: ToolConfig<OktaDeleteGroupParams, OktaDeleteGr
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId)}`
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId.trim())}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
|
||||
@@ -43,7 +43,7 @@ export const oktaDeleteUserTool: ToolConfig<OktaDeleteUserParams, OktaDeleteUser
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const sendEmail = params.sendEmail === true
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId)}?sendEmail=${sendEmail}`
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}?sendEmail=${sendEmail}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
|
||||
@@ -40,7 +40,7 @@ export const oktaGetGroupTool: ToolConfig<OktaGetGroupParams, OktaGetGroupRespon
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId)}`
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId.trim())}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
|
||||
@@ -40,7 +40,7 @@ export const oktaGetUserTool: ToolConfig<OktaGetUserParams, OktaGetUserResponse>
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId)}`
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
|
||||
@@ -54,7 +54,7 @@ export const oktaListGroupMembersTool: ToolConfig<
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
const base = `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId)}/users`
|
||||
const base = `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId.trim())}/users`
|
||||
return queryString ? `${base}?${queryString}` : base
|
||||
},
|
||||
method: 'GET',
|
||||
|
||||
@@ -48,7 +48,7 @@ export const oktaRemoveUserFromGroupTool: ToolConfig<
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId)}/users/${encodeURIComponent(params.userId)}`
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId.trim())}/users/${encodeURIComponent(params.userId.trim())}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
|
||||
@@ -48,7 +48,7 @@ export const oktaResetPasswordTool: ToolConfig<OktaResetPasswordParams, OktaRese
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const sendEmail = params.sendEmail ?? true
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId)}/lifecycle/reset_password?sendEmail=${sendEmail}`
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/lifecycle/reset_password?sendEmail=${sendEmail}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
|
||||
@@ -40,7 +40,7 @@ export const oktaSuspendUserTool: ToolConfig<OktaSuspendUserParams, OktaSuspendU
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId)}/lifecycle/suspend`
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/lifecycle/suspend`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
|
||||
@@ -41,7 +41,7 @@ export const oktaUnsuspendUserTool: ToolConfig<OktaUnsuspendUserParams, OktaUnsu
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId)}/lifecycle/unsuspend`
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/lifecycle/unsuspend`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
|
||||
@@ -53,7 +53,7 @@ export const oktaUpdateGroupTool: ToolConfig<OktaUpdateGroupParams, OktaUpdateGr
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId)}`
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId.trim())}`
|
||||
},
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
|
||||
@@ -82,7 +82,7 @@ export const oktaUpdateUserTool: ToolConfig<OktaUpdateUserParams, OktaUpdateUser
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId)}`
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
|
||||
@@ -537,11 +537,15 @@ import {
|
||||
import {
|
||||
databricksCancelRunTool,
|
||||
databricksExecuteSqlTool,
|
||||
databricksGetClusterTool,
|
||||
databricksGetJobTool,
|
||||
databricksGetRunOutputTool,
|
||||
databricksGetRunTool,
|
||||
databricksGetStatementTool,
|
||||
databricksListClustersTool,
|
||||
databricksListJobsTool,
|
||||
databricksListRunsTool,
|
||||
databricksListWarehousesTool,
|
||||
databricksRunJobTool,
|
||||
} from '@/tools/databricks'
|
||||
import {
|
||||
@@ -632,10 +636,16 @@ import {
|
||||
} from '@/tools/dropbox'
|
||||
import { chainOfThoughtTool, predictTool, reactTool } from '@/tools/dspy'
|
||||
import {
|
||||
dubBulkCreateLinksTool,
|
||||
dubBulkDeleteLinksTool,
|
||||
dubBulkUpdateLinksTool,
|
||||
dubCreateLinkTool,
|
||||
dubDeleteLinkTool,
|
||||
dubGetAnalyticsTool,
|
||||
dubGetEventsTool,
|
||||
dubGetLinksCountTool,
|
||||
dubGetLinkTool,
|
||||
dubGetQrCodeTool,
|
||||
dubListLinksTool,
|
||||
dubUpdateLinkTool,
|
||||
dubUpsertLinkTool,
|
||||
@@ -703,10 +713,13 @@ import {
|
||||
enrichSearchCompanyActivitiesTool,
|
||||
enrichSearchCompanyEmployeesTool,
|
||||
enrichSearchCompanyTool,
|
||||
enrichSearchJobsTool,
|
||||
enrichSearchLogoTool,
|
||||
enrichSearchPeopleActivitiesTool,
|
||||
enrichSearchPeopleTool,
|
||||
enrichSearchPostCommentsByUrlTool,
|
||||
enrichSearchPostCommentsTool,
|
||||
enrichSearchPostReactionsByUrlTool,
|
||||
enrichSearchPostReactionsTool,
|
||||
enrichSearchPostsTool,
|
||||
enrichSearchSimilarCompaniesTool,
|
||||
@@ -1023,8 +1036,10 @@ import {
|
||||
} from '@/tools/gmail'
|
||||
import {
|
||||
gongAggregateActivityTool,
|
||||
gongAggregateByPeriodTool,
|
||||
gongAnsweredScorecardsTool,
|
||||
gongCreateCallTool,
|
||||
gongDayByDayActivityTool,
|
||||
gongGetCallTool,
|
||||
gongGetCallTranscriptTool,
|
||||
gongGetCoachingTool,
|
||||
@@ -2763,10 +2778,14 @@ import {
|
||||
} from '@/tools/sentry'
|
||||
import { serperSearchTool } from '@/tools/serper'
|
||||
import {
|
||||
servicenowAggregateTool,
|
||||
servicenowCreateRecordTool,
|
||||
servicenowDeleteRecordTool,
|
||||
servicenowDownloadAttachmentTool,
|
||||
servicenowListAttachmentsTool,
|
||||
servicenowReadRecordTool,
|
||||
servicenowUpdateRecordTool,
|
||||
servicenowUploadAttachmentTool,
|
||||
} from '@/tools/servicenow'
|
||||
import {
|
||||
sesCreateTemplateTool,
|
||||
@@ -3612,8 +3631,10 @@ export const tools: Record<string, ToolConfig> = {
|
||||
fireflies_list_bites: firefliesListBitesTool,
|
||||
fireflies_list_contacts: firefliesListContactsTool,
|
||||
gong_aggregate_activity: gongAggregateActivityTool,
|
||||
gong_aggregate_by_period: gongAggregateByPeriodTool,
|
||||
gong_answered_scorecards: gongAnsweredScorecardsTool,
|
||||
gong_create_call: gongCreateCallTool,
|
||||
gong_day_by_day_activity: gongDayByDayActivityTool,
|
||||
gong_get_call: gongGetCallTool,
|
||||
gong_get_call_transcript: gongGetCallTranscriptTool,
|
||||
gong_get_coaching: gongGetCoachingTool,
|
||||
@@ -3991,6 +4012,10 @@ export const tools: Record<string, ToolConfig> = {
|
||||
servicenow_read_record: servicenowReadRecordTool,
|
||||
servicenow_update_record: servicenowUpdateRecordTool,
|
||||
servicenow_delete_record: servicenowDeleteRecordTool,
|
||||
servicenow_aggregate: servicenowAggregateTool,
|
||||
servicenow_list_attachments: servicenowListAttachmentsTool,
|
||||
servicenow_download_attachment: servicenowDownloadAttachmentTool,
|
||||
servicenow_upload_attachment: servicenowUploadAttachmentTool,
|
||||
sixtyfour_find_phone: sixtyfourFindPhoneTool,
|
||||
sixtyfour_find_email: sixtyfourFindEmailTool,
|
||||
sixtyfour_enrich_lead: sixtyfourEnrichLeadTool,
|
||||
@@ -4321,16 +4346,26 @@ export const tools: Record<string, ToolConfig> = {
|
||||
dagster_wipe_asset: dagsterWipeAssetTool,
|
||||
databricks_cancel_run: databricksCancelRunTool,
|
||||
databricks_execute_sql: databricksExecuteSqlTool,
|
||||
databricks_get_cluster: databricksGetClusterTool,
|
||||
databricks_get_job: databricksGetJobTool,
|
||||
databricks_get_run: databricksGetRunTool,
|
||||
databricks_get_run_output: databricksGetRunOutputTool,
|
||||
databricks_get_statement: databricksGetStatementTool,
|
||||
databricks_list_clusters: databricksListClustersTool,
|
||||
databricks_list_jobs: databricksListJobsTool,
|
||||
databricks_list_runs: databricksListRunsTool,
|
||||
databricks_list_warehouses: databricksListWarehousesTool,
|
||||
databricks_run_job: databricksRunJobTool,
|
||||
dub_bulk_create_links: dubBulkCreateLinksTool,
|
||||
dub_bulk_delete_links: dubBulkDeleteLinksTool,
|
||||
dub_bulk_update_links: dubBulkUpdateLinksTool,
|
||||
dub_create_link: dubCreateLinkTool,
|
||||
dub_delete_link: dubDeleteLinkTool,
|
||||
dub_get_analytics: dubGetAnalyticsTool,
|
||||
dub_get_events: dubGetEventsTool,
|
||||
dub_get_link: dubGetLinkTool,
|
||||
dub_get_links_count: dubGetLinksCountTool,
|
||||
dub_get_qr_code: dubGetQrCodeTool,
|
||||
dub_list_links: dubListLinksTool,
|
||||
dub_update_link: dubUpdateLinkTool,
|
||||
dub_upsert_link: dubUpsertLinkTool,
|
||||
@@ -4626,11 +4661,14 @@ export const tools: Record<string, ToolConfig> = {
|
||||
enrich_search_company: enrichSearchCompanyTool,
|
||||
enrich_search_company_activities: enrichSearchCompanyActivitiesTool,
|
||||
enrich_search_company_employees: enrichSearchCompanyEmployeesTool,
|
||||
enrich_search_jobs: enrichSearchJobsTool,
|
||||
enrich_search_logo: enrichSearchLogoTool,
|
||||
enrich_search_people: enrichSearchPeopleTool,
|
||||
enrich_search_people_activities: enrichSearchPeopleActivitiesTool,
|
||||
enrich_search_post_comments: enrichSearchPostCommentsTool,
|
||||
enrich_search_post_comments_by_url: enrichSearchPostCommentsByUrlTool,
|
||||
enrich_search_post_reactions: enrichSearchPostReactionsTool,
|
||||
enrich_search_post_reactions_by_url: enrichSearchPostReactionsByUrlTool,
|
||||
enrich_search_posts: enrichSearchPostsTool,
|
||||
enrich_search_similar_companies: enrichSearchSimilarCompaniesTool,
|
||||
enrich_verify_email: enrichVerifyEmailTool,
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ServiceNowAggregateParams,
|
||||
ServiceNowAggregateResponse,
|
||||
} from '@/tools/servicenow/types'
|
||||
import { createBasicAuthHeader } from '@/tools/servicenow/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ServiceNowAggregateTool')
|
||||
|
||||
export const aggregateTool: ToolConfig<ServiceNowAggregateParams, ServiceNowAggregateResponse> = {
|
||||
id: 'servicenow_aggregate',
|
||||
name: 'Aggregate ServiceNow Records',
|
||||
description:
|
||||
'Compute aggregate statistics (count, sum, average, min, max, group by) over a ServiceNow table',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow instance URL (e.g., https://instance.service-now.com)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow password',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table name (e.g., incident, change_request, task)',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Encoded query string to filter records before aggregating (e.g., "active=true")',
|
||||
},
|
||||
count: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return the count of matching records',
|
||||
},
|
||||
groupBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated fields to group results by (e.g., category,priority)',
|
||||
},
|
||||
avgFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated numeric fields to average (e.g., reassignment_count)',
|
||||
},
|
||||
sumFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated numeric fields to sum',
|
||||
},
|
||||
minFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated fields to compute the minimum of',
|
||||
},
|
||||
maxFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated fields to compute the maximum of',
|
||||
},
|
||||
having: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter on aggregate results (e.g., "count>5")',
|
||||
},
|
||||
displayValue: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return display values for grouped reference fields: "true", "false", or "all"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
const url = `${baseUrl}/api/now/stats/${params.tableName.trim()}`
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.query) {
|
||||
queryParams.append('sysparm_query', params.query)
|
||||
}
|
||||
if (params.count) {
|
||||
queryParams.append('sysparm_count', 'true')
|
||||
}
|
||||
if (params.groupBy) {
|
||||
queryParams.append('sysparm_group_by', params.groupBy)
|
||||
}
|
||||
if (params.avgFields) {
|
||||
queryParams.append('sysparm_avg_fields', params.avgFields)
|
||||
}
|
||||
if (params.sumFields) {
|
||||
queryParams.append('sysparm_sum_fields', params.sumFields)
|
||||
}
|
||||
if (params.minFields) {
|
||||
queryParams.append('sysparm_min_fields', params.minFields)
|
||||
}
|
||||
if (params.maxFields) {
|
||||
queryParams.append('sysparm_max_fields', params.maxFields)
|
||||
}
|
||||
if (params.having) {
|
||||
queryParams.append('sysparm_having', params.having)
|
||||
}
|
||||
if (params.displayValue) {
|
||||
queryParams.append('sysparm_display_value', params.displayValue)
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${url}?${queryString}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.username || !params.password) {
|
||||
throw new Error('ServiceNow username and password are required')
|
||||
}
|
||||
return {
|
||||
Authorization: createBasicAuthHeader(params.username, params.password),
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
try {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
const error = data.error || data
|
||||
throw new Error(typeof error === 'string' ? error : error.message || JSON.stringify(error))
|
||||
}
|
||||
|
||||
const result = data.result ?? null
|
||||
const grouped = Array.isArray(result)
|
||||
const count = !grouped && result?.stats?.count != null ? Number(result.stats.count) : null
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
result,
|
||||
count,
|
||||
metadata: {
|
||||
grouped,
|
||||
groupCount: grouped ? result.length : null,
|
||||
},
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('ServiceNow aggregate - Error processing response:', { error })
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
result: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Aggregate result. Ungrouped: {stats: {count, sum, avg, min, max}}. Grouped: array of {stats, groupby_fields}.',
|
||||
},
|
||||
count: {
|
||||
type: 'number',
|
||||
description: 'Total matching record count (only present for ungrouped count queries)',
|
||||
optional: true,
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata (grouped, groupCount)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -47,11 +47,11 @@ export const createRecordTool: ToolConfig<ServiceNowCreateParams, ServiceNowCrea
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.replace(/\/$/, '')
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
return `${baseUrl}/api/now/table/${params.tableName}`
|
||||
return `${baseUrl}/api/now/table/${params.tableName.trim()}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
|
||||
@@ -46,11 +46,11 @@ export const deleteRecordTool: ToolConfig<ServiceNowDeleteParams, ServiceNowDele
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.replace(/\/$/, '')
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
return `${baseUrl}/api/now/table/${params.tableName}/${params.sysId}`
|
||||
return `${baseUrl}/api/now/table/${params.tableName.trim()}/${params.sysId.trim()}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ServiceNowDownloadAttachmentParams,
|
||||
ServiceNowDownloadAttachmentResponse,
|
||||
} from '@/tools/servicenow/types'
|
||||
import { createBasicAuthHeader } from '@/tools/servicenow/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ServiceNowDownloadAttachmentTool')
|
||||
|
||||
export const downloadAttachmentTool: ToolConfig<
|
||||
ServiceNowDownloadAttachmentParams,
|
||||
ServiceNowDownloadAttachmentResponse
|
||||
> = {
|
||||
id: 'servicenow_download_attachment',
|
||||
name: 'Download ServiceNow Attachment',
|
||||
description: 'Download an attachment file from ServiceNow by its sys_id',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow instance URL (e.g., https://instance.service-now.com)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow password',
|
||||
},
|
||||
attachmentSysId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'sys_id of the attachment to download (from List Attachments)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
return `${baseUrl}/api/now/attachment/${params.attachmentSysId.trim()}/file`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.username || !params.password) {
|
||||
throw new Error('ServiceNow username and password are required')
|
||||
}
|
||||
return {
|
||||
Authorization: createBasicAuthHeader(params.username, params.password),
|
||||
Accept: '*/*',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('ServiceNow download attachment - request failed', {
|
||||
status: response.status,
|
||||
errorText,
|
||||
})
|
||||
throw new Error(errorText || `Failed to download attachment: ${response.status}`)
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') || 'application/octet-stream'
|
||||
const contentDisposition = response.headers.get('content-disposition')
|
||||
let fileName = 'attachment'
|
||||
|
||||
if (contentDisposition) {
|
||||
const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
|
||||
if (match?.[1]) {
|
||||
fileName = match[1].replace(/['"]/g, '')
|
||||
}
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer()
|
||||
const buffer = Buffer.from(arrayBuffer)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
file: {
|
||||
name: fileName,
|
||||
mimeType: contentType,
|
||||
data: buffer.toString('base64'),
|
||||
size: buffer.length,
|
||||
},
|
||||
content: buffer.toString('base64'),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
file: {
|
||||
type: 'file',
|
||||
description: 'Downloaded attachment stored in execution files',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Base64 encoded file content',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
import { aggregateTool } from '@/tools/servicenow/aggregate'
|
||||
import { createRecordTool } from '@/tools/servicenow/create_record'
|
||||
import { deleteRecordTool } from '@/tools/servicenow/delete_record'
|
||||
import { downloadAttachmentTool } from '@/tools/servicenow/download_attachment'
|
||||
import { listAttachmentsTool } from '@/tools/servicenow/list_attachments'
|
||||
import { readRecordTool } from '@/tools/servicenow/read_record'
|
||||
import { updateRecordTool } from '@/tools/servicenow/update_record'
|
||||
import { uploadAttachmentTool } from '@/tools/servicenow/upload_attachment'
|
||||
|
||||
export {
|
||||
createRecordTool as servicenowCreateRecordTool,
|
||||
readRecordTool as servicenowReadRecordTool,
|
||||
updateRecordTool as servicenowUpdateRecordTool,
|
||||
deleteRecordTool as servicenowDeleteRecordTool,
|
||||
aggregateTool as servicenowAggregateTool,
|
||||
listAttachmentsTool as servicenowListAttachmentsTool,
|
||||
downloadAttachmentTool as servicenowDownloadAttachmentTool,
|
||||
uploadAttachmentTool as servicenowUploadAttachmentTool,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ServiceNowListAttachmentsParams,
|
||||
ServiceNowListAttachmentsResponse,
|
||||
} from '@/tools/servicenow/types'
|
||||
import { createBasicAuthHeader } from '@/tools/servicenow/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ServiceNowListAttachmentsTool')
|
||||
|
||||
export const listAttachmentsTool: ToolConfig<
|
||||
ServiceNowListAttachmentsParams,
|
||||
ServiceNowListAttachmentsResponse
|
||||
> = {
|
||||
id: 'servicenow_list_attachments',
|
||||
name: 'List ServiceNow Attachments',
|
||||
description: 'List the attachments on a ServiceNow record',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow instance URL (e.g., https://instance.service-now.com)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow password',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table that owns the record (e.g., incident, change_request)',
|
||||
},
|
||||
recordSysId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'sys_id of the record whose attachments should be listed',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of attachments to return',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append(
|
||||
'sysparm_query',
|
||||
`table_name=${params.tableName.trim()}^table_sys_id=${params.recordSysId.trim()}`
|
||||
)
|
||||
if (params.limit) {
|
||||
queryParams.append('sysparm_limit', params.limit.toString())
|
||||
}
|
||||
|
||||
return `${baseUrl}/api/now/attachment?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.username || !params.password) {
|
||||
throw new Error('ServiceNow username and password are required')
|
||||
}
|
||||
return {
|
||||
Authorization: createBasicAuthHeader(params.username, params.password),
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
try {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
const error = data.error || data
|
||||
throw new Error(typeof error === 'string' ? error : error.message || JSON.stringify(error))
|
||||
}
|
||||
|
||||
const attachments = Array.isArray(data.result) ? data.result : []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
attachments,
|
||||
metadata: {
|
||||
recordCount: attachments.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('ServiceNow list attachments - Error processing response:', { error })
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
attachments: {
|
||||
type: 'array',
|
||||
description: 'Attachment metadata records',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sys_id: { type: 'string', description: 'Attachment sys_id' },
|
||||
file_name: { type: 'string', description: 'File name' },
|
||||
content_type: { type: 'string', description: 'MIME type' },
|
||||
size_bytes: { type: 'string', description: 'File size in bytes' },
|
||||
download_link: { type: 'string', description: 'Direct download URL for the file' },
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata (recordCount)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -84,16 +84,16 @@ export const readRecordTool: ToolConfig<ServiceNowReadParams, ServiceNowReadResp
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.replace(/\/$/, '')
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
let url = `${baseUrl}/api/now/table/${params.tableName}`
|
||||
let url = `${baseUrl}/api/now/table/${params.tableName.trim()}`
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.sysId) {
|
||||
url = `${url}/${params.sysId}`
|
||||
url = `${url}/${params.sysId.trim()}`
|
||||
} else if (params.number) {
|
||||
const numberQuery = `number=${params.number}`
|
||||
const existingQuery = params.query
|
||||
|
||||
@@ -73,8 +73,102 @@ export interface ServiceNowDeleteResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceNowAggregateParams extends ServiceNowBaseParams {
|
||||
query?: string
|
||||
count?: boolean
|
||||
groupBy?: string
|
||||
avgFields?: string
|
||||
sumFields?: string
|
||||
minFields?: string
|
||||
maxFields?: string
|
||||
having?: string
|
||||
displayValue?: string
|
||||
}
|
||||
|
||||
export interface ServiceNowAggregateResponse extends ToolResponse {
|
||||
output: {
|
||||
result: Record<string, any> | Record<string, any>[] | null
|
||||
count: number | null
|
||||
metadata: {
|
||||
grouped: boolean
|
||||
groupCount: number | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceNowAttachment {
|
||||
sys_id: string
|
||||
file_name: string
|
||||
content_type: string
|
||||
size_bytes?: string
|
||||
table_name?: string
|
||||
table_sys_id?: string
|
||||
download_link?: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface ServiceNowListAttachmentsParams {
|
||||
instanceUrl: string
|
||||
username: string
|
||||
password: string
|
||||
tableName: string
|
||||
recordSysId: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface ServiceNowListAttachmentsResponse extends ToolResponse {
|
||||
output: {
|
||||
attachments: ServiceNowAttachment[]
|
||||
metadata: {
|
||||
recordCount: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceNowDownloadAttachmentParams {
|
||||
instanceUrl: string
|
||||
username: string
|
||||
password: string
|
||||
attachmentSysId: string
|
||||
}
|
||||
|
||||
export interface ServiceNowDownloadAttachmentResponse extends ToolResponse {
|
||||
output: {
|
||||
file: {
|
||||
name: string
|
||||
mimeType: string
|
||||
data: string
|
||||
size: number
|
||||
}
|
||||
content: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceNowUploadAttachmentParams {
|
||||
instanceUrl: string
|
||||
username: string
|
||||
password: string
|
||||
tableName: string
|
||||
recordSysId: string
|
||||
fileName: string
|
||||
file?: unknown
|
||||
}
|
||||
|
||||
export interface ServiceNowUploadAttachmentResponse extends ToolResponse {
|
||||
output: {
|
||||
attachment: ServiceNowAttachment
|
||||
metadata: {
|
||||
recordCount: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ServiceNowResponse =
|
||||
| ServiceNowCreateResponse
|
||||
| ServiceNowReadResponse
|
||||
| ServiceNowUpdateResponse
|
||||
| ServiceNowDeleteResponse
|
||||
| ServiceNowAggregateResponse
|
||||
| ServiceNowListAttachmentsResponse
|
||||
| ServiceNowDownloadAttachmentResponse
|
||||
| ServiceNowUploadAttachmentResponse
|
||||
|
||||
@@ -52,11 +52,11 @@ export const updateRecordTool: ToolConfig<ServiceNowUpdateParams, ServiceNowUpda
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.replace(/\/$/, '')
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
return `${baseUrl}/api/now/table/${params.tableName}/${params.sysId}`
|
||||
return `${baseUrl}/api/now/table/${params.tableName.trim()}/${params.sysId.trim()}`
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ServiceNowUploadAttachmentParams,
|
||||
ServiceNowUploadAttachmentResponse,
|
||||
} from '@/tools/servicenow/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ServiceNowUploadAttachmentTool')
|
||||
|
||||
export const uploadAttachmentTool: ToolConfig<
|
||||
ServiceNowUploadAttachmentParams,
|
||||
ServiceNowUploadAttachmentResponse
|
||||
> = {
|
||||
id: 'servicenow_upload_attachment',
|
||||
name: 'Upload ServiceNow Attachment',
|
||||
description: 'Attach a file to a ServiceNow record',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow instance URL (e.g., https://instance.service-now.com)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow password',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table that owns the record (e.g., incident, change_request)',
|
||||
},
|
||||
recordSysId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'sys_id of the record to attach the file to',
|
||||
},
|
||||
fileName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name to give the uploaded file (e.g., logs.txt)',
|
||||
},
|
||||
file: {
|
||||
type: 'file',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'File to upload (UserFile object)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/servicenow/upload-attachment',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
instanceUrl: params.instanceUrl,
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
tableName: params.tableName,
|
||||
recordSysId: params.recordSysId,
|
||||
fileName: params.fileName,
|
||||
file: params.file,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('ServiceNow upload attachment failed', { error: data.error })
|
||||
throw new Error(data.error || 'ServiceNow upload attachment failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: data.output,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
attachment: {
|
||||
type: 'json',
|
||||
description: 'Created attachment metadata (sys_id, file_name, content_type, download_link)',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
},
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user