Compare commits

...

9 Commits

Author SHA1 Message Date
Waleed
da46a387c9 v0.5.92: shortlinks, copilot scrolling stickiness, pagination 2026-02-17 15:13:21 -08:00
Waleed
a0afb5d03e feat(pipedrive): added sort order to endpoints that support it, upgraded turborepo (#3237)
* feat(pipedrive): added sort order to endpoints that support it

* upgraded turborepo

* fix
2026-02-17 14:58:54 -08:00
Waleed
cdacb796a8 improvement(providers): replace @ts-ignore with typed ProviderError class (#3235) 2026-02-17 14:20:31 -08:00
Waleed
3ce54147e6 fix(pagination): add missing next_page to response interfaces and operator comments (#3236) 2026-02-17 14:13:45 -08:00
Waleed
08690b2906 feat(pagination): update pagination for remaining integrations that support it (#3233)
* feat(pagination): update pagination for remaining integrations that support it

* fixed remaining

* ack comments
2026-02-17 13:34:46 -08:00
Waleed
299cc26694 improvement(lint): fix react-doctor errors and warnings (#3232)
* improvement(lint): fix react-doctor errors and warnings

* remove separators
2026-02-17 11:40:47 -08:00
Emir Karabeg
48715ff013 improvement(copilot): scrolling stickiness (#3218)
- Changed default stickinessThreshold from 100 to 30 in use-scroll-management.ts
- Removed explicit stickinessThreshold override (40) from copilot.tsx
- Both copilot and chat now use the same default value of 30
- This makes scrolling less sticky across all copilot message interactions

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Emir Karabeg <emir-karabeg@users.noreply.github.com>
2026-02-17 10:33:10 -08:00
Waleed
ad0d0ed1f1 feat(shortlink): add Beluga short link rewrite for hosted campaigns (#3231) 2026-02-17 10:32:32 -08:00
Waleed
b7e377ec4b v0.5.91: docs i18n, turborepo upgrade 2026-02-16 00:36:05 -08:00
78 changed files with 952 additions and 729 deletions

View File

@@ -59,12 +59,6 @@ body {
--content-gap: 1.75rem; --content-gap: 1.75rem;
} }
/* Remove custom layout variable overrides to fallback to fumadocs defaults */
/* ============================================
Navbar Light Mode Styling
============================================ */
/* Light mode navbar and search styling */ /* Light mode navbar and search styling */
:root:not(.dark) nav { :root:not(.dark) nav {
background-color: hsla(0, 0%, 96%, 0.85) !important; background-color: hsla(0, 0%, 96%, 0.85) !important;
@@ -88,10 +82,6 @@ body {
-webkit-backdrop-filter: blur(25px) saturate(180%) brightness(0.6) !important; -webkit-backdrop-filter: blur(25px) saturate(180%) brightness(0.6) !important;
} }
/* ============================================
Custom Sidebar Styling (Turborepo-inspired)
============================================ */
/* Floating sidebar appearance - remove background */ /* Floating sidebar appearance - remove background */
[data-sidebar-container], [data-sidebar-container],
#nd-sidebar { #nd-sidebar {
@@ -468,10 +458,6 @@ aside[data-sidebar],
writing-mode: horizontal-tb !important; writing-mode: horizontal-tb !important;
} }
/* ============================================
Code Block Styling (Improved)
============================================ */
/* Apply Geist Mono to code elements */ /* Apply Geist Mono to code elements */
code, code,
pre, pre,
@@ -532,10 +518,6 @@ pre code .line {
color: var(--color-fd-primary); color: var(--color-fd-primary);
} }
/* ============================================
TOC (Table of Contents) Styling
============================================ */
/* Remove the thin border-left on nested TOC items (keeps main indicator only) */ /* Remove the thin border-left on nested TOC items (keeps main indicator only) */
#nd-toc a[style*="padding-inline-start"] { #nd-toc a[style*="padding-inline-start"] {
border-left: none !important; border-left: none !important;
@@ -554,10 +536,6 @@ main article,
padding-bottom: 4rem; padding-bottom: 4rem;
} }
/* ============================================
Center and Constrain Main Content Width
============================================ */
/* Main content area - center and constrain like turborepo/raindrop */ /* Main content area - center and constrain like turborepo/raindrop */
/* Note: --sidebar-offset and --toc-offset are now applied at #nd-docs-layout level */ /* Note: --sidebar-offset and --toc-offset are now applied at #nd-docs-layout level */
main[data-main] { main[data-main] {

View File

@@ -234,7 +234,6 @@ List actions from incident.io. Optionally filter by incident ID.
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | incident.io API Key | | `apiKey` | string | Yes | incident.io API Key |
| `incident_id` | string | No | Filter actions by incident ID \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) | | `incident_id` | string | No | Filter actions by incident ID \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) |
| `page_size` | number | No | Number of actions to return per page \(e.g., 10, 25, 50\) |
#### Output #### Output
@@ -309,7 +308,6 @@ List follow-ups from incident.io. Optionally filter by incident ID.
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | incident.io API Key | | `apiKey` | string | Yes | incident.io API Key |
| `incident_id` | string | No | Filter follow-ups by incident ID \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) | | `incident_id` | string | No | Filter follow-ups by incident ID \(e.g., "01FCNDV6P870EA6S7TK1DSYDG0"\) |
| `page_size` | number | No | Number of follow-ups to return per page \(e.g., 10, 25, 50\) |
#### Output #### Output
@@ -396,6 +394,7 @@ List all users in your Incident.io workspace. Returns user details including id,
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Incident.io API Key | | `apiKey` | string | Yes | Incident.io API Key |
| `page_size` | number | No | Number of results to return per page \(e.g., 10, 25, 50\). Default: 25 | | `page_size` | number | No | Number of results to return per page \(e.g., 10, 25, 50\). Default: 25 |
| `after` | string | No | Pagination cursor to fetch the next page of results |
#### Output #### Output
@@ -406,6 +405,10 @@ List all users in your Incident.io workspace. Returns user details including id,
| ↳ `name` | string | Full name of the user | | ↳ `name` | string | Full name of the user |
| ↳ `email` | string | Email address of the user | | ↳ `email` | string | Email address of the user |
| ↳ `role` | string | Role of the user in the workspace | | ↳ `role` | string | Role of the user in the workspace |
| `pagination_meta` | object | Pagination metadata |
| ↳ `after` | string | Cursor for next page |
| ↳ `page_size` | number | Number of items per page |
| ↳ `total_record_count` | number | Total number of records |
### `incidentio_users_show` ### `incidentio_users_show`
@@ -644,7 +647,6 @@ List all escalation policies in incident.io
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | incident.io API Key | | `apiKey` | string | Yes | incident.io API Key |
| `page_size` | number | No | Number of results per page \(e.g., 10, 25, 50\). Default: 25 |
#### Output #### Output

View File

@@ -49,6 +49,7 @@ Retrieve all deals from Pipedrive with optional filters
| `pipeline_id` | string | No | If supplied, only deals in the specified pipeline are returned \(e.g., "1"\) | | `pipeline_id` | string | No | If supplied, only deals in the specified pipeline are returned \(e.g., "1"\) |
| `updated_since` | string | No | If set, only deals updated after this time are returned. Format: 2025-01-01T10:20:00Z | | `updated_since` | string | No | If set, only deals updated after this time are returned. Format: 2025-01-01T10:20:00Z |
| `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 500\) | | `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 500\) |
| `cursor` | string | No | For pagination, the marker representing the first item on the next page |
#### Output #### Output
@@ -74,6 +75,8 @@ Retrieve all deals from Pipedrive with optional filters
| `metadata` | object | Pagination metadata for the response | | `metadata` | object | Pagination metadata for the response |
| ↳ `total_items` | number | Total number of items | | ↳ `total_items` | number | Total number of items |
| ↳ `has_more` | boolean | Whether more items are available | | ↳ `has_more` | boolean | Whether more items are available |
| ↳ `next_cursor` | string | Cursor for fetching the next page \(v2 endpoints\) |
| ↳ `next_start` | number | Offset for fetching the next page \(v1 endpoints\) |
| `success` | boolean | Operation success status | | `success` | boolean | Operation success status |
### `pipedrive_get_deal` ### `pipedrive_get_deal`
@@ -148,10 +151,9 @@ Retrieve files from Pipedrive with optional filters
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `deal_id` | string | No | Filter files by deal ID \(e.g., "123"\) | | `sort` | string | No | Sort files by field \(supported: "id", "update_time"\) |
| `person_id` | string | No | Filter files by person ID \(e.g., "456"\) | | `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 100\) |
| `org_id` | string | No | Filter files by organization ID \(e.g., "789"\) | | `start` | string | No | Pagination start offset \(0-based index of the first item to return\) |
| `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 500\) |
| `downloadFiles` | boolean | No | Download file contents into file outputs | | `downloadFiles` | boolean | No | Download file contents into file outputs |
#### Output #### Output
@@ -171,6 +173,8 @@ Retrieve files from Pipedrive with optional filters
| ↳ `url` | string | File download URL | | ↳ `url` | string | File download URL |
| `downloadedFiles` | file[] | Downloaded files from Pipedrive | | `downloadedFiles` | file[] | Downloaded files from Pipedrive |
| `total_items` | number | Total number of files returned | | `total_items` | number | Total number of files returned |
| `has_more` | boolean | Whether more files are available |
| `next_start` | number | Offset for fetching the next page |
| `success` | boolean | Operation success status | | `success` | boolean | Operation success status |
### `pipedrive_get_mail_messages` ### `pipedrive_get_mail_messages`
@@ -183,6 +187,7 @@ Retrieve mail threads from Pipedrive mailbox
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `folder` | string | No | Filter by folder: inbox, drafts, sent, archive \(default: inbox\) | | `folder` | string | No | Filter by folder: inbox, drafts, sent, archive \(default: inbox\) |
| `limit` | string | No | Number of results to return \(e.g., "25", default: 50\) | | `limit` | string | No | Number of results to return \(e.g., "25", default: 50\) |
| `start` | string | No | Pagination start offset \(0-based index of the first item to return\) |
#### Output #### Output
@@ -190,6 +195,8 @@ Retrieve mail threads from Pipedrive mailbox
| --------- | ---- | ----------- | | --------- | ---- | ----------- |
| `messages` | array | Array of mail thread objects from Pipedrive mailbox | | `messages` | array | Array of mail thread objects from Pipedrive mailbox |
| `total_items` | number | Total number of mail threads returned | | `total_items` | number | Total number of mail threads returned |
| `has_more` | boolean | Whether more messages are available |
| `next_start` | number | Offset for fetching the next page |
| `success` | boolean | Operation success status | | `success` | boolean | Operation success status |
### `pipedrive_get_mail_thread` ### `pipedrive_get_mail_thread`
@@ -221,7 +228,7 @@ Retrieve all pipelines from Pipedrive
| `sort_by` | string | No | Field to sort by: id, update_time, add_time \(default: id\) | | `sort_by` | string | No | Field to sort by: id, update_time, add_time \(default: id\) |
| `sort_direction` | string | No | Sorting direction: asc, desc \(default: asc\) | | `sort_direction` | string | No | Sorting direction: asc, desc \(default: asc\) |
| `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 500\) | | `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 500\) |
| `cursor` | string | No | For pagination, the marker representing the first item on the next page | | `start` | string | No | Pagination start offset \(0-based index of the first item to return\) |
#### Output #### Output
@@ -237,6 +244,8 @@ Retrieve all pipelines from Pipedrive
| ↳ `add_time` | string | When the pipeline was created | | ↳ `add_time` | string | When the pipeline was created |
| ↳ `update_time` | string | When the pipeline was last updated | | ↳ `update_time` | string | When the pipeline was last updated |
| `total_items` | number | Total number of pipelines returned | | `total_items` | number | Total number of pipelines returned |
| `has_more` | boolean | Whether more pipelines are available |
| `next_start` | number | Offset for fetching the next page |
| `success` | boolean | Operation success status | | `success` | boolean | Operation success status |
### `pipedrive_get_pipeline_deals` ### `pipedrive_get_pipeline_deals`
@@ -249,8 +258,8 @@ Retrieve all deals in a specific pipeline
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `pipeline_id` | string | Yes | The ID of the pipeline \(e.g., "1"\) | | `pipeline_id` | string | Yes | The ID of the pipeline \(e.g., "1"\) |
| `stage_id` | string | No | Filter by specific stage within the pipeline \(e.g., "2"\) | | `stage_id` | string | No | Filter by specific stage within the pipeline \(e.g., "2"\) |
| `status` | string | No | Filter by deal status: open, won, lost |
| `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 500\) | | `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 500\) |
| `start` | string | No | Pagination start offset \(0-based index of the first item to return\) |
#### Output #### Output
@@ -271,6 +280,7 @@ Retrieve all projects or a specific project from Pipedrive
| `project_id` | string | No | Optional: ID of a specific project to retrieve \(e.g., "123"\) | | `project_id` | string | No | Optional: ID of a specific project to retrieve \(e.g., "123"\) |
| `status` | string | No | Filter by project status: open, completed, deleted \(only for listing all\) | | `status` | string | No | Filter by project status: open, completed, deleted \(only for listing all\) |
| `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 500, only for listing all\) | | `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 500, only for listing all\) |
| `cursor` | string | No | For pagination, the marker representing the first item on the next page |
#### Output #### Output
@@ -279,6 +289,8 @@ Retrieve all projects or a specific project from Pipedrive
| `projects` | array | Array of project objects \(when listing all\) | | `projects` | array | Array of project objects \(when listing all\) |
| `project` | object | Single project object \(when project_id is provided\) | | `project` | object | Single project object \(when project_id is provided\) |
| `total_items` | number | Total number of projects returned | | `total_items` | number | Total number of projects returned |
| `has_more` | boolean | Whether more projects are available |
| `next_cursor` | string | Cursor for fetching the next page |
| `success` | boolean | Operation success status | | `success` | boolean | Operation success status |
### `pipedrive_create_project` ### `pipedrive_create_project`
@@ -309,12 +321,11 @@ Retrieve activities (tasks) from Pipedrive with optional filters
| Parameter | Type | Required | Description | | Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `deal_id` | string | No | Filter activities by deal ID \(e.g., "123"\) | | `user_id` | string | No | Filter activities by user ID \(e.g., "123"\) |
| `person_id` | string | No | Filter activities by person ID \(e.g., "456"\) |
| `org_id` | string | No | Filter activities by organization ID \(e.g., "789"\) |
| `type` | string | No | Filter by activity type \(call, meeting, task, deadline, email, lunch\) | | `type` | string | No | Filter by activity type \(call, meeting, task, deadline, email, lunch\) |
| `done` | string | No | Filter by completion status: 0 for not done, 1 for done | | `done` | string | No | Filter by completion status: 0 for not done, 1 for done |
| `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 500\) | | `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 500\) |
| `start` | string | No | Pagination start offset \(0-based index of the first item to return\) |
#### Output #### Output
@@ -335,6 +346,8 @@ Retrieve activities (tasks) from Pipedrive with optional filters
| ↳ `add_time` | string | When the activity was created | | ↳ `add_time` | string | When the activity was created |
| ↳ `update_time` | string | When the activity was last updated | | ↳ `update_time` | string | When the activity was last updated |
| `total_items` | number | Total number of activities returned | | `total_items` | number | Total number of activities returned |
| `has_more` | boolean | Whether more activities are available |
| `next_start` | number | Offset for fetching the next page |
| `success` | boolean | Operation success status | | `success` | boolean | Operation success status |
### `pipedrive_create_activity` ### `pipedrive_create_activity`
@@ -399,6 +412,7 @@ Retrieve all leads or a specific lead from Pipedrive
| `person_id` | string | No | Filter by person ID \(e.g., "456"\) | | `person_id` | string | No | Filter by person ID \(e.g., "456"\) |
| `organization_id` | string | No | Filter by organization ID \(e.g., "789"\) | | `organization_id` | string | No | Filter by organization ID \(e.g., "789"\) |
| `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 500\) | | `limit` | string | No | Number of results to return \(e.g., "50", default: 100, max: 500\) |
| `start` | string | No | Pagination start offset \(0-based index of the first item to return\) |
#### Output #### Output
@@ -433,6 +447,8 @@ Retrieve all leads or a specific lead from Pipedrive
| ↳ `add_time` | string | When the lead was created \(ISO 8601\) | | ↳ `add_time` | string | When the lead was created \(ISO 8601\) |
| ↳ `update_time` | string | When the lead was last updated \(ISO 8601\) | | ↳ `update_time` | string | When the lead was last updated \(ISO 8601\) |
| `total_items` | number | Total number of leads returned | | `total_items` | number | Total number of leads returned |
| `has_more` | boolean | Whether more leads are available |
| `next_start` | number | Offset for fetching the next page |
| `success` | boolean | Operation success status | | `success` | boolean | Operation success status |
### `pipedrive_create_lead` ### `pipedrive_create_lead`

View File

@@ -57,6 +57,7 @@ Query data from a Supabase table
| `filter` | string | No | PostgREST filter \(e.g., "id=eq.123"\) | | `filter` | string | No | PostgREST filter \(e.g., "id=eq.123"\) |
| `orderBy` | string | No | Column to order by \(add DESC for descending\) | | `orderBy` | string | No | Column to order by \(add DESC for descending\) |
| `limit` | number | No | Maximum number of rows to return | | `limit` | number | No | Maximum number of rows to return |
| `offset` | number | No | Number of rows to skip \(for pagination\) |
| `apiKey` | string | Yes | Your Supabase service role secret key | | `apiKey` | string | Yes | Your Supabase service role secret key |
#### Output #### Output
@@ -211,6 +212,7 @@ Perform full-text search on a Supabase table
| `searchType` | string | No | Search type: plain, phrase, or websearch \(default: websearch\) | | `searchType` | string | No | Search type: plain, phrase, or websearch \(default: websearch\) |
| `language` | string | No | Language for text search configuration \(default: english\) | | `language` | string | No | Language for text search configuration \(default: english\) |
| `limit` | number | No | Maximum number of rows to return | | `limit` | number | No | Maximum number of rows to return |
| `offset` | number | No | Number of rows to skip \(for pagination\) |
| `apiKey` | string | Yes | Your Supabase service role secret key | | `apiKey` | string | Yes | Your Supabase service role secret key |
#### Output #### Output

View File

@@ -43,6 +43,8 @@ Retrieve form responses from Typeform
| `formId` | string | Yes | Typeform form ID \(e.g., "abc123XYZ"\) | | `formId` | string | Yes | Typeform form ID \(e.g., "abc123XYZ"\) |
| `apiKey` | string | Yes | Typeform Personal Access Token | | `apiKey` | string | Yes | Typeform Personal Access Token |
| `pageSize` | number | No | Number of responses to retrieve \(e.g., 10, 25, 50\) | | `pageSize` | number | No | Number of responses to retrieve \(e.g., 10, 25, 50\) |
| `before` | string | No | Cursor token for fetching the next page of older responses |
| `after` | string | No | Cursor token for fetching the next page of newer responses |
| `since` | string | No | Retrieve responses submitted after this date \(e.g., "2024-01-01T00:00:00Z"\) | | `since` | string | No | Retrieve responses submitted after this date \(e.g., "2024-01-01T00:00:00Z"\) |
| `until` | string | No | Retrieve responses submitted before this date \(e.g., "2024-12-31T23:59:59Z"\) | | `until` | string | No | Retrieve responses submitted before this date \(e.g., "2024-12-31T23:59:59Z"\) |
| `completed` | string | No | Filter by completion status \(e.g., "true", "false", "all"\) | | `completed` | string | No | Filter by completion status \(e.g., "true", "false", "all"\) |

View File

@@ -67,10 +67,9 @@ Retrieve a list of tickets from Zendesk with optional filtering
| `type` | string | No | Filter by type: "problem", "incident", "question", or "task" | | `type` | string | No | Filter by type: "problem", "incident", "question", or "task" |
| `assigneeId` | string | No | Filter by assignee user ID as a numeric string \(e.g., "12345"\) | | `assigneeId` | string | No | Filter by assignee user ID as a numeric string \(e.g., "12345"\) |
| `organizationId` | string | No | Filter by organization ID as a numeric string \(e.g., "67890"\) | | `organizationId` | string | No | Filter by organization ID as a numeric string \(e.g., "67890"\) |
| `sortBy` | string | No | Sort field: "created_at", "updated_at", "priority", or "status" | | `sort` | string | No | Sort field for ticket listing \(only applies without filters\): "updated_at", "id", or "status". Prefix with "-" for descending \(e.g., "-updated_at"\) |
| `sortOrder` | string | No | Sort order: "asc" or "desc" |
| `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) | | `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) |
| `page` | string | No | Page number as a string \(e.g., "1", "2"\) | | `pageAfter` | string | No | Cursor from a previous response to fetch the next page of results |
#### Output #### Output
@@ -129,10 +128,10 @@ Retrieve a list of tickets from Zendesk with optional filtering
| ↳ `from_messaging_channel` | boolean | Whether the ticket originated from a messaging channel | | ↳ `from_messaging_channel` | boolean | Whether the ticket originated from a messaging channel |
| ↳ `ticket_form_id` | number | Ticket form ID | | ↳ `ticket_form_id` | number | Ticket form ID |
| ↳ `generated_timestamp` | number | Unix timestamp of the ticket generation | | ↳ `generated_timestamp` | number | Unix timestamp of the ticket generation |
| `paging` | object | Pagination information | | `paging` | object | Cursor-based pagination information |
| ↳ `after_cursor` | string | Cursor for fetching the next page of results |
| ↳ `has_more` | boolean | Whether more results are available |
| ↳ `next_page` | string | URL for next page of results | | ↳ `next_page` | string | URL for next page of results |
| ↳ `previous_page` | string | URL for previous page of results |
| ↳ `count` | number | Total count of items |
| `metadata` | object | Response metadata | | `metadata` | object | Response metadata |
| ↳ `total_returned` | number | Number of items returned in this response | | ↳ `total_returned` | number | Number of items returned in this response |
| ↳ `has_more` | boolean | Whether more items are available | | ↳ `has_more` | boolean | Whether more items are available |
@@ -515,7 +514,7 @@ Retrieve a list of users from Zendesk with optional filtering
| `role` | string | No | Filter by role: "end-user", "agent", or "admin" | | `role` | string | No | Filter by role: "end-user", "agent", or "admin" |
| `permissionSet` | string | No | Filter by permission set ID as a numeric string \(e.g., "12345"\) | | `permissionSet` | string | No | Filter by permission set ID as a numeric string \(e.g., "12345"\) |
| `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) | | `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) |
| `page` | string | No | Page number as a string \(e.g., "1", "2"\) | | `pageAfter` | string | No | Cursor from a previous response to fetch the next page of results |
#### Output #### Output
@@ -563,10 +562,10 @@ Retrieve a list of users from Zendesk with optional filtering
| ↳ `shared` | boolean | Whether the user is shared from a different Zendesk | | ↳ `shared` | boolean | Whether the user is shared from a different Zendesk |
| ↳ `shared_agent` | boolean | Whether the agent is shared from a different Zendesk | | ↳ `shared_agent` | boolean | Whether the agent is shared from a different Zendesk |
| ↳ `remote_photo_url` | string | URL to a remote photo | | ↳ `remote_photo_url` | string | URL to a remote photo |
| `paging` | object | Pagination information | | `paging` | object | Cursor-based pagination information |
| ↳ `after_cursor` | string | Cursor for fetching the next page of results |
| ↳ `has_more` | boolean | Whether more results are available |
| ↳ `next_page` | string | URL for next page of results | | ↳ `next_page` | string | URL for next page of results |
| ↳ `previous_page` | string | URL for previous page of results |
| ↳ `count` | number | Total count of items |
| `metadata` | object | Response metadata | | `metadata` | object | Response metadata |
| ↳ `total_returned` | number | Number of items returned in this response | | ↳ `total_returned` | number | Number of items returned in this response |
| ↳ `has_more` | boolean | Whether more items are available | | ↳ `has_more` | boolean | Whether more items are available |
@@ -706,7 +705,7 @@ Search for users in Zendesk using a query string
| `query` | string | No | Search query string \(e.g., user name or email\) | | `query` | string | No | Search query string \(e.g., user name or email\) |
| `externalId` | string | No | External ID to search by \(your system identifier\) | | `externalId` | string | No | External ID to search by \(your system identifier\) |
| `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) | | `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) |
| `page` | string | No | Page number as a string \(e.g., "1", "2"\) | | `page` | string | No | Page number for pagination \(1-based\) |
#### Output #### Output
@@ -754,10 +753,10 @@ Search for users in Zendesk using a query string
| ↳ `shared` | boolean | Whether the user is shared from a different Zendesk | | ↳ `shared` | boolean | Whether the user is shared from a different Zendesk |
| ↳ `shared_agent` | boolean | Whether the agent is shared from a different Zendesk | | ↳ `shared_agent` | boolean | Whether the agent is shared from a different Zendesk |
| ↳ `remote_photo_url` | string | URL to a remote photo | | ↳ `remote_photo_url` | string | URL to a remote photo |
| `paging` | object | Pagination information | | `paging` | object | Cursor-based pagination information |
| ↳ `after_cursor` | string | Cursor for fetching the next page of results |
| ↳ `has_more` | boolean | Whether more results are available |
| ↳ `next_page` | string | URL for next page of results | | ↳ `next_page` | string | URL for next page of results |
| ↳ `previous_page` | string | URL for previous page of results |
| ↳ `count` | number | Total count of items |
| `metadata` | object | Response metadata | | `metadata` | object | Response metadata |
| ↳ `total_returned` | number | Number of items returned in this response | | ↳ `total_returned` | number | Number of items returned in this response |
| ↳ `has_more` | boolean | Whether more items are available | | ↳ `has_more` | boolean | Whether more items are available |
@@ -999,7 +998,7 @@ Retrieve a list of organizations from Zendesk
| `apiToken` | string | Yes | Zendesk API token | | `apiToken` | string | Yes | Zendesk API token |
| `subdomain` | string | Yes | Your Zendesk subdomain \(e.g., "mycompany" for mycompany.zendesk.com\) | | `subdomain` | string | Yes | Your Zendesk subdomain \(e.g., "mycompany" for mycompany.zendesk.com\) |
| `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) | | `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) |
| `page` | string | No | Page number as a string \(e.g., "1", "2"\) | | `pageAfter` | string | No | Cursor from a previous response to fetch the next page of results |
#### Output #### Output
@@ -1020,10 +1019,10 @@ Retrieve a list of organizations from Zendesk
| ↳ `created_at` | string | When the organization was created \(ISO 8601 format\) | | ↳ `created_at` | string | When the organization was created \(ISO 8601 format\) |
| ↳ `updated_at` | string | When the organization was last updated \(ISO 8601 format\) | | ↳ `updated_at` | string | When the organization was last updated \(ISO 8601 format\) |
| ↳ `external_id` | string | External ID for linking to external records | | ↳ `external_id` | string | External ID for linking to external records |
| `paging` | object | Pagination information | | `paging` | object | Cursor-based pagination information |
| ↳ `after_cursor` | string | Cursor for fetching the next page of results |
| ↳ `has_more` | boolean | Whether more results are available |
| ↳ `next_page` | string | URL for next page of results | | ↳ `next_page` | string | URL for next page of results |
| ↳ `previous_page` | string | URL for previous page of results |
| ↳ `count` | number | Total count of items |
| `metadata` | object | Response metadata | | `metadata` | object | Response metadata |
| ↳ `total_returned` | number | Number of items returned in this response | | ↳ `total_returned` | number | Number of items returned in this response |
| ↳ `has_more` | boolean | Whether more items are available | | ↳ `has_more` | boolean | Whether more items are available |
@@ -1075,7 +1074,7 @@ Autocomplete organizations in Zendesk by name prefix (for name matching/autocomp
| `subdomain` | string | Yes | Your Zendesk subdomain | | `subdomain` | string | Yes | Your Zendesk subdomain |
| `name` | string | Yes | Organization name prefix to search for \(e.g., "Acme"\) | | `name` | string | Yes | Organization name prefix to search for \(e.g., "Acme"\) |
| `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) | | `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) |
| `page` | string | No | Page number as a string \(e.g., "1", "2"\) | | `page` | string | No | Page number for pagination \(1-based\) |
#### Output #### Output
@@ -1096,10 +1095,10 @@ Autocomplete organizations in Zendesk by name prefix (for name matching/autocomp
| ↳ `created_at` | string | When the organization was created \(ISO 8601 format\) | | ↳ `created_at` | string | When the organization was created \(ISO 8601 format\) |
| ↳ `updated_at` | string | When the organization was last updated \(ISO 8601 format\) | | ↳ `updated_at` | string | When the organization was last updated \(ISO 8601 format\) |
| ↳ `external_id` | string | External ID for linking to external records | | ↳ `external_id` | string | External ID for linking to external records |
| `paging` | object | Pagination information | | `paging` | object | Cursor-based pagination information |
| ↳ `after_cursor` | string | Cursor for fetching the next page of results |
| ↳ `has_more` | boolean | Whether more results are available |
| ↳ `next_page` | string | URL for next page of results | | ↳ `next_page` | string | URL for next page of results |
| ↳ `previous_page` | string | URL for previous page of results |
| ↳ `count` | number | Total count of items |
| `metadata` | object | Response metadata | | `metadata` | object | Response metadata |
| ↳ `total_returned` | number | Number of items returned in this response | | ↳ `total_returned` | number | Number of items returned in this response |
| ↳ `has_more` | boolean | Whether more items are available | | ↳ `has_more` | boolean | Whether more items are available |
@@ -1249,19 +1248,18 @@ Unified search across tickets, users, and organizations in Zendesk
| `apiToken` | string | Yes | Zendesk API token | | `apiToken` | string | Yes | Zendesk API token |
| `subdomain` | string | Yes | Your Zendesk subdomain | | `subdomain` | string | Yes | Your Zendesk subdomain |
| `query` | string | Yes | Search query string using Zendesk search syntax \(e.g., "type:ticket status:open"\) | | `query` | string | Yes | Search query string using Zendesk search syntax \(e.g., "type:ticket status:open"\) |
| `sortBy` | string | No | Sort field: "relevance", "created_at", "updated_at", "priority", "status", or "ticket_type" | | `filterType` | string | Yes | Resource type to search for: "ticket", "user", "organization", or "group" |
| `sortOrder` | string | No | Sort order: "asc" or "desc" |
| `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) | | `perPage` | string | No | Results per page as a number string \(default: "100", max: "100"\) |
| `page` | string | No | Page number as a string \(e.g., "1", "2"\) | | `pageAfter` | string | No | Cursor from a previous response to fetch the next page of results |
#### Output #### Output
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ---- | ----------- | | --------- | ---- | ----------- |
| `paging` | object | Pagination information | | `paging` | object | Cursor-based pagination information |
| ↳ `after_cursor` | string | Cursor for fetching the next page of results |
| ↳ `has_more` | boolean | Whether more results are available |
| ↳ `next_page` | string | URL for next page of results | | ↳ `next_page` | string | URL for next page of results |
| ↳ `previous_page` | string | URL for previous page of results |
| ↳ `count` | number | Total count of items |
| `metadata` | object | Response metadata | | `metadata` | object | Response metadata |
| ↳ `total_returned` | number | Number of items returned in this response | | ↳ `total_returned` | number | Number of items returned in this response |
| ↳ `has_more` | boolean | Whether more items are available | | ↳ `has_more` | boolean | Whether more items are available |

View File

@@ -1,5 +1,3 @@
'use server'
import { env } from '@/lib/core/config/env' import { env } from '@/lib/core/config/env'
import { isProd } from '@/lib/core/config/feature-flags' import { isProd } from '@/lib/core/config/feature-flags'

View File

@@ -85,7 +85,7 @@ export const LandingNode = React.memo(function LandingNode({ data }: { data: Lan
transform: isAnimated ? 'translateY(0) scale(1)' : 'translateY(8px) scale(0.98)', transform: isAnimated ? 'translateY(0) scale(1)' : 'translateY(8px) scale(0.98)',
transition: transition:
'opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1), transform 0.6s cubic-bezier(0.22, 1, 0.36, 1)', 'opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1), transform 0.6s cubic-bezier(0.22, 1, 0.36, 1)',
willChange: 'transform, opacity', willChange: isAnimated ? 'auto' : 'transform, opacity',
}} }}
> >
<LandingBlock icon={data.icon} color={data.color} name={data.name} tags={data.tags} /> <LandingBlock icon={data.icon} color={data.color} name={data.name} tags={data.tags} />

View File

@@ -67,7 +67,6 @@ export const LandingEdge = React.memo(function LandingEdge(props: EdgeProps) {
strokeLinejoin: 'round', strokeLinejoin: 'round',
pointerEvents: 'none', pointerEvents: 'none',
animation: `landing-edge-dash-${id} 1s linear infinite`, animation: `landing-edge-dash-${id} 1s linear infinite`,
willChange: 'stroke-dashoffset',
...style, ...style,
}} }}
/> />

View File

@@ -754,3 +754,100 @@ input[type="search"]::-ms-clear {
text-decoration: none !important; text-decoration: none !important;
color: inherit !important; color: inherit !important;
} }
/**
* Respect user's prefers-reduced-motion setting (WCAG 2.3.3)
* Disables animations and transitions for users who prefer reduced motion.
*/
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* WandPromptBar status indicator */
@keyframes smoke-pulse {
0%,
100% {
transform: scale(0.8);
opacity: 0.4;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
}
.status-indicator {
position: relative;
width: 12px;
height: 12px;
border-radius: 50%;
overflow: hidden;
background-color: hsl(var(--muted-foreground) / 0.5);
transition: background-color 0.3s ease;
}
.status-indicator.streaming {
background-color: transparent;
}
.status-indicator.streaming::before {
content: "";
position: absolute;
inset: 0;
border-radius: 50%;
background: radial-gradient(
circle,
hsl(var(--primary) / 0.9) 0%,
hsl(var(--primary) / 0.4) 60%,
transparent 80%
);
animation: smoke-pulse 1.8s ease-in-out infinite;
opacity: 0.9;
}
.dark .status-indicator.streaming::before {
background: #6b7280;
opacity: 0.9;
animation: smoke-pulse 1.8s ease-in-out infinite;
}
/* MessageContainer loading dot */
@keyframes growShrink {
0%,
100% {
transform: scale(0.9);
}
50% {
transform: scale(1.1);
}
}
.loading-dot {
animation: growShrink 1.5s infinite ease-in-out;
}
/* Subflow node z-index and drag-over styles */
.workflow-container .react-flow__node-subflowNode {
z-index: -1 !important;
}
.workflow-container .react-flow__node-subflowNode:has([data-subflow-selected="true"]) {
z-index: 10 !important;
}
.loop-node-drag-over,
.parallel-node-drag-over {
box-shadow: 0 0 0 1.75px var(--brand-secondary) !important;
border-radius: 8px !important;
}
.react-flow__node[data-parent-node-id] .react-flow__handle {
z-index: 30;
}

View File

@@ -22,15 +22,20 @@ interface PipedriveFile {
interface PipedriveApiResponse { interface PipedriveApiResponse {
success: boolean success: boolean
data?: PipedriveFile[] data?: PipedriveFile[]
additional_data?: {
pagination?: {
more_items_in_collection: boolean
next_start: number
}
}
error?: string error?: string
} }
const PipedriveGetFilesSchema = z.object({ const PipedriveGetFilesSchema = z.object({
accessToken: z.string().min(1, 'Access token is required'), accessToken: z.string().min(1, 'Access token is required'),
deal_id: z.string().optional().nullable(), sort: z.enum(['id', 'update_time']).optional().nullable(),
person_id: z.string().optional().nullable(),
org_id: z.string().optional().nullable(),
limit: z.string().optional().nullable(), limit: z.string().optional().nullable(),
start: z.string().optional().nullable(),
downloadFiles: z.boolean().optional().default(false), downloadFiles: z.boolean().optional().default(false),
}) })
@@ -54,20 +59,19 @@ export async function POST(request: NextRequest) {
const body = await request.json() const body = await request.json()
const validatedData = PipedriveGetFilesSchema.parse(body) const validatedData = PipedriveGetFilesSchema.parse(body)
const { accessToken, deal_id, person_id, org_id, limit, downloadFiles } = validatedData const { accessToken, sort, limit, start, downloadFiles } = validatedData
const baseUrl = 'https://api.pipedrive.com/v1/files' const baseUrl = 'https://api.pipedrive.com/v1/files'
const queryParams = new URLSearchParams() const queryParams = new URLSearchParams()
if (deal_id) queryParams.append('deal_id', deal_id) if (sort) queryParams.append('sort', sort)
if (person_id) queryParams.append('person_id', person_id)
if (org_id) queryParams.append('org_id', org_id)
if (limit) queryParams.append('limit', limit) if (limit) queryParams.append('limit', limit)
if (start) queryParams.append('start', start)
const queryString = queryParams.toString() const queryString = queryParams.toString()
const apiUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl const apiUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl
logger.info(`[${requestId}] Fetching files from Pipedrive`, { deal_id, person_id, org_id }) logger.info(`[${requestId}] Fetching files from Pipedrive`)
const urlValidation = await validateUrlWithDNS(apiUrl, 'apiUrl') const urlValidation = await validateUrlWithDNS(apiUrl, 'apiUrl')
if (!urlValidation.isValid) { if (!urlValidation.isValid) {
@@ -93,6 +97,8 @@ export async function POST(request: NextRequest) {
} }
const files = data.data || [] const files = data.data || []
const hasMore = data.additional_data?.pagination?.more_items_in_collection || false
const nextStart = data.additional_data?.pagination?.next_start ?? null
const downloadedFiles: Array<{ const downloadedFiles: Array<{
name: string name: string
mimeType: string mimeType: string
@@ -149,6 +155,8 @@ export async function POST(request: NextRequest) {
files, files,
downloadedFiles: downloadedFiles.length > 0 ? downloadedFiles : undefined, downloadedFiles: downloadedFiles.length > 0 ? downloadedFiles : undefined,
total_items: files.length, total_items: files.length,
has_more: hasMore,
next_start: nextStart,
success: true, success: true,
}, },
}) })

View File

@@ -30,21 +30,6 @@ export const ChatMessageContainer = memo(function ChatMessageContainer({
}: ChatMessageContainerProps) { }: ChatMessageContainerProps) {
return ( return (
<div className='relative flex flex-1 flex-col overflow-hidden bg-white'> <div className='relative flex flex-1 flex-col overflow-hidden bg-white'>
<style jsx>{`
@keyframes growShrink {
0%,
100% {
transform: scale(0.9);
}
50% {
transform: scale(1.1);
}
}
.loading-dot {
animation: growShrink 1.5s infinite ease-in-out;
}
`}</style>
{/* Scrollable Messages Area */} {/* Scrollable Messages Area */}
<div <div
ref={messagesContainerRef} ref={messagesContainerRef}

View File

@@ -71,7 +71,7 @@ export function VoiceInterface({
const [state, setState] = useState<'idle' | 'listening' | 'agent_speaking'>('idle') const [state, setState] = useState<'idle' | 'listening' | 'agent_speaking'>('idle')
const [isInitialized, setIsInitialized] = useState(false) const [isInitialized, setIsInitialized] = useState(false)
const [isMuted, setIsMuted] = useState(false) const [isMuted, setIsMuted] = useState(false)
const [audioLevels, setAudioLevels] = useState<number[]>(new Array(200).fill(0)) const [audioLevels, setAudioLevels] = useState<number[]>(() => new Array(200).fill(0))
const [permissionStatus, setPermissionStatus] = useState<'prompt' | 'granted' | 'denied'>( const [permissionStatus, setPermissionStatus] = useState<'prompt' | 'granted' | 'denied'>(
'prompt' 'prompt'
) )

View File

@@ -1,4 +1,4 @@
import { redirect } from 'next/navigation' import { redirect, unstable_rethrow } from 'next/navigation'
import { getSession } from '@/lib/auth' import { getSession } from '@/lib/auth'
import { getWorkspaceFile } from '@/lib/uploads/contexts/workspace' import { getWorkspaceFile } from '@/lib/uploads/contexts/workspace'
import { verifyWorkspaceMembership } from '@/app/api/workflows/utils' import { verifyWorkspaceMembership } from '@/app/api/workflows/utils'
@@ -14,7 +14,6 @@ interface FileViewerPageProps {
export default async function FileViewerPage({ params }: FileViewerPageProps) { export default async function FileViewerPage({ params }: FileViewerPageProps) {
const { workspaceId, fileId } = await params const { workspaceId, fileId } = await params
try {
const session = await getSession() const session = await getSession()
if (!session?.user?.id) { if (!session?.user?.id) {
redirect('/') redirect('/')
@@ -25,13 +24,17 @@ export default async function FileViewerPage({ params }: FileViewerPageProps) {
redirect(`/workspace/${workspaceId}`) redirect(`/workspace/${workspaceId}`)
} }
const fileRecord = await getWorkspaceFile(workspaceId, fileId) let fileRecord: Awaited<ReturnType<typeof getWorkspaceFile>>
try {
fileRecord = await getWorkspaceFile(workspaceId, fileId)
} catch (error) {
unstable_rethrow(error)
redirect(`/workspace/${workspaceId}`)
}
if (!fileRecord) { if (!fileRecord) {
redirect(`/workspace/${workspaceId}`) redirect(`/workspace/${workspaceId}`)
} }
return <FileViewer file={fileRecord} /> return <FileViewer file={fileRecord} />
} catch (error) {
redirect(`/workspace/${workspaceId}`)
}
} }

View File

@@ -131,10 +131,8 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(({ panelWidth }, ref
resumeActiveStream, resumeActiveStream,
}) })
// Handle scroll management (80px stickiness for copilot) // Handle scroll management
const { scrollAreaRef, scrollToBottom } = useScrollManagement(messages, isSendingMessage, { const { scrollAreaRef, scrollToBottom } = useScrollManagement(messages, isSendingMessage)
stickinessThreshold: 40,
})
// Handle chat history grouping // Handle chat history grouping
const { groupedChats, handleHistoryDropdownOpen: handleHistoryDropdownOpenHook } = useChatHistory( const { groupedChats, handleHistoryDropdownOpen: handleHistoryDropdownOpenHook } = useChatHistory(

View File

@@ -1,5 +1,5 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { isEqual } from 'lodash' import isEqual from 'lodash/isEqual'
import { useReactFlow } from 'reactflow' import { useReactFlow } from 'reactflow'
import { useStoreWithEqualityFn } from 'zustand/traditional' import { useStoreWithEqualityFn } from 'zustand/traditional'
import { Combobox, type ComboboxOption } from '@/components/emcn/components' import { Combobox, type ComboboxOption } from '@/components/emcn/components'

View File

@@ -1,5 +1,5 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { isEqual } from 'lodash' import isEqual from 'lodash/isEqual'
import { useStoreWithEqualityFn } from 'zustand/traditional' import { useStoreWithEqualityFn } from 'zustand/traditional'
import { Badge } from '@/components/emcn' import { Badge } from '@/components/emcn'
import { Combobox, type ComboboxOption } from '@/components/emcn/components' import { Combobox, type ComboboxOption } from '@/components/emcn/components'

View File

@@ -7,7 +7,7 @@ import {
useRef, useRef,
useState, useState,
} from 'react' } from 'react'
import { isEqual } from 'lodash' import isEqual from 'lodash/isEqual'
import { ChevronDown, ChevronsUpDown, ChevronUp, Plus } from 'lucide-react' import { ChevronDown, ChevronsUpDown, ChevronUp, Plus } from 'lucide-react'
import { Button, Popover, PopoverContent, PopoverItem, PopoverTrigger } from '@/components/emcn' import { Button, Popover, PopoverContent, PopoverItem, PopoverTrigger } from '@/components/emcn'
import { Trash } from '@/components/emcn/icons/trash' import { Trash } from '@/components/emcn/icons/trash'

View File

@@ -1,5 +1,5 @@
import { type JSX, type MouseEvent, memo, useCallback, useRef, useState } from 'react' import { type JSX, type MouseEvent, memo, useCallback, useRef, useState } from 'react'
import { isEqual } from 'lodash' import isEqual from 'lodash/isEqual'
import { AlertTriangle, ArrowLeftRight, ArrowUp, Check, Clipboard } from 'lucide-react' import { AlertTriangle, ArrowLeftRight, ArrowUp, Check, Clipboard } from 'lucide-react'
import { Button, Input, Label, Tooltip } from '@/components/emcn/components' import { Button, Input, Label, Tooltip } from '@/components/emcn/components'
import { cn } from '@/lib/core/utils/cn' import { cn } from '@/lib/core/utils/cn'

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { isEqual } from 'lodash' import isEqual from 'lodash/isEqual'
import { import {
BookOpen, BookOpen,
Check, Check,

View File

@@ -10,40 +10,6 @@ import { ActionBar } from '@/app/workspace/[workspaceId]/w/[workflowId]/componen
import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks' import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks'
import { usePanelEditorStore } from '@/stores/panel' import { usePanelEditorStore } from '@/stores/panel'
/**
* Global styles for subflow nodes (loop and parallel containers).
* Includes animations for drag-over states and hover effects.
*
* @returns Style component with global CSS
*/
const SubflowNodeStyles: React.FC = () => {
return (
<style jsx global>{`
/* Z-index management for subflow nodes - default behind blocks */
.workflow-container .react-flow__node-subflowNode {
z-index: -1 !important;
}
/* Selected subflows appear above other subflows but below blocks (z-21) */
.workflow-container .react-flow__node-subflowNode:has([data-subflow-selected='true']) {
z-index: 10 !important;
}
/* Drag-over states */
.loop-node-drag-over,
.parallel-node-drag-over {
box-shadow: 0 0 0 1.75px var(--brand-secondary) !important;
border-radius: 8px !important;
}
/* Handle z-index for nested nodes */
.react-flow__node[data-parent-node-id] .react-flow__handle {
z-index: 30;
}
`}</style>
)
}
/** /**
* Data structure for subflow nodes (loop and parallel containers) * Data structure for subflow nodes (loop and parallel containers)
*/ */
@@ -151,8 +117,6 @@ export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps<Subf
) )
return ( return (
<>
<SubflowNodeStyles />
<div className='group relative'> <div className='group relative'>
<div <div
ref={blockRef} ref={blockRef}
@@ -277,7 +241,6 @@ export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps<Subf
)} )}
</div> </div>
</div> </div>
</>
) )
}) })

View File

@@ -134,57 +134,6 @@ export function WandPromptBar({
</Button> </Button>
)} )}
</div> </div>
<style jsx global>{`
@keyframes smoke-pulse {
0%,
100% {
transform: scale(0.8);
opacity: 0.4;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
}
.status-indicator {
position: relative;
width: 12px;
height: 12px;
border-radius: 50%;
overflow: hidden;
background-color: hsl(var(--muted-foreground) / 0.5);
transition: background-color 0.3s ease;
}
.status-indicator.streaming {
background-color: transparent;
}
.status-indicator.streaming::before {
content: '';
position: absolute;
inset: 0;
border-radius: 50%;
background: radial-gradient(
circle,
hsl(var(--primary) / 0.9) 0%,
hsl(var(--primary) / 0.4) 60%,
transparent 80%
);
animation: smoke-pulse 1.8s ease-in-out infinite;
opacity: 0.9;
}
.dark .status-indicator.streaming::before {
background: #6b7280;
opacity: 0.9;
animation: smoke-pulse 1.8s ease-in-out infinite;
}
`}</style>
</div> </div>
) )
} }

View File

@@ -1,6 +1,6 @@
import { memo, useCallback, useEffect, useMemo, useRef } from 'react' import { memo, useCallback, useEffect, useMemo, useRef } from 'react'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { isEqual } from 'lodash' import isEqual from 'lodash/isEqual'
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
import { Handle, type NodeProps, Position, useUpdateNodeInternals } from 'reactflow' import { Handle, type NodeProps, Position, useUpdateNodeInternals } from 'reactflow'
import { useStoreWithEqualityFn } from 'zustand/traditional' import { useStoreWithEqualityFn } from 'zustand/traditional'

View File

@@ -16,7 +16,7 @@ interface UseScrollManagementOptions {
/** /**
* Distance from bottom (in pixels) within which auto-scroll stays active * Distance from bottom (in pixels) within which auto-scroll stays active
* @remarks Lower values = less sticky (user can scroll away easier) * @remarks Lower values = less sticky (user can scroll away easier)
* @defaultValue 100 * @defaultValue 30
*/ */
stickinessThreshold?: number stickinessThreshold?: number
} }
@@ -41,7 +41,7 @@ export function useScrollManagement(
const lastScrollTopRef = useRef(0) const lastScrollTopRef = useRef(0)
const scrollBehavior = options?.behavior ?? 'smooth' const scrollBehavior = options?.behavior ?? 'smooth'
const stickinessThreshold = options?.stickinessThreshold ?? 100 const stickinessThreshold = options?.stickinessThreshold ?? 30
/** Scrolls the container to the bottom */ /** Scrolls the container to the bottom */
const scrollToBottom = useCallback(() => { const scrollToBottom = useCallback(() => {

View File

@@ -514,6 +514,7 @@ export function HelpModal({ open, onOpenChange, workflowId, workspaceId }: HelpM
alt={`Preview ${index + 1}`} alt={`Preview ${index + 1}`}
fill fill
unoptimized unoptimized
sizes='(max-width: 768px) 100vw, 50vw'
className='object-contain' className='object-contain'
/> />
<button <button

View File

@@ -165,12 +165,16 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub
logger.info('Subscription restored successfully', result) logger.info('Subscription restored successfully', result)
} }
await queryClient.invalidateQueries({ queryKey: subscriptionKeys.all }) await Promise.all([
if (activeOrgId) { queryClient.invalidateQueries({ queryKey: subscriptionKeys.all }),
await queryClient.invalidateQueries({ queryKey: organizationKeys.detail(activeOrgId) }) ...(activeOrgId
await queryClient.invalidateQueries({ queryKey: organizationKeys.billing(activeOrgId) }) ? [
await queryClient.invalidateQueries({ queryKey: organizationKeys.lists() }) queryClient.invalidateQueries({ queryKey: organizationKeys.detail(activeOrgId) }),
} queryClient.invalidateQueries({ queryKey: organizationKeys.billing(activeOrgId) }),
queryClient.invalidateQueries({ queryKey: organizationKeys.lists() }),
]
: []),
])
setIsDialogOpen(false) setIsDialogOpen(false)
} catch (err) { } catch (err) {

View File

@@ -37,7 +37,7 @@ export const UsageLimit = forwardRef<UsageLimitRef, UsageLimitProps>(
}, },
ref ref
) => { ) => {
const [inputValue, setInputValue] = useState(currentLimit.toString()) const [inputValue, setInputValue] = useState(() => currentLimit.toString())
const [hasError, setHasError] = useState(false) const [hasError, setHasError] = useState(false)
const [errorType, setErrorType] = useState<'general' | 'belowUsage' | null>(null) const [errorType, setErrorType] = useState<'general' | 'belowUsage' | null>(null)
const [isEditing, setIsEditing] = useState(false) const [isEditing, setIsEditing] = useState(false)

View File

@@ -92,12 +92,9 @@ export const IncidentioBlock: BlockConfig<IncidentioResponse> = {
field: 'operation', field: 'operation',
value: [ value: [
'incidentio_incidents_list', 'incidentio_incidents_list',
'incidentio_actions_list',
'incidentio_follow_ups_list',
'incidentio_users_list', 'incidentio_users_list',
'incidentio_workflows_list', 'incidentio_workflows_list',
'incidentio_schedules_list', 'incidentio_schedules_list',
'incidentio_escalations_list',
'incidentio_incident_updates_list', 'incidentio_incident_updates_list',
'incidentio_schedule_entries_list', 'incidentio_schedule_entries_list',
], ],
@@ -113,6 +110,7 @@ export const IncidentioBlock: BlockConfig<IncidentioResponse> = {
field: 'operation', field: 'operation',
value: [ value: [
'incidentio_incidents_list', 'incidentio_incidents_list',
'incidentio_users_list',
'incidentio_workflows_list', 'incidentio_workflows_list',
'incidentio_schedules_list', 'incidentio_schedules_list',
'incidentio_incident_updates_list', 'incidentio_incident_updates_list',

View File

@@ -216,31 +216,21 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
condition: { field: 'operation', value: ['update_deal'] }, condition: { field: 'operation', value: ['update_deal'] },
}, },
{ {
id: 'deal_id', id: 'sort',
title: 'Deal ID', title: 'Sort By',
type: 'short-input', type: 'dropdown',
placeholder: 'Filter by deal ID ', options: [
condition: { field: 'operation', value: ['get_files'] }, { label: 'ID', id: 'id' },
}, { label: 'Update Time', id: 'update_time' },
{ ],
id: 'person_id', value: () => 'id',
title: 'Person ID',
type: 'short-input',
placeholder: 'Filter by person ID ',
condition: { field: 'operation', value: ['get_files'] },
},
{
id: 'org_id',
title: 'Organization ID',
type: 'short-input',
placeholder: 'Filter by organization ID ',
condition: { field: 'operation', value: ['get_files'] }, condition: { field: 'operation', value: ['get_files'] },
}, },
{ {
id: 'limit', id: 'limit',
title: 'Limit', title: 'Limit',
type: 'short-input', type: 'short-input',
placeholder: 'Number of results (default 100, max 500)', placeholder: 'Number of results (default 100, max 100)',
condition: { field: 'operation', value: ['get_files'] }, condition: { field: 'operation', value: ['get_files'] },
}, },
{ {
@@ -305,8 +295,28 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
id: 'cursor', id: 'cursor',
title: 'Cursor', title: 'Cursor',
type: 'short-input', type: 'short-input',
placeholder: 'Pagination cursor (optional)', placeholder: 'Pagination cursor from previous response',
condition: { field: 'operation', value: ['get_pipelines'] }, condition: {
field: 'operation',
value: ['get_all_deals', 'get_projects'],
},
},
{
id: 'start',
title: 'Start (Offset)',
type: 'short-input',
placeholder: 'Pagination offset (e.g., 0, 100, 200)',
condition: {
field: 'operation',
value: [
'get_activities',
'get_leads',
'get_files',
'get_pipeline_deals',
'get_mail_messages',
'get_pipelines',
],
},
}, },
{ {
id: 'pipeline_id', id: 'pipeline_id',
@@ -323,19 +333,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
placeholder: 'Filter by stage ID ', placeholder: 'Filter by stage ID ',
condition: { field: 'operation', value: ['get_pipeline_deals'] }, condition: { field: 'operation', value: ['get_pipeline_deals'] },
}, },
{
id: 'status',
title: 'Status',
type: 'dropdown',
options: [
{ label: 'All', id: '' },
{ label: 'Open', id: 'open' },
{ label: 'Won', id: 'won' },
{ label: 'Lost', id: 'lost' },
],
value: () => '',
condition: { field: 'operation', value: ['get_pipeline_deals'] },
},
{ {
id: 'limit', id: 'limit',
title: 'Limit', title: 'Limit',
@@ -426,22 +423,29 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
id: 'deal_id', id: 'deal_id',
title: 'Deal ID', title: 'Deal ID',
type: 'short-input', type: 'short-input',
placeholder: 'Filter by deal ID ', placeholder: 'Associated deal ID ',
condition: { field: 'operation', value: ['get_activities', 'create_activity'] }, condition: { field: 'operation', value: ['create_activity'] },
}, },
{ {
id: 'person_id', id: 'person_id',
title: 'Person ID', title: 'Person ID',
type: 'short-input', type: 'short-input',
placeholder: 'Filter by person ID ', placeholder: 'Associated person ID ',
condition: { field: 'operation', value: ['get_activities', 'create_activity'] }, condition: { field: 'operation', value: ['create_activity'] },
}, },
{ {
id: 'org_id', id: 'org_id',
title: 'Organization ID', title: 'Organization ID',
type: 'short-input', type: 'short-input',
placeholder: 'Filter by organization ID ', placeholder: 'Associated organization ID ',
condition: { field: 'operation', value: ['get_activities', 'create_activity'] }, condition: { field: 'operation', value: ['create_activity'] },
},
{
id: 'user_id',
title: 'User ID',
type: 'short-input',
placeholder: 'Filter by user ID',
condition: { field: 'operation', value: ['get_activities'] },
}, },
{ {
id: 'type', id: 'type',
@@ -781,7 +785,8 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
thread_id: { type: 'string', description: 'Mail thread ID' }, thread_id: { type: 'string', description: 'Mail thread ID' },
sort_by: { type: 'string', description: 'Field to sort by' }, sort_by: { type: 'string', description: 'Field to sort by' },
sort_direction: { type: 'string', description: 'Sorting direction' }, sort_direction: { type: 'string', description: 'Sorting direction' },
cursor: { type: 'string', description: 'Pagination cursor' }, cursor: { type: 'string', description: 'Pagination cursor (v2 endpoints)' },
start: { type: 'string', description: 'Pagination start offset (v1 endpoints)' },
project_id: { type: 'string', description: 'Project ID' }, project_id: { type: 'string', description: 'Project ID' },
description: { type: 'string', description: 'Description' }, description: { type: 'string', description: 'Description' },
start_date: { type: 'string', description: 'Start date' }, start_date: { type: 'string', description: 'Start date' },
@@ -793,12 +798,15 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
due_time: { type: 'string', description: 'Due time' }, due_time: { type: 'string', description: 'Due time' },
duration: { type: 'string', description: 'Duration' }, duration: { type: 'string', description: 'Duration' },
done: { type: 'string', description: 'Completion status' }, done: { type: 'string', description: 'Completion status' },
user_id: { type: 'string', description: 'User ID' },
note: { type: 'string', description: 'Notes' }, note: { type: 'string', description: 'Notes' },
lead_id: { type: 'string', description: 'Lead ID' }, lead_id: { type: 'string', description: 'Lead ID' },
archived: { type: 'string', description: 'Archived status' }, archived: { type: 'string', description: 'Archived status' },
value_amount: { type: 'string', description: 'Value amount' }, value_amount: { type: 'string', description: 'Value amount' },
value_currency: { type: 'string', description: 'Value currency' }, value_currency: { type: 'string', description: 'Value currency' },
is_archived: { type: 'string', description: 'Archive status' }, is_archived: { type: 'string', description: 'Archive status' },
organization_id: { type: 'string', description: 'Organization ID' },
owner_id: { type: 'string', description: 'Owner user ID' },
}, },
outputs: { outputs: {
deals: { type: 'json', description: 'Array of deal objects' }, deals: { type: 'json', description: 'Array of deal objects' },

View File

@@ -445,6 +445,13 @@ Return ONLY the order by expression - no explanations, no extra text.`,
placeholder: '100', placeholder: '100',
condition: { field: 'operation', value: 'query' }, condition: { field: 'operation', value: 'query' },
}, },
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'query' },
},
// Vector search operation fields // Vector search operation fields
{ {
id: 'functionName', id: 'functionName',
@@ -543,6 +550,13 @@ Return ONLY the order by expression - no explanations, no extra text.`,
placeholder: '100', placeholder: '100',
condition: { field: 'operation', value: 'text_search' }, condition: { field: 'operation', value: 'text_search' },
}, },
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'text_search' },
},
// Count operation fields // Count operation fields
{ {
id: 'filter', id: 'filter',

View File

@@ -66,6 +66,20 @@ export const TypeformBlock: BlockConfig<TypeformResponse> = {
placeholder: 'Number of responses per page (default: 25)', placeholder: 'Number of responses per page (default: 25)',
condition: { field: 'operation', value: 'typeform_responses' }, condition: { field: 'operation', value: 'typeform_responses' },
}, },
{
id: 'before',
title: 'Before (Cursor)',
type: 'short-input',
placeholder: 'Cursor token from previous response for pagination',
condition: { field: 'operation', value: 'typeform_responses' },
},
{
id: 'after',
title: 'After (Cursor)',
type: 'short-input',
placeholder: 'Cursor token from previous response for newer results',
condition: { field: 'operation', value: 'typeform_responses' },
},
{ {
id: 'since', id: 'since',
title: 'Since', title: 'Since',
@@ -380,6 +394,8 @@ Do not include any explanations, markdown formatting, or other text outside the
apiKey: { type: 'string', description: 'Personal access token' }, apiKey: { type: 'string', description: 'Personal access token' },
// Response operation params // Response operation params
pageSize: { type: 'number', description: 'Responses per page' }, pageSize: { type: 'number', description: 'Responses per page' },
before: { type: 'string', description: 'Cursor token for fetching the next page' },
after: { type: 'string', description: 'Cursor token for fetching newer results' },
since: { type: 'string', description: 'Start date filter' }, since: { type: 'string', description: 'Start date filter' },
until: { type: 'string', description: 'End date filter' }, until: { type: 'string', description: 'End date filter' },
completed: { type: 'string', description: 'Completion status filter' }, completed: { type: 'string', description: 'Completion status filter' },

View File

@@ -444,33 +444,36 @@ Return ONLY the search query - no explanations.`,
}, },
}, },
{ {
id: 'sortBy', id: 'filterType',
title: 'Sort By', title: 'Resource Type',
type: 'dropdown', type: 'dropdown',
options: [ options: [
{ label: 'Relevance', id: 'relevance' }, { label: 'Ticket', id: 'ticket' },
{ label: 'Created At', id: 'created_at' }, { label: 'User', id: 'user' },
{ label: 'Updated At', id: 'updated_at' }, { label: 'Organization', id: 'organization' },
{ label: 'Priority', id: 'priority' }, { label: 'Group', id: 'group' },
{ label: 'Status', id: 'status' },
{ label: 'Ticket Type', id: 'ticket_type' },
], ],
required: true,
condition: { condition: {
field: 'operation', field: 'operation',
value: ['search'], value: ['search'],
}, },
}, },
{ {
id: 'sortOrder', id: 'sort',
title: 'Sort Order', title: 'Sort',
type: 'dropdown', type: 'dropdown',
options: [ options: [
{ label: 'Ascending', id: 'asc' }, { label: 'Updated At (Asc)', id: 'updated_at' },
{ label: 'Descending', id: 'desc' }, { label: 'Updated At (Desc)', id: '-updated_at' },
{ label: 'ID (Asc)', id: 'id' },
{ label: 'ID (Desc)', id: '-id' },
{ label: 'Status (Asc)', id: 'status' },
{ label: 'Status (Desc)', id: '-status' },
], ],
condition: { condition: {
field: 'operation', field: 'operation',
value: ['search'], value: ['get_tickets'],
}, },
}, },
// Pagination fields // Pagination fields
@@ -492,20 +495,25 @@ Return ONLY the search query - no explanations.`,
}, },
}, },
{ {
id: 'page', id: 'pageAfter',
title: 'Page', title: 'Page After (Cursor)',
type: 'short-input', type: 'short-input',
placeholder: 'Page number', placeholder: 'Cursor from previous response (after_cursor)',
description: 'Cursor value from a previous response to fetch the next page of results',
condition: { condition: {
field: 'operation', field: 'operation',
value: [ value: ['get_tickets', 'get_users', 'get_organizations', 'search'],
'get_tickets', },
'get_users', },
'get_organizations', {
'search_users', id: 'page',
'autocomplete_organizations', title: 'Page Number',
'search', type: 'short-input',
], placeholder: 'Page number (default: 1)',
description: 'Page number for offset-based pagination',
condition: {
field: 'operation',
value: ['search_users', 'autocomplete_organizations'],
}, },
}, },
], ],
@@ -624,6 +632,7 @@ Return ONLY the search query - no explanations.`,
email: { type: 'string', description: 'Zendesk email address' }, email: { type: 'string', description: 'Zendesk email address' },
apiToken: { type: 'string', description: 'Zendesk API token' }, apiToken: { type: 'string', description: 'Zendesk API token' },
subdomain: { type: 'string', description: 'Zendesk subdomain' }, subdomain: { type: 'string', description: 'Zendesk subdomain' },
sort: { type: 'string', description: 'Sort field for ticket listing' },
}, },
outputs: { outputs: {
// Ticket operations - list // Ticket operations - list
@@ -665,8 +674,11 @@ Return ONLY the search query - no explanations.`,
type: 'boolean', type: 'boolean',
description: 'Deletion confirmation (delete_ticket, delete_user, delete_organization)', description: 'Deletion confirmation (delete_ticket, delete_user, delete_organization)',
}, },
// Pagination (shared across list operations) // Cursor-based pagination (shared across list operations)
paging: { type: 'json', description: 'Pagination information for list operations' }, paging: {
type: 'json',
description: 'Cursor-based pagination information (after_cursor, has_more)',
},
// Metadata (shared across all operations) // Metadata (shared across all operations)
metadata: { type: 'json', description: 'Operation metadata including operation type' }, metadata: { type: 'json', description: 'Operation metadata including operation type' },
}, },

View File

@@ -9,6 +9,7 @@ import {
type ReactNode, type ReactNode,
useCallback, useCallback,
useEffect, useEffect,
useId,
useMemo, useMemo,
useRef, useRef,
useState, useState,
@@ -170,6 +171,7 @@ const Combobox = memo(
}, },
ref ref
) => { ) => {
const listboxId = useId()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [highlightedIndex, setHighlightedIndex] = useState(-1) const [highlightedIndex, setHighlightedIndex] = useState(-1)
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
@@ -513,6 +515,7 @@ const Combobox = memo(
role='combobox' role='combobox'
aria-expanded={open} aria-expanded={open}
aria-haspopup='listbox' aria-haspopup='listbox'
aria-controls={listboxId}
aria-disabled={disabled} aria-disabled={disabled}
tabIndex={disabled ? -1 : 0} tabIndex={disabled ? -1 : 0}
className={cn( className={cn(
@@ -616,7 +619,7 @@ const Combobox = memo(
} }
}} }}
> >
<div ref={dropdownRef} role='listbox'> <div ref={dropdownRef} role='listbox' id={listboxId}>
{isLoading ? ( {isLoading ? (
<div className='flex items-center justify-center py-[14px]'> <div className='flex items-center justify-center py-[14px]'>
<Loader2 className='h-[16px] w-[16px] animate-spin text-[var(--text-muted)]' /> <Loader2 className='h-[16px] w-[16px] animate-spin text-[var(--text-muted)]' />

View File

@@ -27,12 +27,14 @@ const Alert = React.forwardRef<
Alert.displayName = 'Alert' Alert.displayName = 'Alert'
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>( const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => ( ({ className, children, ...props }, ref) => (
<h5 <h5
ref={ref} ref={ref}
className={cn('mb-1 font-medium leading-none tracking-tight', className)} className={cn('mb-1 font-medium leading-none tracking-tight', className)}
{...props} {...props}
/> >
{children}
</h5>
) )
) )
AlertTitle.displayName = 'AlertTitle' AlertTitle.displayName = 'AlertTitle'

View File

@@ -16,26 +16,32 @@ export const mdxComponents: MDXRemoteProps['components'] = {
unoptimized unoptimized
/> />
), ),
h2: (props: any) => ( h2: ({ children, className, ...props }: any) => (
<h2 <h2
{...props} {...props}
style={{ fontSize: '30px', marginTop: '3rem', marginBottom: '1.5rem' }} style={{ fontSize: '30px', marginTop: '3rem', marginBottom: '1.5rem' }}
className={clsx('font-medium text-black leading-tight', props.className)} className={clsx('font-medium text-black leading-tight', className)}
/> >
{children}
</h2>
), ),
h3: (props: any) => ( h3: ({ children, className, ...props }: any) => (
<h3 <h3
{...props} {...props}
style={{ fontSize: '24px', marginTop: '1.5rem', marginBottom: '0.75rem' }} style={{ fontSize: '24px', marginTop: '1.5rem', marginBottom: '0.75rem' }}
className={clsx('font-medium leading-tight', props.className)} className={clsx('font-medium leading-tight', className)}
/> >
{children}
</h3>
), ),
h4: (props: any) => ( h4: ({ children, className, ...props }: any) => (
<h4 <h4
{...props} {...props}
style={{ fontSize: '19px', marginTop: '1.5rem', marginBottom: '0.75rem' }} style={{ fontSize: '19px', marginTop: '1.5rem', marginBottom: '0.75rem' }}
className={clsx('font-medium leading-tight', props.className)} className={clsx('font-medium leading-tight', className)}
/> >
{children}
</h4>
), ),
p: (props: any) => ( p: (props: any) => (
<p <p

View File

@@ -107,5 +107,3 @@ if (typeof process !== 'undefined') {
logger.info(`S3 copilot bucket: ${env.S3_COPILOT_BUCKET_NAME}`) logger.info(`S3 copilot bucket: ${env.S3_COPILOT_BUCKET_NAME}`)
} }
} }
export default ensureUploadsDirectory

View File

@@ -326,6 +326,18 @@ const nextConfig: NextConfig = {
return redirects return redirects
}, },
async rewrites() {
return [
...(isHosted
? [
{
source: '/r/:shortCode',
destination: 'https://go.trybeluga.ai/:shortCode',
},
]
: []),
]
},
} }
export default nextConfig export default nextConfig

View File

@@ -14,6 +14,7 @@ import {
supportsNativeStructuredOutputs, supportsNativeStructuredOutputs,
} from '@/providers/models' } from '@/providers/models'
import type { ProviderRequest, ProviderResponse, TimeSegment } from '@/providers/types' import type { ProviderRequest, ProviderResponse, TimeSegment } from '@/providers/types'
import { ProviderError } from '@/providers/types'
import { import {
calculateCost, calculateCost,
prepareToolExecution, prepareToolExecution,
@@ -842,15 +843,11 @@ export async function executeAnthropicProviderRequest(
duration: totalDuration, duration: totalDuration,
}) })
const enhancedError = new Error(error instanceof Error ? error.message : String(error)) throw new ProviderError(error instanceof Error ? error.message : String(error), {
// @ts-ignore
enhancedError.timing = {
startTime: providerStartTimeISO, startTime: providerStartTimeISO,
endTime: providerEndTimeISO, endTime: providerEndTimeISO,
duration: totalDuration, duration: totalDuration,
} })
throw enhancedError
} }
} }
@@ -1299,14 +1296,10 @@ export async function executeAnthropicProviderRequest(
duration: totalDuration, duration: totalDuration,
}) })
const enhancedError = new Error(error instanceof Error ? error.message : String(error)) throw new ProviderError(error instanceof Error ? error.message : String(error), {
// @ts-ignore
enhancedError.timing = {
startTime: providerStartTimeISO, startTime: providerStartTimeISO,
endTime: providerEndTimeISO, endTime: providerEndTimeISO,
duration: totalDuration, duration: totalDuration,
} })
throw enhancedError
} }
} }

View File

@@ -30,6 +30,7 @@ import type {
ProviderResponse, ProviderResponse,
TimeSegment, TimeSegment,
} from '@/providers/types' } from '@/providers/types'
import { ProviderError } from '@/providers/types'
import { import {
calculateCost, calculateCost,
prepareToolExecution, prepareToolExecution,
@@ -251,7 +252,7 @@ async function executeChatCompletionsRequest(
output: currentResponse.usage?.completion_tokens || 0, output: currentResponse.usage?.completion_tokens || 0,
total: currentResponse.usage?.total_tokens || 0, total: currentResponse.usage?.total_tokens || 0,
} }
const toolCalls: (FunctionCallResponse & { success: boolean })[] = [] const toolCalls: FunctionCallResponse[] = []
const toolResults: Record<string, unknown>[] = [] const toolResults: Record<string, unknown>[] = []
const currentMessages = [...allMessages] const currentMessages = [...allMessages]
let iterationCount = 0 let iterationCount = 0
@@ -577,15 +578,11 @@ async function executeChatCompletionsRequest(
duration: totalDuration, duration: totalDuration,
}) })
const enhancedError = new Error(error instanceof Error ? error.message : String(error)) throw new ProviderError(error instanceof Error ? error.message : String(error), {
// @ts-ignore - Adding timing property to the error
enhancedError.timing = {
startTime: providerStartTimeISO, startTime: providerStartTimeISO,
endTime: providerEndTimeISO, endTime: providerEndTimeISO,
duration: totalDuration, duration: totalDuration,
} })
throw enhancedError
} }
} }

View File

@@ -22,11 +22,13 @@ import {
} from '@/providers/bedrock/utils' } from '@/providers/bedrock/utils'
import { getProviderDefaultModel, getProviderModels } from '@/providers/models' import { getProviderDefaultModel, getProviderModels } from '@/providers/models'
import type { import type {
FunctionCallResponse,
ProviderConfig, ProviderConfig,
ProviderRequest, ProviderRequest,
ProviderResponse, ProviderResponse,
TimeSegment, TimeSegment,
} from '@/providers/types' } from '@/providers/types'
import { ProviderError } from '@/providers/types'
import { import {
calculateCost, calculateCost,
prepareToolExecution, prepareToolExecution,
@@ -419,8 +421,8 @@ export const bedrockProvider: ProviderConfig = {
pricing: initialCost.pricing, pricing: initialCost.pricing,
} }
const toolCalls: any[] = [] const toolCalls: FunctionCallResponse[] = []
const toolResults: any[] = [] const toolResults: Record<string, unknown>[] = []
const currentMessages = [...messages] const currentMessages = [...messages]
let iterationCount = 0 let iterationCount = 0
let hasUsedForcedTool = false let hasUsedForcedTool = false
@@ -561,7 +563,7 @@ export const bedrockProvider: ProviderConfig = {
let resultContent: any let resultContent: any
if (result.success) { if (result.success) {
toolResults.push(result.output) toolResults.push(result.output!)
resultContent = result.output resultContent = result.output
} else { } else {
resultContent = { resultContent = {
@@ -903,15 +905,11 @@ export const bedrockProvider: ProviderConfig = {
duration: totalDuration, duration: totalDuration,
}) })
const enhancedError = new Error(error instanceof Error ? error.message : String(error)) throw new ProviderError(error instanceof Error ? error.message : String(error), {
// @ts-ignore
enhancedError.timing = {
startTime: providerStartTimeISO, startTime: providerStartTimeISO,
endTime: providerEndTimeISO, endTime: providerEndTimeISO,
duration: totalDuration, duration: totalDuration,
} })
throw enhancedError
} }
}, },
} }

View File

@@ -11,6 +11,7 @@ import type {
ProviderResponse, ProviderResponse,
TimeSegment, TimeSegment,
} from '@/providers/types' } from '@/providers/types'
import { ProviderError } from '@/providers/types'
import { import {
calculateCost, calculateCost,
prepareToolExecution, prepareToolExecution,
@@ -539,15 +540,11 @@ export const cerebrasProvider: ProviderConfig = {
duration: totalDuration, duration: totalDuration,
}) })
const enhancedError = new Error(error instanceof Error ? error.message : String(error)) throw new ProviderError(error instanceof Error ? error.message : String(error), {
// @ts-ignore - Adding timing property to error for debugging
enhancedError.timing = {
startTime: providerStartTimeISO, startTime: providerStartTimeISO,
endTime: providerEndTimeISO, endTime: providerEndTimeISO,
duration: totalDuration, duration: totalDuration,
} })
throw enhancedError
} }
}, },
} }

View File

@@ -10,6 +10,7 @@ import type {
ProviderResponse, ProviderResponse,
TimeSegment, TimeSegment,
} from '@/providers/types' } from '@/providers/types'
import { ProviderError } from '@/providers/types'
import { import {
calculateCost, calculateCost,
prepareToolExecution, prepareToolExecution,
@@ -538,15 +539,11 @@ export const deepseekProvider: ProviderConfig = {
duration: totalDuration, duration: totalDuration,
}) })
const enhancedError = new Error(error instanceof Error ? error.message : String(error)) throw new ProviderError(error instanceof Error ? error.message : String(error), {
// @ts-ignore
enhancedError.timing = {
startTime: providerStartTimeISO, startTime: providerStartTimeISO,
endTime: providerEndTimeISO, endTime: providerEndTimeISO,
duration: totalDuration, duration: totalDuration,
} })
throw enhancedError
} }
}, },
} }

View File

@@ -10,6 +10,7 @@ import type {
ProviderResponse, ProviderResponse,
TimeSegment, TimeSegment,
} from '@/providers/types' } from '@/providers/types'
import { ProviderError } from '@/providers/types'
import { import {
calculateCost, calculateCost,
prepareToolExecution, prepareToolExecution,
@@ -496,15 +497,11 @@ export const groqProvider: ProviderConfig = {
duration: totalDuration, duration: totalDuration,
}) })
const enhancedError = new Error(error instanceof Error ? error.message : String(error)) throw new ProviderError(error instanceof Error ? error.message : String(error), {
// @ts-ignore
enhancedError.timing = {
startTime: providerStartTimeISO, startTime: providerStartTimeISO,
endTime: providerEndTimeISO, endTime: providerEndTimeISO,
duration: totalDuration, duration: totalDuration,
} })
throw enhancedError
} }
}, },
} }

View File

@@ -11,6 +11,7 @@ import type {
ProviderResponse, ProviderResponse,
TimeSegment, TimeSegment,
} from '@/providers/types' } from '@/providers/types'
import { ProviderError } from '@/providers/types'
import { import {
calculateCost, calculateCost,
prepareToolExecution, prepareToolExecution,
@@ -551,15 +552,11 @@ export const mistralProvider: ProviderConfig = {
duration: totalDuration, duration: totalDuration,
}) })
const enhancedError = new Error(error instanceof Error ? error.message : String(error)) throw new ProviderError(error instanceof Error ? error.message : String(error), {
// @ts-ignore - Adding timing property to error for debugging
enhancedError.timing = {
startTime: providerStartTimeISO, startTime: providerStartTimeISO,
endTime: providerEndTimeISO, endTime: providerEndTimeISO,
duration: totalDuration, duration: totalDuration,
} })
throw enhancedError
} }
}, },
} }

View File

@@ -12,6 +12,7 @@ import type {
ProviderResponse, ProviderResponse,
TimeSegment, TimeSegment,
} from '@/providers/types' } from '@/providers/types'
import { ProviderError } from '@/providers/types'
import { calculateCost, prepareToolExecution } from '@/providers/utils' import { calculateCost, prepareToolExecution } from '@/providers/utils'
import { useProvidersStore } from '@/stores/providers' import { useProvidersStore } from '@/stores/providers'
import { executeTool } from '@/tools' import { executeTool } from '@/tools'
@@ -554,15 +555,11 @@ export const ollamaProvider: ProviderConfig = {
duration: totalDuration, duration: totalDuration,
}) })
const enhancedError = new Error(error instanceof Error ? error.message : String(error)) throw new ProviderError(error instanceof Error ? error.message : String(error), {
// @ts-ignore
enhancedError.timing = {
startTime: providerStartTimeISO, startTime: providerStartTimeISO,
endTime: providerEndTimeISO, endTime: providerEndTimeISO,
duration: totalDuration, duration: totalDuration,
} })
throw enhancedError
} }
}, },
} }

View File

@@ -3,6 +3,7 @@ import type OpenAI from 'openai'
import type { StreamingExecution } from '@/executor/types' import type { StreamingExecution } from '@/executor/types'
import { MAX_TOOL_ITERATIONS } from '@/providers' import { MAX_TOOL_ITERATIONS } from '@/providers'
import type { Message, ProviderRequest, ProviderResponse, TimeSegment } from '@/providers/types' import type { Message, ProviderRequest, ProviderResponse, TimeSegment } from '@/providers/types'
import { ProviderError } from '@/providers/types'
import { import {
calculateCost, calculateCost,
prepareToolExecution, prepareToolExecution,
@@ -806,14 +807,10 @@ export async function executeResponsesProviderRequest(
duration: totalDuration, duration: totalDuration,
}) })
const enhancedError = new Error(error instanceof Error ? error.message : String(error)) throw new ProviderError(error instanceof Error ? error.message : String(error), {
// @ts-ignore - Adding timing property to the error
enhancedError.timing = {
startTime: providerStartTimeISO, startTime: providerStartTimeISO,
endTime: providerEndTimeISO, endTime: providerEndTimeISO,
duration: totalDuration, duration: totalDuration,
} })
throw enhancedError
} }
} }

View File

@@ -10,11 +10,14 @@ import {
supportsNativeStructuredOutputs, supportsNativeStructuredOutputs,
} from '@/providers/openrouter/utils' } from '@/providers/openrouter/utils'
import type { import type {
FunctionCallResponse,
Message,
ProviderConfig, ProviderConfig,
ProviderRequest, ProviderRequest,
ProviderResponse, ProviderResponse,
TimeSegment, TimeSegment,
} from '@/providers/types' } from '@/providers/types'
import { ProviderError } from '@/providers/types'
import { import {
calculateCost, calculateCost,
generateSchemaInstructions, generateSchemaInstructions,
@@ -90,7 +93,7 @@ export const openRouterProvider: ProviderConfig = {
stream: !!request.stream, stream: !!request.stream,
}) })
const allMessages = [] as any[] const allMessages: Message[] = []
if (request.systemPrompt) { if (request.systemPrompt) {
allMessages.push({ role: 'system', content: request.systemPrompt }) allMessages.push({ role: 'system', content: request.systemPrompt })
@@ -237,8 +240,8 @@ export const openRouterProvider: ProviderConfig = {
output: currentResponse.usage?.completion_tokens || 0, output: currentResponse.usage?.completion_tokens || 0,
total: currentResponse.usage?.total_tokens || 0, total: currentResponse.usage?.total_tokens || 0,
} }
const toolCalls = [] as any[] const toolCalls: FunctionCallResponse[] = []
const toolResults = [] as any[] const toolResults: Record<string, unknown>[] = []
const currentMessages = [...allMessages] const currentMessages = [...allMessages]
let iterationCount = 0 let iterationCount = 0
let modelTime = firstResponseTime let modelTime = firstResponseTime
@@ -352,7 +355,7 @@ export const openRouterProvider: ProviderConfig = {
let resultContent: any let resultContent: any
if (result.success) { if (result.success) {
toolResults.push(result.output) toolResults.push(result.output!)
resultContent = result.output resultContent = result.output
} else { } else {
resultContent = { resultContent = {
@@ -593,14 +596,11 @@ export const openRouterProvider: ProviderConfig = {
} }
logger.error('Error in OpenRouter request:', errorDetails) logger.error('Error in OpenRouter request:', errorDetails)
const enhancedError = new Error(error instanceof Error ? error.message : String(error)) throw new ProviderError(error instanceof Error ? error.message : String(error), {
// @ts-ignore
enhancedError.timing = {
startTime: providerStartTimeISO, startTime: providerStartTimeISO,
endTime: providerEndTimeISO, endTime: providerEndTimeISO,
duration: totalDuration, duration: totalDuration,
} })
throw enhancedError
} }
}, },
} }

View File

@@ -59,6 +59,7 @@ export interface FunctionCallResponse {
result?: Record<string, any> result?: Record<string, any>
output?: Record<string, any> output?: Record<string, any>
input?: Record<string, any> input?: Record<string, any>
success?: boolean
} }
export interface TimeSegment { export interface TimeSegment {
@@ -177,4 +178,21 @@ export interface ProviderRequest {
previousInteractionId?: string previousInteractionId?: string
} }
/**
* Typed error class for provider failures that includes timing information.
*/
export class ProviderError extends Error {
timing: {
startTime: string
endTime: string
duration: number
}
constructor(message: string, timing: { startTime: string; endTime: string; duration: number }) {
super(message)
this.name = 'ProviderError'
this.timing = timing
}
}
export const providers: Record<string, ProviderConfig> = {} export const providers: Record<string, ProviderConfig> = {}

View File

@@ -6,11 +6,13 @@ import type { StreamingExecution } from '@/executor/types'
import { MAX_TOOL_ITERATIONS } from '@/providers' import { MAX_TOOL_ITERATIONS } from '@/providers'
import { getProviderDefaultModel, getProviderModels } from '@/providers/models' import { getProviderDefaultModel, getProviderModels } from '@/providers/models'
import type { import type {
Message,
ProviderConfig, ProviderConfig,
ProviderRequest, ProviderRequest,
ProviderResponse, ProviderResponse,
TimeSegment, TimeSegment,
} from '@/providers/types' } from '@/providers/types'
import { ProviderError } from '@/providers/types'
import { import {
calculateCost, calculateCost,
prepareToolExecution, prepareToolExecution,
@@ -98,7 +100,7 @@ export const vllmProvider: ProviderConfig = {
baseURL: `${baseUrl}/v1`, baseURL: `${baseUrl}/v1`,
}) })
const allMessages = [] as any[] const allMessages: Message[] = []
if (request.systemPrompt) { if (request.systemPrompt) {
allMessages.push({ allMessages.push({
@@ -635,23 +637,11 @@ export const vllmProvider: ProviderConfig = {
duration: totalDuration, duration: totalDuration,
}) })
const enhancedError = new Error(errorMessage) throw new ProviderError(errorMessage, {
// @ts-ignore
enhancedError.timing = {
startTime: providerStartTimeISO, startTime: providerStartTimeISO,
endTime: providerEndTimeISO, endTime: providerEndTimeISO,
duration: totalDuration, duration: totalDuration,
} })
if (errorType) {
// @ts-ignore
enhancedError.vllmErrorType = errorType
}
if (errorCode) {
// @ts-ignore
enhancedError.vllmErrorCode = errorCode
}
throw enhancedError
} }
}, },
} }

View File

@@ -5,11 +5,13 @@ import type { StreamingExecution } from '@/executor/types'
import { MAX_TOOL_ITERATIONS } from '@/providers' import { MAX_TOOL_ITERATIONS } from '@/providers'
import { getProviderDefaultModel, getProviderModels } from '@/providers/models' import { getProviderDefaultModel, getProviderModels } from '@/providers/models'
import type { import type {
Message,
ProviderConfig, ProviderConfig,
ProviderRequest, ProviderRequest,
ProviderResponse, ProviderResponse,
TimeSegment, TimeSegment,
} from '@/providers/types' } from '@/providers/types'
import { ProviderError } from '@/providers/types'
import { import {
calculateCost, calculateCost,
prepareToolExecution, prepareToolExecution,
@@ -52,7 +54,7 @@ export const xAIProvider: ProviderConfig = {
streaming: !!request.stream, streaming: !!request.stream,
}) })
const allMessages: any[] = [] const allMessages: Message[] = []
if (request.systemPrompt) { if (request.systemPrompt) {
allMessages.push({ allMessages.push({
@@ -587,15 +589,11 @@ export const xAIProvider: ProviderConfig = {
hasResponseFormat: !!request.responseFormat, hasResponseFormat: !!request.responseFormat,
}) })
const enhancedError = new Error(error instanceof Error ? error.message : String(error)) throw new ProviderError(error instanceof Error ? error.message : String(error), {
// @ts-ignore - Adding timing property to error for debugging
enhancedError.timing = {
startTime: providerStartTimeISO, startTime: providerStartTimeISO,
endTime: providerEndTimeISO, endTime: providerEndTimeISO,
duration: totalDuration, duration: totalDuration,
} })
throw enhancedError
} }
}, },
} }

View File

@@ -26,12 +26,6 @@ export const actionsListTool: ToolConfig<
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Filter actions by incident ID (e.g., "01FCNDV6P870EA6S7TK1DSYDG0")', description: 'Filter actions by incident ID (e.g., "01FCNDV6P870EA6S7TK1DSYDG0")',
}, },
page_size: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of actions to return per page (e.g., 10, 25, 50)',
},
}, },
request: { request: {
@@ -42,10 +36,6 @@ export const actionsListTool: ToolConfig<
url.searchParams.append('incident_id', params.incident_id) url.searchParams.append('incident_id', params.incident_id)
} }
if (params.page_size) {
url.searchParams.append('page_size', params.page_size.toString())
}
return url.toString() return url.toString()
}, },
method: 'GET', method: 'GET',

View File

@@ -20,22 +20,10 @@ export const escalationsListTool: ToolConfig<
visibility: 'user-only', visibility: 'user-only',
description: 'incident.io API Key', description: 'incident.io API Key',
}, },
page_size: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (e.g., 10, 25, 50). Default: 25',
},
}, },
request: { request: {
url: (params) => { url: () => 'https://api.incident.io/v2/escalations',
const url = new URL('https://api.incident.io/v2/escalations')
if (params.page_size) {
url.searchParams.append('page_size', params.page_size.toString())
}
return url.toString()
},
method: 'GET', method: 'GET',
headers: (params) => ({ headers: (params) => ({
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@@ -26,12 +26,6 @@ export const followUpsListTool: ToolConfig<
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Filter follow-ups by incident ID (e.g., "01FCNDV6P870EA6S7TK1DSYDG0")', description: 'Filter follow-ups by incident ID (e.g., "01FCNDV6P870EA6S7TK1DSYDG0")',
}, },
page_size: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of follow-ups to return per page (e.g., 10, 25, 50)',
},
}, },
request: { request: {
@@ -42,10 +36,6 @@ export const followUpsListTool: ToolConfig<
url.searchParams.append('incident_id', params.incident_id) url.searchParams.append('incident_id', params.incident_id)
} }
if (params.page_size) {
url.searchParams.append('page_size', params.page_size.toString())
}
return url.toString() return url.toString()
}, },
method: 'GET', method: 'GET',

View File

@@ -396,7 +396,6 @@ export interface IncidentioIncidentsUpdateResponse extends ToolResponse {
// Action types // Action types
export interface IncidentioActionsListParams extends IncidentioBaseParams { export interface IncidentioActionsListParams extends IncidentioBaseParams {
incident_id?: string incident_id?: string
page_size?: number
} }
export interface IncidentioAction { export interface IncidentioAction {
@@ -446,7 +445,6 @@ export interface IncidentioActionsShowResponse extends ToolResponse {
// Follow-up types // Follow-up types
export interface IncidentioFollowUpsListParams extends IncidentioBaseParams { export interface IncidentioFollowUpsListParams extends IncidentioBaseParams {
incident_id?: string incident_id?: string
page_size?: number
} }
export interface IncidentioFollowUp { export interface IncidentioFollowUp {
@@ -664,6 +662,7 @@ export interface CustomFieldsDeleteResponse extends ToolResponse {
// Users list tool types // Users list tool types
export interface IncidentioUsersListParams extends IncidentioBaseParams { export interface IncidentioUsersListParams extends IncidentioBaseParams {
page_size?: number page_size?: number
after?: string
} }
export interface IncidentioUser { export interface IncidentioUser {
@@ -676,6 +675,11 @@ export interface IncidentioUser {
export interface IncidentioUsersListResponse extends ToolResponse { export interface IncidentioUsersListResponse extends ToolResponse {
output: { output: {
users: IncidentioUser[] users: IncidentioUser[]
pagination_meta?: {
after: string
page_size: number
total_record_count?: number
}
} }
} }
@@ -786,9 +790,7 @@ export type IncidentioResponse =
| IncidentioEscalationPathsDeleteResponse | IncidentioEscalationPathsDeleteResponse
// Escalations types // Escalations types
export interface IncidentioEscalationsListParams extends IncidentioBaseParams { export interface IncidentioEscalationsListParams extends IncidentioBaseParams {}
page_size?: number
}
export interface IncidentioEscalation { export interface IncidentioEscalation {
id: string id: string

View File

@@ -1,6 +1,7 @@
import type { import {
IncidentioUsersListParams, INCIDENTIO_PAGINATION_OUTPUT_PROPERTIES,
IncidentioUsersListResponse, type IncidentioUsersListParams,
type IncidentioUsersListResponse,
} from '@/tools/incidentio/types' } from '@/tools/incidentio/types'
import type { ToolConfig } from '@/tools/types' import type { ToolConfig } from '@/tools/types'
@@ -24,15 +25,27 @@ export const usersListTool: ToolConfig<IncidentioUsersListParams, IncidentioUser
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Number of results to return per page (e.g., 10, 25, 50). Default: 25', description: 'Number of results to return per page (e.g., 10, 25, 50). Default: 25',
}, },
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination cursor to fetch the next page of results',
},
}, },
request: { request: {
url: (params) => { url: (params) => {
const baseUrl = 'https://api.incident.io/v2/users' const url = new URL('https://api.incident.io/v2/users')
if (params.page_size) { if (params.page_size) {
return `${baseUrl}?page_size=${params.page_size}` url.searchParams.append('page_size', params.page_size.toString())
} }
return baseUrl
if (params.after) {
url.searchParams.append('after', params.after)
}
return url.toString()
}, },
method: 'GET', method: 'GET',
headers: (params) => ({ headers: (params) => ({
@@ -53,6 +66,13 @@ export const usersListTool: ToolConfig<IncidentioUsersListParams, IncidentioUser
email: user.email, email: user.email,
role: user.role, role: user.role,
})), })),
pagination_meta: data.pagination_meta
? {
after: data.pagination_meta.after,
page_size: data.pagination_meta.page_size,
total_record_count: data.pagination_meta.total_record_count,
}
: undefined,
}, },
} }
}, },
@@ -71,5 +91,11 @@ export const usersListTool: ToolConfig<IncidentioUsersListParams, IncidentioUser
}, },
}, },
}, },
pagination_meta: {
type: 'object',
description: 'Pagination metadata',
optional: true,
properties: INCIDENTIO_PAGINATION_OUTPUT_PROPERTIES,
},
}, },
} }

View File

@@ -24,23 +24,11 @@ export const pipedriveGetActivitiesTool: ToolConfig<
visibility: 'hidden', visibility: 'hidden',
description: 'The access token for the Pipedrive API', description: 'The access token for the Pipedrive API',
}, },
deal_id: { user_id: {
type: 'string', type: 'string',
required: false, required: false,
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Filter activities by deal ID (e.g., "123")', description: 'Filter activities by user ID (e.g., "123")',
},
person_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter activities by person ID (e.g., "456")',
},
org_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter activities by organization ID (e.g., "789")',
}, },
type: { type: {
type: 'string', type: 'string',
@@ -60,6 +48,12 @@ export const pipedriveGetActivitiesTool: ToolConfig<
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Number of results to return (e.g., "50", default: 100, max: 500)', description: 'Number of results to return (e.g., "50", default: 100, max: 500)',
}, },
start: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination start offset (0-based index of the first item to return)',
},
}, },
request: { request: {
@@ -67,12 +61,11 @@ export const pipedriveGetActivitiesTool: ToolConfig<
const baseUrl = 'https://api.pipedrive.com/v1/activities' const baseUrl = 'https://api.pipedrive.com/v1/activities'
const queryParams = new URLSearchParams() const queryParams = new URLSearchParams()
if (params.deal_id) queryParams.append('deal_id', params.deal_id) if (params.user_id) queryParams.append('user_id', params.user_id)
if (params.person_id) queryParams.append('person_id', params.person_id)
if (params.org_id) queryParams.append('org_id', params.org_id)
if (params.type) queryParams.append('type', params.type) if (params.type) queryParams.append('type', params.type)
if (params.done) queryParams.append('done', params.done) if (params.done) queryParams.append('done', params.done)
if (params.limit) queryParams.append('limit', params.limit) if (params.limit) queryParams.append('limit', params.limit)
if (params.start) queryParams.append('start', params.start)
const queryString = queryParams.toString() const queryString = queryParams.toString()
return queryString ? `${baseUrl}?${queryString}` : baseUrl return queryString ? `${baseUrl}?${queryString}` : baseUrl
@@ -99,12 +92,16 @@ export const pipedriveGetActivitiesTool: ToolConfig<
} }
const activities = data.data || [] const activities = data.data || []
const hasMore = data.additional_data?.pagination?.more_items_in_collection || false
const nextStart = data.additional_data?.pagination?.next_start ?? null
return { return {
success: true, success: true,
output: { output: {
activities, activities,
total_items: activities.length, total_items: activities.length,
has_more: hasMore,
next_start: nextStart,
success: true, success: true,
}, },
} }
@@ -120,6 +117,16 @@ export const pipedriveGetActivitiesTool: ToolConfig<
}, },
}, },
total_items: { type: 'number', description: 'Total number of activities returned' }, total_items: { type: 'number', description: 'Total number of activities returned' },
has_more: {
type: 'boolean',
description: 'Whether more activities are available',
optional: true,
},
next_start: {
type: 'number',
description: 'Offset for fetching the next page',
optional: true,
},
success: { type: 'boolean', description: 'Operation success status' }, success: { type: 'boolean', description: 'Operation success status' },
}, },
} }

View File

@@ -67,6 +67,12 @@ export const pipedriveGetAllDealsTool: ToolConfig<
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Number of results to return (e.g., "50", default: 100, max: 500)', description: 'Number of results to return (e.g., "50", default: 100, max: 500)',
}, },
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'For pagination, the marker representing the first item on the next page',
},
}, },
request: { request: {
@@ -81,6 +87,7 @@ export const pipedriveGetAllDealsTool: ToolConfig<
if (params.pipeline_id) queryParams.append('pipeline_id', params.pipeline_id) if (params.pipeline_id) queryParams.append('pipeline_id', params.pipeline_id)
if (params.updated_since) queryParams.append('updated_since', params.updated_since) if (params.updated_since) queryParams.append('updated_since', params.updated_since)
if (params.limit) queryParams.append('limit', params.limit) if (params.limit) queryParams.append('limit', params.limit)
if (params.cursor) queryParams.append('cursor', params.cursor)
const queryString = queryParams.toString() const queryString = queryParams.toString()
return queryString ? `${baseUrl}?${queryString}` : baseUrl return queryString ? `${baseUrl}?${queryString}` : baseUrl
@@ -107,7 +114,8 @@ export const pipedriveGetAllDealsTool: ToolConfig<
} }
const deals = data.data || [] const deals = data.data || []
const hasMore = data.additional_data?.pagination?.more_items_in_collection || false const nextCursor = data.additional_data?.next_cursor ?? null
const hasMore = nextCursor !== null
return { return {
success: true, success: true,
@@ -116,6 +124,7 @@ export const pipedriveGetAllDealsTool: ToolConfig<
metadata: { metadata: {
total_items: deals.length, total_items: deals.length,
has_more: hasMore, has_more: hasMore,
next_cursor: nextCursor,
}, },
success: true, success: true,
}, },

View File

@@ -16,29 +16,23 @@ export const pipedriveGetFilesTool: ToolConfig<PipedriveGetFilesParams, Pipedriv
visibility: 'hidden', visibility: 'hidden',
description: 'The access token for the Pipedrive API', description: 'The access token for the Pipedrive API',
}, },
deal_id: { sort: {
type: 'string', type: 'string',
required: false, required: false,
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Filter files by deal ID (e.g., "123")', description: 'Sort files by field (supported: "id", "update_time")',
},
person_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter files by person ID (e.g., "456")',
},
org_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter files by organization ID (e.g., "789")',
}, },
limit: { limit: {
type: 'string', type: 'string',
required: false, required: false,
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Number of results to return (e.g., "50", default: 100, max: 500)', description: 'Number of results to return (e.g., "50", default: 100, max: 100)',
},
start: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination start offset (0-based index of the first item to return)',
}, },
downloadFiles: { downloadFiles: {
type: 'boolean', type: 'boolean',
@@ -56,10 +50,9 @@ export const pipedriveGetFilesTool: ToolConfig<PipedriveGetFilesParams, Pipedriv
}), }),
body: (params) => ({ body: (params) => ({
accessToken: params.accessToken, accessToken: params.accessToken,
deal_id: params.deal_id, sort: params.sort,
person_id: params.person_id,
org_id: params.org_id,
limit: params.limit, limit: params.limit,
start: params.start,
downloadFiles: params.downloadFiles, downloadFiles: params.downloadFiles,
}), }),
}, },
@@ -79,6 +72,16 @@ export const pipedriveGetFilesTool: ToolConfig<PipedriveGetFilesParams, Pipedriv
optional: true, optional: true,
}, },
total_items: { type: 'number', description: 'Total number of files returned' }, total_items: { type: 'number', description: 'Total number of files returned' },
has_more: {
type: 'boolean',
description: 'Whether more files are available',
optional: true,
},
next_start: {
type: 'number',
description: 'Offset for fetching the next page',
optional: true,
},
success: { type: 'boolean', description: 'Operation success status' }, success: { type: 'boolean', description: 'Operation success status' },
}, },
} }

View File

@@ -60,6 +60,12 @@ export const pipedriveGetLeadsTool: ToolConfig<PipedriveGetLeadsParams, Pipedriv
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Number of results to return (e.g., "50", default: 100, max: 500)', description: 'Number of results to return (e.g., "50", default: 100, max: 500)',
}, },
start: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination start offset (0-based index of the first item to return)',
},
}, },
request: { request: {
@@ -81,6 +87,7 @@ export const pipedriveGetLeadsTool: ToolConfig<PipedriveGetLeadsParams, Pipedriv
if (params.person_id) queryParams.append('person_id', params.person_id) if (params.person_id) queryParams.append('person_id', params.person_id)
if (params.organization_id) queryParams.append('organization_id', params.organization_id) if (params.organization_id) queryParams.append('organization_id', params.organization_id)
if (params.limit) queryParams.append('limit', params.limit) if (params.limit) queryParams.append('limit', params.limit)
if (params.start) queryParams.append('start', params.start)
const queryString = queryParams.toString() const queryString = queryParams.toString()
return queryString ? `${baseUrl}?${queryString}` : baseUrl return queryString ? `${baseUrl}?${queryString}` : baseUrl
@@ -119,12 +126,19 @@ export const pipedriveGetLeadsTool: ToolConfig<PipedriveGetLeadsParams, Pipedriv
// Otherwise, return list of leads // Otherwise, return list of leads
const leads = data.data || [] const leads = data.data || []
// Leads endpoint puts pagination fields directly on additional_data (no .pagination wrapper)
const hasMore = data.additional_data?.more_items_in_collection || false
const currentStart = data.additional_data?.start ?? 0
const currentLimit = data.additional_data?.limit ?? leads.length
const nextStart = hasMore ? currentStart + currentLimit : null
return { return {
success: true, success: true,
output: { output: {
leads, leads,
total_items: leads.length, total_items: leads.length,
has_more: hasMore,
next_start: nextStart,
success: true, success: true,
}, },
} }
@@ -151,6 +165,16 @@ export const pipedriveGetLeadsTool: ToolConfig<PipedriveGetLeadsParams, Pipedriv
description: 'Total number of leads returned', description: 'Total number of leads returned',
optional: true, optional: true,
}, },
has_more: {
type: 'boolean',
description: 'Whether more leads are available',
optional: true,
},
next_start: {
type: 'number',
description: 'Offset for fetching the next page',
optional: true,
},
success: { type: 'boolean', description: 'Operation success status' }, success: { type: 'boolean', description: 'Operation success status' },
}, },
} }

View File

@@ -40,6 +40,12 @@ export const pipedriveGetMailMessagesTool: ToolConfig<
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Number of results to return (e.g., "25", default: 50)', description: 'Number of results to return (e.g., "25", default: 50)',
}, },
start: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination start offset (0-based index of the first item to return)',
},
}, },
request: { request: {
@@ -49,6 +55,7 @@ export const pipedriveGetMailMessagesTool: ToolConfig<
if (params.folder) queryParams.append('folder', params.folder) if (params.folder) queryParams.append('folder', params.folder)
if (params.limit) queryParams.append('limit', params.limit) if (params.limit) queryParams.append('limit', params.limit)
if (params.start) queryParams.append('start', params.start)
const queryString = queryParams.toString() const queryString = queryParams.toString()
return queryString ? `${baseUrl}?${queryString}` : baseUrl return queryString ? `${baseUrl}?${queryString}` : baseUrl
@@ -75,12 +82,16 @@ export const pipedriveGetMailMessagesTool: ToolConfig<
} }
const threads = data.data || [] const threads = data.data || []
const hasMore = data.additional_data?.pagination?.more_items_in_collection || false
const nextStart = data.additional_data?.pagination?.next_start ?? null
return { return {
success: true, success: true,
output: { output: {
messages: threads, messages: threads,
total_items: threads.length, total_items: threads.length,
has_more: hasMore,
next_start: nextStart,
success: true, success: true,
}, },
} }
@@ -89,6 +100,16 @@ export const pipedriveGetMailMessagesTool: ToolConfig<
outputs: { outputs: {
messages: { type: 'array', description: 'Array of mail thread objects from Pipedrive mailbox' }, messages: { type: 'array', description: 'Array of mail thread objects from Pipedrive mailbox' },
total_items: { type: 'number', description: 'Total number of mail threads returned' }, total_items: { type: 'number', description: 'Total number of mail threads returned' },
has_more: {
type: 'boolean',
description: 'Whether more messages are available',
optional: true,
},
next_start: {
type: 'number',
description: 'Offset for fetching the next page',
optional: true,
},
success: { type: 'boolean', description: 'Operation success status' }, success: { type: 'boolean', description: 'Operation success status' },
}, },
} }

View File

@@ -35,18 +35,18 @@ export const pipedriveGetPipelineDealsTool: ToolConfig<
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Filter by specific stage within the pipeline (e.g., "2")', description: 'Filter by specific stage within the pipeline (e.g., "2")',
}, },
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by deal status: open, won, lost',
},
limit: { limit: {
type: 'string', type: 'string',
required: false, required: false,
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Number of results to return (e.g., "50", default: 100, max: 500)', description: 'Number of results to return (e.g., "50", default: 100, max: 500)',
}, },
start: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination start offset (0-based index of the first item to return)',
},
}, },
request: { request: {
@@ -55,8 +55,8 @@ export const pipedriveGetPipelineDealsTool: ToolConfig<
const queryParams = new URLSearchParams() const queryParams = new URLSearchParams()
if (params.stage_id) queryParams.append('stage_id', params.stage_id) if (params.stage_id) queryParams.append('stage_id', params.stage_id)
if (params.status) queryParams.append('status', params.status)
if (params.limit) queryParams.append('limit', params.limit) if (params.limit) queryParams.append('limit', params.limit)
if (params.start) queryParams.append('start', params.start)
const queryString = queryParams.toString() const queryString = queryParams.toString()
return queryString ? `${baseUrl}?${queryString}` : baseUrl return queryString ? `${baseUrl}?${queryString}` : baseUrl
@@ -83,6 +83,8 @@ export const pipedriveGetPipelineDealsTool: ToolConfig<
} }
const deals = data.data || [] const deals = data.data || []
const hasMore = data.additional_data?.pagination?.more_items_in_collection || false
const nextStart = data.additional_data?.pagination?.next_start ?? null
return { return {
success: true, success: true,
@@ -91,6 +93,8 @@ export const pipedriveGetPipelineDealsTool: ToolConfig<
metadata: { metadata: {
pipeline_id: params?.pipeline_id || '', pipeline_id: params?.pipeline_id || '',
total_items: deals.length, total_items: deals.length,
has_more: hasMore,
next_start: nextStart,
}, },
success: true, success: true,
}, },

View File

@@ -42,11 +42,11 @@ export const pipedriveGetPipelinesTool: ToolConfig<
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Number of results to return (e.g., "50", default: 100, max: 500)', description: 'Number of results to return (e.g., "50", default: 100, max: 500)',
}, },
cursor: { start: {
type: 'string', type: 'string',
required: false, required: false,
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'For pagination, the marker representing the first item on the next page', description: 'Pagination start offset (0-based index of the first item to return)',
}, },
}, },
@@ -58,7 +58,7 @@ export const pipedriveGetPipelinesTool: ToolConfig<
if (params.sort_by) queryParams.append('sort_by', params.sort_by) if (params.sort_by) queryParams.append('sort_by', params.sort_by)
if (params.sort_direction) queryParams.append('sort_direction', params.sort_direction) if (params.sort_direction) queryParams.append('sort_direction', params.sort_direction)
if (params.limit) queryParams.append('limit', params.limit) if (params.limit) queryParams.append('limit', params.limit)
if (params.cursor) queryParams.append('cursor', params.cursor) if (params.start) queryParams.append('start', params.start)
const queryString = queryParams.toString() const queryString = queryParams.toString()
return queryString ? `${baseUrl}?${queryString}` : baseUrl return queryString ? `${baseUrl}?${queryString}` : baseUrl
@@ -85,12 +85,16 @@ export const pipedriveGetPipelinesTool: ToolConfig<
} }
const pipelines = data.data || [] const pipelines = data.data || []
const hasMore = data.additional_data?.pagination?.more_items_in_collection || false
const nextStart = data.additional_data?.pagination?.next_start ?? null
return { return {
success: true, success: true,
output: { output: {
pipelines, pipelines,
total_items: pipelines.length, total_items: pipelines.length,
has_more: hasMore,
next_start: nextStart,
success: true, success: true,
}, },
} }
@@ -106,6 +110,16 @@ export const pipedriveGetPipelinesTool: ToolConfig<
}, },
}, },
total_items: { type: 'number', description: 'Total number of pipelines returned' }, total_items: { type: 'number', description: 'Total number of pipelines returned' },
has_more: {
type: 'boolean',
description: 'Whether more pipelines are available',
optional: true,
},
next_start: {
type: 'number',
description: 'Offset for fetching the next page',
optional: true,
},
success: { type: 'boolean', description: 'Operation success status' }, success: { type: 'boolean', description: 'Operation success status' },
}, },
} }

View File

@@ -42,6 +42,12 @@ export const pipedriveGetProjectsTool: ToolConfig<
description: description:
'Number of results to return (e.g., "50", default: 100, max: 500, only for listing all)', 'Number of results to return (e.g., "50", default: 100, max: 500, only for listing all)',
}, },
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'For pagination, the marker representing the first item on the next page',
},
}, },
request: { request: {
@@ -57,6 +63,7 @@ export const pipedriveGetProjectsTool: ToolConfig<
if (params.status) queryParams.append('status', params.status) if (params.status) queryParams.append('status', params.status)
if (params.limit) queryParams.append('limit', params.limit) if (params.limit) queryParams.append('limit', params.limit)
if (params.cursor) queryParams.append('cursor', params.cursor)
const queryString = queryParams.toString() const queryString = queryParams.toString()
return queryString ? `${baseUrl}?${queryString}` : baseUrl return queryString ? `${baseUrl}?${queryString}` : baseUrl
@@ -95,12 +102,16 @@ export const pipedriveGetProjectsTool: ToolConfig<
// Otherwise, return list of projects // Otherwise, return list of projects
const projects = data.data || [] const projects = data.data || []
const nextCursor = data.additional_data?.next_cursor ?? null
const hasMore = nextCursor !== null
return { return {
success: true, success: true,
output: { output: {
projects, projects,
total_items: projects.length, total_items: projects.length,
has_more: hasMore,
next_cursor: nextCursor,
success: true, success: true,
}, },
} }
@@ -122,6 +133,16 @@ export const pipedriveGetProjectsTool: ToolConfig<
description: 'Total number of projects returned', description: 'Total number of projects returned',
optional: true, optional: true,
}, },
has_more: {
type: 'boolean',
description: 'Whether more projects are available',
optional: true,
},
next_cursor: {
type: 'string',
description: 'Cursor for fetching the next page',
optional: true,
},
success: { type: 'boolean', description: 'Operation success status' }, success: { type: 'boolean', description: 'Operation success status' },
}, },
} }

View File

@@ -239,6 +239,16 @@ export const PIPEDRIVE_MAIL_MESSAGE_OUTPUT: OutputProperty = {
export const PIPEDRIVE_METADATA_OUTPUT_PROPERTIES = { export const PIPEDRIVE_METADATA_OUTPUT_PROPERTIES = {
total_items: { type: 'number', description: 'Total number of items' }, total_items: { type: 'number', description: 'Total number of items' },
has_more: { type: 'boolean', description: 'Whether more items are available', optional: true }, has_more: { type: 'boolean', description: 'Whether more items are available', optional: true },
next_cursor: {
type: 'string',
description: 'Cursor for fetching the next page (v2 endpoints)',
optional: true,
},
next_start: {
type: 'number',
description: 'Offset for fetching the next page (v1 endpoints)',
optional: true,
},
} as const satisfies Record<string, OutputProperty> } as const satisfies Record<string, OutputProperty>
// Common Pipedrive types // Common Pipedrive types
@@ -355,6 +365,7 @@ export interface PipedriveGetAllDealsParams {
pipeline_id?: string pipeline_id?: string
updated_since?: string updated_since?: string
limit?: string limit?: string
cursor?: string
} }
export interface PipedriveGetAllDealsOutput { export interface PipedriveGetAllDealsOutput {
@@ -362,6 +373,7 @@ export interface PipedriveGetAllDealsOutput {
metadata: { metadata: {
total_items: number total_items: number
has_more: boolean has_more: boolean
next_cursor?: string
} }
success: boolean success: boolean
} }
@@ -431,10 +443,9 @@ export interface PipedriveUpdateDealResponse extends ToolResponse {
// GET Files // GET Files
export interface PipedriveGetFilesParams { export interface PipedriveGetFilesParams {
accessToken: string accessToken: string
deal_id?: string sort?: string
person_id?: string
org_id?: string
limit?: string limit?: string
start?: string
downloadFiles?: boolean downloadFiles?: boolean
} }
@@ -442,6 +453,8 @@ export interface PipedriveGetFilesOutput {
files: PipedriveFile[] files: PipedriveFile[]
downloadedFiles?: ToolFileData[] downloadedFiles?: ToolFileData[]
total_items: number total_items: number
has_more?: boolean
next_start?: number
success: boolean success: boolean
} }
@@ -453,11 +466,14 @@ export interface PipedriveGetMailMessagesParams {
accessToken: string accessToken: string
folder?: string folder?: string
limit?: string limit?: string
start?: string
} }
export interface PipedriveGetMailMessagesOutput { export interface PipedriveGetMailMessagesOutput {
messages: PipedriveMailMessage[] messages: PipedriveMailMessage[]
total_items: number total_items: number
has_more?: boolean
next_start?: number
success: boolean success: boolean
} }
@@ -490,12 +506,14 @@ export interface PipedriveGetPipelinesParams {
sort_by?: string sort_by?: string
sort_direction?: string sort_direction?: string
limit?: string limit?: string
cursor?: string start?: string
} }
export interface PipedriveGetPipelinesOutput { export interface PipedriveGetPipelinesOutput {
pipelines: PipedrivePipeline[] pipelines: PipedrivePipeline[]
total_items: number total_items: number
has_more?: boolean
next_start?: number
success: boolean success: boolean
} }
@@ -508,8 +526,8 @@ export interface PipedriveGetPipelineDealsParams {
accessToken: string accessToken: string
pipeline_id: string pipeline_id: string
stage_id?: string stage_id?: string
status?: string
limit?: string limit?: string
start?: string
} }
export interface PipedriveGetPipelineDealsOutput { export interface PipedriveGetPipelineDealsOutput {
@@ -517,6 +535,8 @@ export interface PipedriveGetPipelineDealsOutput {
metadata: { metadata: {
pipeline_id: string pipeline_id: string
total_items: number total_items: number
has_more?: boolean
next_start?: number
} }
success: boolean success: boolean
} }
@@ -531,12 +551,15 @@ export interface PipedriveGetProjectsParams {
project_id?: string project_id?: string
status?: string status?: string
limit?: string limit?: string
cursor?: string
} }
export interface PipedriveGetProjectsOutput { export interface PipedriveGetProjectsOutput {
projects?: PipedriveProject[] projects?: PipedriveProject[]
project?: PipedriveProject project?: PipedriveProject
total_items?: number total_items?: number
has_more?: boolean
next_cursor?: string
success: boolean success: boolean
} }
@@ -565,17 +588,18 @@ export interface PipedriveCreateProjectResponse extends ToolResponse {
// GET All Activities // GET All Activities
export interface PipedriveGetActivitiesParams { export interface PipedriveGetActivitiesParams {
accessToken: string accessToken: string
deal_id?: string user_id?: string
person_id?: string
org_id?: string
type?: string type?: string
done?: string done?: string
limit?: string limit?: string
start?: string
} }
export interface PipedriveGetActivitiesOutput { export interface PipedriveGetActivitiesOutput {
activities: PipedriveActivity[] activities: PipedriveActivity[]
total_items: number total_items: number
has_more?: boolean
next_start?: number
success: boolean success: boolean
} }
@@ -636,12 +660,15 @@ export interface PipedriveGetLeadsParams {
person_id?: string person_id?: string
organization_id?: string organization_id?: string
limit?: string limit?: string
start?: string
} }
export interface PipedriveGetLeadsOutput { export interface PipedriveGetLeadsOutput {
leads?: PipedriveLead[] leads?: PipedriveLead[]
lead?: PipedriveLead lead?: PipedriveLead
total_items?: number total_items?: number
has_more?: boolean
next_start?: number
success: boolean success: boolean
} }

View File

@@ -51,6 +51,12 @@ export const queryTool: ToolConfig<SupabaseQueryParams, SupabaseQueryResponse> =
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Maximum number of rows to return', description: 'Maximum number of rows to return',
}, },
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of rows to skip (for pagination)',
},
apiKey: { apiKey: {
type: 'string', type: 'string',
required: true, required: true,
@@ -91,10 +97,15 @@ export const queryTool: ToolConfig<SupabaseQueryParams, SupabaseQueryResponse> =
} }
// Add limit if provided // Add limit if provided
if (params.limit) { if (params.limit !== undefined && params.limit !== null) {
url += `&limit=${Number(params.limit)}` url += `&limit=${Number(params.limit)}`
} }
// Add offset if provided
if (params.offset !== undefined && params.offset !== null) {
url += `&offset=${Number(params.offset)}`
}
return url return url
}, },
method: 'GET', method: 'GET',

View File

@@ -57,6 +57,12 @@ export const textSearchTool: ToolConfig<SupabaseTextSearchParams, SupabaseTextSe
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Maximum number of rows to return', description: 'Maximum number of rows to return',
}, },
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of rows to skip (for pagination)',
},
apiKey: { apiKey: {
type: 'string', type: 'string',
required: true, required: true,
@@ -74,8 +80,9 @@ export const textSearchTool: ToolConfig<SupabaseTextSearchParams, SupabaseTextSe
let url = `https://${params.projectId}.supabase.co/rest/v1/${params.table}?select=*` let url = `https://${params.projectId}.supabase.co/rest/v1/${params.table}?select=*`
// Map search types to PostgREST operators // Map search types to PostgREST operators
// plfts = plainto_tsquery (natural language), phfts = phraseto_tsquery, wfts = websearch_to_tsquery
const operatorMap: Record<string, string> = { const operatorMap: Record<string, string> = {
plain: 'fts', plain: 'plfts',
phrase: 'phfts', phrase: 'phfts',
websearch: 'wfts', websearch: 'wfts',
} }
@@ -86,10 +93,15 @@ export const textSearchTool: ToolConfig<SupabaseTextSearchParams, SupabaseTextSe
url += `&${params.column}=${operator}(${language}).${encodeURIComponent(params.query)}` url += `&${params.column}=${operator}(${language}).${encodeURIComponent(params.query)}`
// Add limit if provided // Add limit if provided
if (params.limit) { if (params.limit !== undefined && params.limit !== null) {
url += `&limit=${Number(params.limit)}` url += `&limit=${Number(params.limit)}`
} }
// Add offset if provided
if (params.offset !== undefined && params.offset !== null) {
url += `&offset=${Number(params.offset)}`
}
return url return url
}, },
method: 'GET', method: 'GET',

View File

@@ -315,6 +315,7 @@ export interface SupabaseQueryParams {
filter?: string filter?: string
orderBy?: string orderBy?: string
limit?: number limit?: number
offset?: number
} }
export interface SupabaseInsertParams { export interface SupabaseInsertParams {
@@ -413,6 +414,7 @@ export interface SupabaseTextSearchParams {
searchType?: string searchType?: string
language?: string language?: string
limit?: number limit?: number
offset?: number
} }
export interface SupabaseTextSearchResponse extends SupabaseBaseResponse {} export interface SupabaseTextSearchResponse extends SupabaseBaseResponse {}

View File

@@ -26,6 +26,18 @@ export const responsesTool: ToolConfig<TypeformResponsesParams, TypeformResponse
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Number of responses to retrieve (e.g., 10, 25, 50)', description: 'Number of responses to retrieve (e.g., 10, 25, 50)',
}, },
before: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor token for fetching the next page of older responses',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor token for fetching the next page of newer responses',
},
since: { since: {
type: 'string', type: 'string',
required: false, required: false,
@@ -56,6 +68,14 @@ export const responsesTool: ToolConfig<TypeformResponsesParams, TypeformResponse
queryParams.push(`page_size=${Number(params.pageSize)}`) queryParams.push(`page_size=${Number(params.pageSize)}`)
} }
if (params.before) {
queryParams.push(`before=${encodeURIComponent(params.before)}`)
}
if (params.after) {
queryParams.push(`after=${encodeURIComponent(params.after)}`)
}
if (params.since) { if (params.since) {
queryParams.push(`since=${encodeURIComponent(params.since)}`) queryParams.push(`since=${encodeURIComponent(params.since)}`)
} }

View File

@@ -62,6 +62,8 @@ export interface TypeformResponsesParams {
formId: string formId: string
apiKey: string apiKey: string
pageSize?: number pageSize?: number
before?: string
after?: string
since?: string since?: string
until?: string until?: string
completed?: string completed?: string

View File

@@ -21,9 +21,9 @@ export interface ZendeskAutocompleteOrganizationsResponse {
output: { output: {
organizations: any[] organizations: any[]
paging?: { paging?: {
after_cursor: string | null
has_more: boolean
next_page?: string | null next_page?: string | null
previous_page?: string | null
count: number
} }
metadata: { metadata: {
total_returned: number total_returned: number
@@ -78,7 +78,7 @@ export const zendeskAutocompleteOrganizationsTool: ToolConfig<
type: 'string', type: 'string',
required: false, required: false,
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Page number as a string (e.g., "1", "2")', description: 'Page number for pagination (1-based)',
}, },
}, },
@@ -86,8 +86,8 @@ export const zendeskAutocompleteOrganizationsTool: ToolConfig<
url: (params) => { url: (params) => {
const queryParams = new URLSearchParams() const queryParams = new URLSearchParams()
queryParams.append('name', params.name) queryParams.append('name', params.name)
if (params.page) queryParams.append('page', params.page)
if (params.perPage) queryParams.append('per_page', params.perPage) if (params.perPage) queryParams.append('per_page', params.perPage)
if (params.page) queryParams.append('page', params.page)
const query = queryParams.toString() const query = queryParams.toString()
const url = buildZendeskUrl(params.subdomain, '/organizations/autocomplete') const url = buildZendeskUrl(params.subdomain, '/organizations/autocomplete')
@@ -112,19 +112,22 @@ export const zendeskAutocompleteOrganizationsTool: ToolConfig<
const data = await response.json() const data = await response.json()
const organizations = data.organizations || [] const organizations = data.organizations || []
const hasMore = data.next_page !== null && data.next_page !== undefined
return { return {
success: true, success: true,
output: { output: {
organizations, organizations,
// /organizations/autocomplete uses offset pagination (page/per_page), not cursor pagination.
// after_cursor is always null; use next_page URL or page param for subsequent pages.
paging: { paging: {
after_cursor: null,
has_more: hasMore,
next_page: data.next_page ?? null, next_page: data.next_page ?? null,
previous_page: data.previous_page ?? null,
count: data.count || organizations.length,
}, },
metadata: { metadata: {
total_returned: organizations.length, total_returned: organizations.length,
has_more: !!data.next_page, has_more: hasMore,
}, },
success: true, success: true,
}, },

View File

@@ -1,6 +1,8 @@
import type { ToolConfig } from '@/tools/types' import type { ToolConfig } from '@/tools/types'
import { import {
appendCursorPaginationParams,
buildZendeskUrl, buildZendeskUrl,
extractCursorPagingInfo,
handleZendeskError, handleZendeskError,
METADATA_OUTPUT, METADATA_OUTPUT,
ORGANIZATIONS_ARRAY_OUTPUT, ORGANIZATIONS_ARRAY_OUTPUT,
@@ -12,7 +14,7 @@ export interface ZendeskGetOrganizationsParams {
apiToken: string apiToken: string
subdomain: string subdomain: string
perPage?: string perPage?: string
page?: string pageAfter?: string
} }
export interface ZendeskGetOrganizationsResponse { export interface ZendeskGetOrganizationsResponse {
@@ -20,9 +22,8 @@ export interface ZendeskGetOrganizationsResponse {
output: { output: {
organizations: any[] organizations: any[]
paging?: { paging?: {
next_page?: string | null after_cursor: string | null
previous_page?: string | null has_more: boolean
count: number
} }
metadata: { metadata: {
total_returned: number total_returned: number
@@ -66,19 +67,18 @@ export const zendeskGetOrganizationsTool: ToolConfig<
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Results per page as a number string (default: "100", max: "100")', description: 'Results per page as a number string (default: "100", max: "100")',
}, },
page: { pageAfter: {
type: 'string', type: 'string',
required: false, required: false,
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Page number as a string (e.g., "1", "2")', description: 'Cursor from a previous response to fetch the next page of results',
}, },
}, },
request: { request: {
url: (params) => { url: (params) => {
const queryParams = new URLSearchParams() const queryParams = new URLSearchParams()
if (params.page) queryParams.append('page', params.page) appendCursorPaginationParams(queryParams, params)
if (params.perPage) queryParams.append('per_page', params.perPage)
const query = queryParams.toString() const query = queryParams.toString()
const url = buildZendeskUrl(params.subdomain, '/organizations') const url = buildZendeskUrl(params.subdomain, '/organizations')
@@ -103,19 +103,16 @@ export const zendeskGetOrganizationsTool: ToolConfig<
const data = await response.json() const data = await response.json()
const organizations = data.organizations || [] const organizations = data.organizations || []
const paging = extractCursorPagingInfo(data)
return { return {
success: true, success: true,
output: { output: {
organizations, organizations,
paging: { paging,
next_page: data.next_page ?? null,
previous_page: data.previous_page ?? null,
count: data.count || organizations.length,
},
metadata: { metadata: {
total_returned: organizations.length, total_returned: organizations.length,
has_more: !!data.next_page, has_more: paging.has_more,
}, },
success: true, success: true,
}, },

View File

@@ -1,6 +1,8 @@
import type { ToolConfig } from '@/tools/types' import type { ToolConfig } from '@/tools/types'
import { import {
appendCursorPaginationParams,
buildZendeskUrl, buildZendeskUrl,
extractCursorPagingInfo,
handleZendeskError, handleZendeskError,
METADATA_OUTPUT, METADATA_OUTPUT,
PAGING_OUTPUT, PAGING_OUTPUT,
@@ -16,10 +18,9 @@ export interface ZendeskGetTicketsParams {
type?: string type?: string
assigneeId?: string assigneeId?: string
organizationId?: string organizationId?: string
sortBy?: string sort?: string
sortOrder?: string
perPage?: string perPage?: string
page?: string pageAfter?: string
} }
export interface ZendeskGetTicketsResponse { export interface ZendeskGetTicketsResponse {
@@ -27,9 +28,8 @@ export interface ZendeskGetTicketsResponse {
output: { output: {
tickets: any[] tickets: any[]
paging?: { paging?: {
next_page?: string | null after_cursor: string | null
previous_page?: string | null has_more: boolean
count: number
} }
metadata: { metadata: {
total_returned: number total_returned: number
@@ -95,17 +95,12 @@ export const zendeskGetTicketsTool: ToolConfig<ZendeskGetTicketsParams, ZendeskG
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Filter by organization ID as a numeric string (e.g., "67890")', description: 'Filter by organization ID as a numeric string (e.g., "67890")',
}, },
sortBy: { sort: {
type: 'string', type: 'string',
required: false, required: false,
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Sort field: "created_at", "updated_at", "priority", or "status"', description:
}, 'Sort field for ticket listing (only applies without filters): "updated_at", "id", or "status". Prefix with "-" for descending (e.g., "-updated_at")',
sortOrder: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort order: "asc" or "desc"',
}, },
perPage: { perPage: {
type: 'string', type: 'string',
@@ -113,11 +108,11 @@ export const zendeskGetTicketsTool: ToolConfig<ZendeskGetTicketsParams, ZendeskG
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Results per page as a number string (default: "100", max: "100")', description: 'Results per page as a number string (default: "100", max: "100")',
}, },
page: { pageAfter: {
type: 'string', type: 'string',
required: false, required: false,
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Page number as a string (e.g., "1", "2")', description: 'Cursor from a previous response to fetch the next page of results',
}, },
}, },
@@ -142,20 +137,16 @@ export const zendeskGetTicketsTool: ToolConfig<ZendeskGetTicketsParams, ZendeskG
const queryParams = new URLSearchParams() const queryParams = new URLSearchParams()
queryParams.append('query', searchTerms.join(' ')) queryParams.append('query', searchTerms.join(' '))
if (params.sortBy) queryParams.append('sort_by', params.sortBy) queryParams.append('filter[type]', 'ticket')
if (params.sortOrder) queryParams.append('sort_order', params.sortOrder) appendCursorPaginationParams(queryParams, params)
if (params.page) queryParams.append('page', params.page)
if (params.perPage) queryParams.append('per_page', params.perPage)
return `${buildZendeskUrl(params.subdomain, '/search')}?${queryParams.toString()}` return `${buildZendeskUrl(params.subdomain, '/search/export')}?${queryParams.toString()}`
} }
// No filters - use the simple /tickets endpoint // No filters - use the simple /tickets endpoint with cursor-based pagination
const queryParams = new URLSearchParams() const queryParams = new URLSearchParams()
if (params.sortBy) queryParams.append('sort_by', params.sortBy) if (params.sort) queryParams.append('sort', params.sort)
if (params.sortOrder) queryParams.append('sort_order', params.sortOrder) appendCursorPaginationParams(queryParams, params)
if (params.page) queryParams.append('page', params.page)
if (params.perPage) queryParams.append('per_page', params.perPage)
const query = queryParams.toString() const query = queryParams.toString()
const url = buildZendeskUrl(params.subdomain, '/tickets') const url = buildZendeskUrl(params.subdomain, '/tickets')
@@ -182,19 +173,16 @@ export const zendeskGetTicketsTool: ToolConfig<ZendeskGetTicketsParams, ZendeskG
const data = await response.json() const data = await response.json()
// Handle both /tickets response (data.tickets) and /search response (data.results) // Handle both /tickets response (data.tickets) and /search response (data.results)
const tickets = data.tickets || data.results || [] const tickets = data.tickets || data.results || []
const paging = extractCursorPagingInfo(data)
return { return {
success: true, success: true,
output: { output: {
tickets, tickets,
paging: { paging,
next_page: data.next_page ?? null,
previous_page: data.previous_page ?? null,
count: data.count || tickets.length,
},
metadata: { metadata: {
total_returned: tickets.length, total_returned: tickets.length,
has_more: !!data.next_page, has_more: paging.has_more,
}, },
success: true, success: true,
}, },

View File

@@ -1,6 +1,8 @@
import type { ToolConfig } from '@/tools/types' import type { ToolConfig } from '@/tools/types'
import { import {
appendCursorPaginationParams,
buildZendeskUrl, buildZendeskUrl,
extractCursorPagingInfo,
handleZendeskError, handleZendeskError,
METADATA_OUTPUT, METADATA_OUTPUT,
PAGING_OUTPUT, PAGING_OUTPUT,
@@ -14,7 +16,7 @@ export interface ZendeskGetUsersParams {
role?: string role?: string
permissionSet?: string permissionSet?: string
perPage?: string perPage?: string
page?: string pageAfter?: string
} }
export interface ZendeskGetUsersResponse { export interface ZendeskGetUsersResponse {
@@ -22,9 +24,8 @@ export interface ZendeskGetUsersResponse {
output: { output: {
users: any[] users: any[]
paging?: { paging?: {
next_page?: string | null after_cursor: string | null
previous_page?: string | null has_more: boolean
count: number
} }
metadata: { metadata: {
total_returned: number total_returned: number
@@ -77,11 +78,11 @@ export const zendeskGetUsersTool: ToolConfig<ZendeskGetUsersParams, ZendeskGetUs
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Results per page as a number string (default: "100", max: "100")', description: 'Results per page as a number string (default: "100", max: "100")',
}, },
page: { pageAfter: {
type: 'string', type: 'string',
required: false, required: false,
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Page number as a string (e.g., "1", "2")', description: 'Cursor from a previous response to fetch the next page of results',
}, },
}, },
@@ -90,8 +91,7 @@ export const zendeskGetUsersTool: ToolConfig<ZendeskGetUsersParams, ZendeskGetUs
const queryParams = new URLSearchParams() const queryParams = new URLSearchParams()
if (params.role) queryParams.append('role', params.role) if (params.role) queryParams.append('role', params.role)
if (params.permissionSet) queryParams.append('permission_set', params.permissionSet) if (params.permissionSet) queryParams.append('permission_set', params.permissionSet)
if (params.page) queryParams.append('page', params.page) appendCursorPaginationParams(queryParams, params)
if (params.perPage) queryParams.append('per_page', params.perPage)
const query = queryParams.toString() const query = queryParams.toString()
const url = buildZendeskUrl(params.subdomain, '/users') const url = buildZendeskUrl(params.subdomain, '/users')
@@ -116,19 +116,16 @@ export const zendeskGetUsersTool: ToolConfig<ZendeskGetUsersParams, ZendeskGetUs
const data = await response.json() const data = await response.json()
const users = data.users || [] const users = data.users || []
const paging = extractCursorPagingInfo(data)
return { return {
success: true, success: true,
output: { output: {
users, users,
paging: { paging,
next_page: data.next_page ?? null,
previous_page: data.previous_page ?? null,
count: data.count || users.length,
},
metadata: { metadata: {
total_returned: users.length, total_returned: users.length,
has_more: !!data.next_page, has_more: paging.has_more,
}, },
success: true, success: true,
}, },

View File

@@ -1,6 +1,8 @@
import type { ToolConfig } from '@/tools/types' import type { ToolConfig } from '@/tools/types'
import { import {
appendCursorPaginationParams,
buildZendeskUrl, buildZendeskUrl,
extractCursorPagingInfo,
handleZendeskError, handleZendeskError,
METADATA_OUTPUT, METADATA_OUTPUT,
PAGING_OUTPUT, PAGING_OUTPUT,
@@ -11,10 +13,9 @@ export interface ZendeskSearchParams {
apiToken: string apiToken: string
subdomain: string subdomain: string
query: string query: string
sortBy?: string filterType: string
sortOrder?: string
perPage?: string perPage?: string
page?: string pageAfter?: string
} }
export interface ZendeskSearchResponse { export interface ZendeskSearchResponse {
@@ -22,9 +23,8 @@ export interface ZendeskSearchResponse {
output: { output: {
results: any[] results: any[]
paging?: { paging?: {
next_page?: string | null after_cursor: string | null
previous_page?: string | null has_more: boolean
count: number
} }
metadata: { metadata: {
total_returned: number total_returned: number
@@ -66,18 +66,11 @@ export const zendeskSearchTool: ToolConfig<ZendeskSearchParams, ZendeskSearchRes
description: description:
'Search query string using Zendesk search syntax (e.g., "type:ticket status:open")', 'Search query string using Zendesk search syntax (e.g., "type:ticket status:open")',
}, },
sortBy: { filterType: {
type: 'string', type: 'string',
required: false, required: true,
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: description: 'Resource type to search for: "ticket", "user", "organization", or "group"',
'Sort field: "relevance", "created_at", "updated_at", "priority", "status", or "ticket_type"',
},
sortOrder: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort order: "asc" or "desc"',
}, },
perPage: { perPage: {
type: 'string', type: 'string',
@@ -85,11 +78,11 @@ export const zendeskSearchTool: ToolConfig<ZendeskSearchParams, ZendeskSearchRes
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Results per page as a number string (default: "100", max: "100")', description: 'Results per page as a number string (default: "100", max: "100")',
}, },
page: { pageAfter: {
type: 'string', type: 'string',
required: false, required: false,
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Page number as a string (e.g., "1", "2")', description: 'Cursor from a previous response to fetch the next page of results',
}, },
}, },
@@ -97,13 +90,11 @@ export const zendeskSearchTool: ToolConfig<ZendeskSearchParams, ZendeskSearchRes
url: (params) => { url: (params) => {
const queryParams = new URLSearchParams() const queryParams = new URLSearchParams()
queryParams.append('query', params.query) queryParams.append('query', params.query)
if (params.sortBy) queryParams.append('sort_by', params.sortBy) queryParams.append('filter[type]', params.filterType)
if (params.sortOrder) queryParams.append('sort_order', params.sortOrder) appendCursorPaginationParams(queryParams, params)
if (params.page) queryParams.append('page', params.page)
if (params.perPage) queryParams.append('per_page', params.perPage)
const query = queryParams.toString() const query = queryParams.toString()
const url = buildZendeskUrl(params.subdomain, '/search') const url = buildZendeskUrl(params.subdomain, '/search/export')
return `${url}?${query}` return `${url}?${query}`
}, },
method: 'GET', method: 'GET',
@@ -125,19 +116,16 @@ export const zendeskSearchTool: ToolConfig<ZendeskSearchParams, ZendeskSearchRes
const data = await response.json() const data = await response.json()
const results = data.results || [] const results = data.results || []
const paging = extractCursorPagingInfo(data)
return { return {
success: true, success: true,
output: { output: {
results, results,
paging: { paging,
next_page: data.next_page ?? null,
previous_page: data.previous_page ?? null,
count: data.count || results.length,
},
metadata: { metadata: {
total_returned: results.length, total_returned: results.length,
has_more: !!data.next_page, has_more: paging.has_more,
}, },
success: true, success: true,
}, },

View File

@@ -22,9 +22,9 @@ export interface ZendeskSearchUsersResponse {
output: { output: {
users: any[] users: any[]
paging?: { paging?: {
after_cursor: string | null
has_more: boolean
next_page?: string | null next_page?: string | null
previous_page?: string | null
count: number
} }
metadata: { metadata: {
total_returned: number total_returned: number
@@ -84,7 +84,7 @@ export const zendeskSearchUsersTool: ToolConfig<
type: 'string', type: 'string',
required: false, required: false,
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Page number as a string (e.g., "1", "2")', description: 'Page number for pagination (1-based)',
}, },
}, },
@@ -93,8 +93,8 @@ export const zendeskSearchUsersTool: ToolConfig<
const queryParams = new URLSearchParams() const queryParams = new URLSearchParams()
if (params.query) queryParams.append('query', params.query) if (params.query) queryParams.append('query', params.query)
if (params.externalId) queryParams.append('external_id', params.externalId) if (params.externalId) queryParams.append('external_id', params.externalId)
if (params.page) queryParams.append('page', params.page)
if (params.perPage) queryParams.append('per_page', params.perPage) if (params.perPage) queryParams.append('per_page', params.perPage)
if (params.page) queryParams.append('page', params.page)
const query = queryParams.toString() const query = queryParams.toString()
const url = buildZendeskUrl(params.subdomain, '/users/search') const url = buildZendeskUrl(params.subdomain, '/users/search')
@@ -119,19 +119,22 @@ export const zendeskSearchUsersTool: ToolConfig<
const data = await response.json() const data = await response.json()
const users = data.users || [] const users = data.users || []
const hasMore = data.next_page !== null && data.next_page !== undefined
return { return {
success: true, success: true,
output: { output: {
users, users,
// /users/search uses offset pagination (page/per_page), not cursor pagination.
// after_cursor is always null; use next_page URL or page param for subsequent pages.
paging: { paging: {
after_cursor: null,
has_more: hasMore,
next_page: data.next_page ?? null, next_page: data.next_page ?? null,
previous_page: data.previous_page ?? null,
count: data.count || users.length,
}, },
metadata: { metadata: {
total_returned: users.length, total_returned: users.length,
has_more: !!data.next_page, has_more: hasMore,
}, },
success: true, success: true,
}, },

View File

@@ -11,14 +11,14 @@ export interface ZendeskBaseParams {
} }
export interface ZendeskPaginationParams { export interface ZendeskPaginationParams {
page?: string
perPage?: string perPage?: string
pageAfter?: string
} }
export interface ZendeskPagingInfo { export interface ZendeskPagingInfo {
after_cursor: string | null
has_more: boolean
next_page?: string | null next_page?: string | null
previous_page?: string | null
count: number
} }
export interface ZendeskListMetadata { export interface ZendeskListMetadata {
@@ -50,6 +50,32 @@ export function handleZendeskError(data: any, status: number, operation: string)
throw new Error(`Zendesk ${operation} failed: ${errorMessage}`) throw new Error(`Zendesk ${operation} failed: ${errorMessage}`)
} }
/**
* Appends cursor-based pagination query params.
* Zendesk uses bracket notation: `page[size]` and `page[after]`.
*/
export function appendCursorPaginationParams(
queryParams: URLSearchParams,
params: ZendeskPaginationParams
): void {
if (params.perPage) queryParams.append('page[size]', params.perPage)
if (params.pageAfter) queryParams.append('page[after]', params.pageAfter)
}
/**
* Extracts cursor-based pagination info from Zendesk API response.
* Zendesk cursor-based responses include `meta.after_cursor`, `meta.has_more`, and `links.next`.
*/
export function extractCursorPagingInfo(data: Record<string, unknown>): ZendeskPagingInfo {
const meta = (data.meta as Record<string, unknown>) || {}
const links = (data.links as Record<string, unknown>) || {}
return {
after_cursor: (meta.after_cursor as string) ?? null,
has_more: Boolean(meta.has_more),
next_page: (links.next as string) ?? null,
}
}
/** /**
* Output definition for the "via" object in ticket responses. * Output definition for the "via" object in ticket responses.
* Contains information about how the ticket was created. * Contains information about how the ticket was created.
@@ -377,13 +403,13 @@ export const ORGANIZATION_OUTPUT_PROPERTIES = {
* Pagination output properties for list endpoints * Pagination output properties for list endpoints
*/ */
export const PAGING_OUTPUT_PROPERTIES = { export const PAGING_OUTPUT_PROPERTIES = {
next_page: { type: 'string', description: 'URL for next page of results', optional: true }, after_cursor: {
previous_page: {
type: 'string', type: 'string',
description: 'URL for previous page of results', description: 'Cursor for fetching the next page of results',
optional: true, optional: true,
}, },
count: { type: 'number', description: 'Total count of items' }, has_more: { type: 'boolean', description: 'Whether more results are available' },
next_page: { type: 'string', description: 'URL for next page of results', optional: true },
} as const satisfies Record<string, OutputProperty> } as const satisfies Record<string, OutputProperty>
/** /**
@@ -391,7 +417,7 @@ export const PAGING_OUTPUT_PROPERTIES = {
*/ */
export const PAGING_OUTPUT: OutputProperty = { export const PAGING_OUTPUT: OutputProperty = {
type: 'object', type: 'object',
description: 'Pagination information', description: 'Cursor-based pagination information',
properties: PAGING_OUTPUT_PROPERTIES, properties: PAGING_OUTPUT_PROPERTIES,
} }

View File

@@ -13,7 +13,7 @@
"glob": "13.0.0", "glob": "13.0.0",
"husky": "9.1.7", "husky": "9.1.7",
"lint-staged": "16.0.0", "lint-staged": "16.0.0",
"turbo": "2.8.3", "turbo": "2.8.9",
}, },
}, },
"apps/docs": { "apps/docs": {
@@ -3437,19 +3437,19 @@
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
"turbo": ["turbo@2.8.3", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.3", "turbo-darwin-arm64": "2.8.3", "turbo-linux-64": "2.8.3", "turbo-linux-arm64": "2.8.3", "turbo-windows-64": "2.8.3", "turbo-windows-arm64": "2.8.3" }, "bin": { "turbo": "bin/turbo" } }, "sha512-8Osxz5Tu/Dw2kb31EAY+nhq/YZ3wzmQSmYa1nIArqxgCAldxv9TPlrAiaBUDVnKA4aiPn0OFBD1ACcpc5VFOAQ=="], "turbo": ["turbo@2.8.9", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.9", "turbo-darwin-arm64": "2.8.9", "turbo-linux-64": "2.8.9", "turbo-linux-arm64": "2.8.9", "turbo-windows-64": "2.8.9", "turbo-windows-arm64": "2.8.9" }, "bin": { "turbo": "bin/turbo" } }, "sha512-G+Mq8VVQAlpz/0HTsxiNNk/xywaHGl+dk1oiBREgOEVCCDjXInDlONWUn5srRnC9s5tdHTFD1bx1N19eR4hI+g=="],
"turbo-darwin-64": ["turbo-darwin-64@2.8.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-4kXRLfcygLOeNcP6JquqRLmGB/ATjjfehiojL2dJkL7GFm3SPSXbq7oNj8UbD8XriYQ5hPaSuz59iF1ijPHkTw=="], "turbo-darwin-64": ["turbo-darwin-64@2.8.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-KnCw1ZI9KTnEAhdI9avZrnZ/z4wsM++flMA1w8s8PKOqi5daGpFV36qoPafg4S8TmYMe52JPWEoFr0L+lQ5JIw=="],
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xF7uCeC0UY0Hrv/tqax0BMbFlVP1J/aRyeGQPZT4NjvIPj8gSPDgFhfkfz06DhUwDg5NgMo04uiSkAWE8WB/QQ=="], "turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CbD5Y2NKJKBXTOZ7z7Cc7vGlFPZkYjApA7ri9lH4iFwKV1X7MoZswh9gyRLetXYWImVX1BqIvP8KftulJg/wIA=="],
"turbo-linux-64": ["turbo-linux-64@2.8.3", "", { "os": "linux", "cpu": "x64" }, "sha512-vxMDXwaOjweW/4etY7BxrXCSkvtwh0PbwVafyfT1Ww659SedUxd5rM3V2ZCmbwG8NiCfY7d6VtxyHx3Wh1GoZA=="], "turbo-linux-64": ["turbo-linux-64@2.8.9", "", { "os": "linux", "cpu": "x64" }, "sha512-OXC9HdCtsHvyH+5KUoH8ds+p5WU13vdif0OPbsFzZca4cUXMwKA3HWwUuCgQetk0iAE4cscXpi/t8A263n3VTg=="],
"turbo-linux-arm64": ["turbo-linux-arm64@2.8.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-mQX7uYBZFkuPLLlKaNe9IjR1JIef4YvY8f21xFocvttXvdPebnq3PK1Zjzl9A1zun2BEuWNUwQIL8lgvN9Pm3Q=="], "turbo-linux-arm64": ["turbo-linux-arm64@2.8.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-yI5n8jNXiFA6+CxnXG0gO7h5ZF1+19K8uO3/kXPQmyl37AdiA7ehKJQOvf9OPAnmkGDHcF2HSCPltabERNRmug=="],
"turbo-windows-64": ["turbo-windows-64@2.8.3", "", { "os": "win32", "cpu": "x64" }, "sha512-YLGEfppGxZj3VWcNOVa08h6ISsVKiG85aCAWosOKNUjb6yErWEuydv6/qImRJUI+tDLvDvW7BxopAkujRnWCrw=="], "turbo-windows-64": ["turbo-windows-64@2.8.9", "", { "os": "win32", "cpu": "x64" }, "sha512-/OztzeGftJAg258M/9vK2ZCkUKUzqrWXJIikiD2pm8TlqHcIYUmepDbyZSDfOiUjMy6NzrLFahpNLnY7b5vNgg=="],
"turbo-windows-arm64": ["turbo-windows-arm64@2.8.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-afTUGKBRmOJU1smQSBnFGcbq0iabAPwh1uXu2BVk7BREg30/1gMnJh9DFEQTah+UD3n3ru8V55J83RQNFfqoyw=="], "turbo-windows-arm64": ["turbo-windows-arm64@2.8.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-xZ2VTwVTjIqpFZKN4UBxDHCPM3oJ2J5cpRzCBSmRpJ/Pn33wpiYjs+9FB2E03svKaD04/lSSLlEUej0UYsugfg=="],
"tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="], "tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],

View File

@@ -42,7 +42,7 @@
"glob": "13.0.0", "glob": "13.0.0",
"husky": "9.1.7", "husky": "9.1.7",
"lint-staged": "16.0.0", "lint-staged": "16.0.0",
"turbo": "2.8.3" "turbo": "2.8.9"
}, },
"lint-staged": { "lint-staged": {
"*.{js,jsx,ts,tsx,json,css,scss}": [ "*.{js,jsx,ts,tsx,json,css,scss}": [