mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-12 07:24:55 -05:00
Compare commits
53 Commits
feat/atlas
...
v0.5.86
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50585273ce | ||
|
|
654cb2b407 | ||
|
|
6c66521d64 | ||
|
|
479cd347ad | ||
|
|
a3a99eda19 | ||
|
|
1a66d48add | ||
|
|
46822e91f3 | ||
|
|
2bb68335ee | ||
|
|
8528fbe2d2 | ||
|
|
31fdd2be13 | ||
|
|
028bc652c2 | ||
|
|
c6bf5cd58c | ||
|
|
11dc18a80d | ||
|
|
ab4e9dc72f | ||
|
|
1c58c35bd8 | ||
|
|
d63a5cb504 | ||
|
|
8bd5d41723 | ||
|
|
c12931bc50 | ||
|
|
e9c4251c1c | ||
|
|
cc2be33d6b | ||
|
|
45371e521e | ||
|
|
0ce0f98aa5 | ||
|
|
dff1c9d083 | ||
|
|
b09f683072 | ||
|
|
a8bb0db660 | ||
|
|
af82820a28 | ||
|
|
4372841797 | ||
|
|
5e8c843241 | ||
|
|
7bf3d73ee6 | ||
|
|
7ffc11a738 | ||
|
|
be578e2ed7 | ||
|
|
f415e5edc4 | ||
|
|
13a6e6c3fa | ||
|
|
f5ab7f21ae | ||
|
|
bfb6fffe38 | ||
|
|
4fbec0a43f | ||
|
|
585f5e365b | ||
|
|
3792bdd252 | ||
|
|
eb5d1f3e5b | ||
|
|
54ab82c8dd | ||
|
|
f895bf469b | ||
|
|
dd3209af06 | ||
|
|
b6ba3b50a7 | ||
|
|
b304233062 | ||
|
|
57e4b49bd6 | ||
|
|
e12dd204ed | ||
|
|
3d9d9cbc54 | ||
|
|
0f4ec962ad | ||
|
|
4827866f9a | ||
|
|
3e697d9ed9 | ||
|
|
4431a1a484 | ||
|
|
4d1a9a3f22 | ||
|
|
eb07a080fb |
@@ -41,6 +41,9 @@ Diese Tastenkombinationen wechseln zwischen den Panel-Tabs auf der rechten Seite
|
|||||||
|
|
||||||
| Tastenkombination | Aktion |
|
| Tastenkombination | Aktion |
|
||||||
|----------|--------|
|
|----------|--------|
|
||||||
|
| `C` | Copilot-Tab fokussieren |
|
||||||
|
| `T` | Toolbar-Tab fokussieren |
|
||||||
|
| `E` | Editor-Tab fokussieren |
|
||||||
| `Mod` + `F` | Toolbar-Suche fokussieren |
|
| `Mod` + `F` | Toolbar-Suche fokussieren |
|
||||||
|
|
||||||
## Globale Navigation
|
## Globale Navigation
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ These shortcuts switch between panel tabs on the right side of the canvas.
|
|||||||
|
|
||||||
| Shortcut | Action |
|
| Shortcut | Action |
|
||||||
|----------|--------|
|
|----------|--------|
|
||||||
|
| `C` | Focus Copilot tab |
|
||||||
|
| `T` | Focus Toolbar tab |
|
||||||
|
| `E` | Focus Editor tab |
|
||||||
| `Mod` + `F` | Focus Toolbar search |
|
| `Mod` + `F` | Focus Toolbar search |
|
||||||
|
|
||||||
## Global Navigation
|
## Global Navigation
|
||||||
|
|||||||
@@ -88,8 +88,7 @@ Update a Confluence page using the Confluence API.
|
|||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | Timestamp of update |
|
||||||
| `success` | boolean | Operation success status |
|
|
||||||
| `pageId` | string | Confluence page ID |
|
| `pageId` | string | Confluence page ID |
|
||||||
| `title` | string | Updated page title |
|
| `title` | string | Updated page title |
|
||||||
| `status` | string | Page status |
|
| `status` | string | Page status |
|
||||||
@@ -111,6 +110,7 @@ Update a Confluence page using the Confluence API.
|
|||||||
| ↳ `authorId` | string | Account ID of the version author |
|
| ↳ `authorId` | string | Account ID of the version author |
|
||||||
| ↳ `createdAt` | string | ISO 8601 timestamp of version creation |
|
| ↳ `createdAt` | string | ISO 8601 timestamp of version creation |
|
||||||
| `url` | string | URL to view the page in Confluence |
|
| `url` | string | URL to view the page in Confluence |
|
||||||
|
| `success` | boolean | Update operation success status |
|
||||||
|
|
||||||
### `confluence_create_page`
|
### `confluence_create_page`
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ Create a new page in a Confluence space.
|
|||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | Timestamp of creation |
|
||||||
| `pageId` | string | Created page ID |
|
| `pageId` | string | Created page ID |
|
||||||
| `title` | string | Page title |
|
| `title` | string | Page title |
|
||||||
| `status` | string | Page status |
|
| `status` | string | Page status |
|
||||||
@@ -172,9 +172,9 @@ Delete a Confluence page. By default moves to trash; use purge=true to permanent
|
|||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | Timestamp of deletion |
|
||||||
| `deleted` | boolean | Deletion status |
|
|
||||||
| `pageId` | string | Deleted page ID |
|
| `pageId` | string | Deleted page ID |
|
||||||
|
| `deleted` | boolean | Deletion status |
|
||||||
|
|
||||||
### `confluence_list_pages_in_space`
|
### `confluence_list_pages_in_space`
|
||||||
|
|
||||||
@@ -358,10 +358,10 @@ List all custom properties (metadata) attached to a Confluence page.
|
|||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | ISO 8601 timestamp of the operation |
|
||||||
| `pageId` | string | ID of the page |
|
| `pageId` | string | ID of the page |
|
||||||
| `properties` | array | Array of content properties |
|
| `properties` | array | Array of content properties |
|
||||||
| ↳ `id` | string | Unique property identifier |
|
| ↳ `id` | string | Property ID |
|
||||||
| ↳ `key` | string | Property key/name |
|
| ↳ `key` | string | Property key |
|
||||||
| ↳ `value` | json | Property value \(can be any JSON\) |
|
| ↳ `value` | json | Property value \(can be any JSON\) |
|
||||||
| ↳ `version` | object | Property version information |
|
| ↳ `version` | object | Version information |
|
||||||
| ↳ `number` | number | Version number |
|
| ↳ `number` | number | Version number |
|
||||||
| ↳ `message` | string | Version message |
|
| ↳ `message` | string | Version message |
|
||||||
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
||||||
@@ -388,72 +388,16 @@ Create a new custom property (metadata) on a Confluence page.
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | ISO 8601 timestamp of the operation |
|
||||||
| `id` | string | Unique property identifier |
|
|
||||||
| `key` | string | Property key/name |
|
|
||||||
| `value` | json | Property value \(can be any JSON\) |
|
|
||||||
| `version` | object | Property version information |
|
|
||||||
| ↳ `number` | number | Version number |
|
|
||||||
| ↳ `message` | string | Version message |
|
|
||||||
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
|
||||||
| ↳ `authorId` | string | Account ID of the version author |
|
|
||||||
| ↳ `createdAt` | string | ISO 8601 timestamp of version creation |
|
|
||||||
| `pageId` | string | ID of the page |
|
| `pageId` | string | ID of the page |
|
||||||
| `propertyId` | string | ID of the created property |
|
| `propertyId` | string | ID of the created property |
|
||||||
|
| `key` | string | Property key |
|
||||||
### `confluence_update_page_property`
|
| `value` | json | Property value |
|
||||||
|
| `version` | object | Version information |
|
||||||
Update an existing content property on a Confluence page.
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Confluence domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `pageId` | string | Yes | The ID of the page containing the property |
|
|
||||||
| `propertyId` | string | Yes | The ID of the property to update |
|
|
||||||
| `key` | string | Yes | The key/name of the property |
|
|
||||||
| `value` | json | Yes | The new value for the property \(can be any JSON value\) |
|
|
||||||
| `versionNumber` | number | Yes | The current version number of the property \(for conflict prevention\) |
|
|
||||||
| `cloudId` | string | No | Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain. |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
|
||||||
| `id` | string | Unique property identifier |
|
|
||||||
| `key` | string | Property key/name |
|
|
||||||
| `value` | json | Property value \(can be any JSON\) |
|
|
||||||
| `version` | object | Property version information |
|
|
||||||
| ↳ `number` | number | Version number |
|
| ↳ `number` | number | Version number |
|
||||||
| ↳ `message` | string | Version message |
|
| ↳ `message` | string | Version message |
|
||||||
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
||||||
| ↳ `authorId` | string | Account ID of the version author |
|
| ↳ `authorId` | string | Account ID of the version author |
|
||||||
| ↳ `createdAt` | string | ISO 8601 timestamp of version creation |
|
| ↳ `createdAt` | string | ISO 8601 timestamp of version creation |
|
||||||
| `pageId` | string | ID of the page |
|
|
||||||
| `propertyId` | string | ID of the updated property |
|
|
||||||
|
|
||||||
### `confluence_delete_page_property`
|
|
||||||
|
|
||||||
Delete a content property from a Confluence page by its property ID.
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Confluence domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `pageId` | string | Yes | The ID of the page containing the property |
|
|
||||||
| `propertyId` | string | Yes | The ID of the property to delete |
|
|
||||||
| `cloudId` | string | No | Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain. |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
|
||||||
| `pageId` | string | ID of the page |
|
|
||||||
| `propertyId` | string | ID of the deleted property |
|
|
||||||
| `deleted` | boolean | Deletion status |
|
|
||||||
|
|
||||||
### `confluence_search`
|
### `confluence_search`
|
||||||
|
|
||||||
@@ -472,7 +416,7 @@ Search for content across Confluence pages, blog posts, and other content.
|
|||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | Timestamp of search |
|
||||||
| `results` | array | Array of search results |
|
| `results` | array | Array of search results |
|
||||||
| ↳ `id` | string | Unique content identifier |
|
| ↳ `id` | string | Unique content identifier |
|
||||||
| ↳ `title` | string | Content title |
|
| ↳ `title` | string | Content title |
|
||||||
@@ -546,29 +490,19 @@ List all blog posts across all accessible Confluence spaces.
|
|||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | ISO 8601 timestamp of the operation |
|
||||||
| `blogPosts` | array | Array of blog posts |
|
| `blogPosts` | array | Array of blog posts |
|
||||||
| ↳ `id` | string | Unique blog post identifier |
|
| ↳ `id` | string | Blog post ID |
|
||||||
| ↳ `title` | string | Blog post title |
|
| ↳ `title` | string | Blog post title |
|
||||||
| ↳ `status` | string | Blog post status \(e.g., current, draft\) |
|
| ↳ `status` | string | Blog post status |
|
||||||
| ↳ `spaceId` | string | ID of the space containing the blog post |
|
| ↳ `spaceId` | string | Space ID |
|
||||||
| ↳ `authorId` | string | Account ID of the blog post author |
|
| ↳ `authorId` | string | Author account ID |
|
||||||
| ↳ `createdAt` | string | ISO 8601 timestamp when the blog post was created |
|
| ↳ `createdAt` | string | Creation timestamp |
|
||||||
| ↳ `version` | object | Blog post version information |
|
| ↳ `version` | object | Version information |
|
||||||
| ↳ `number` | number | Version number |
|
| ↳ `number` | number | Version number |
|
||||||
| ↳ `message` | string | Version message |
|
| ↳ `message` | string | Version message |
|
||||||
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
||||||
| ↳ `authorId` | string | Account ID of the version author |
|
| ↳ `authorId` | string | Account ID of the version author |
|
||||||
| ↳ `createdAt` | string | ISO 8601 timestamp of version creation |
|
| ↳ `createdAt` | string | ISO 8601 timestamp of version creation |
|
||||||
| ↳ `body` | object | Blog post body content |
|
| ↳ `webUrl` | string | URL to view the blog post |
|
||||||
| ↳ `storage` | object | Body in storage format \(Confluence markup\) |
|
|
||||||
| ↳ `value` | string | The content value in the specified format |
|
|
||||||
| ↳ `representation` | string | Content representation type |
|
|
||||||
| ↳ `view` | object | Body in view format \(rendered HTML\) |
|
|
||||||
| ↳ `value` | string | The content value in the specified format |
|
|
||||||
| ↳ `representation` | string | Content representation type |
|
|
||||||
| ↳ `atlas_doc_format` | object | Body in Atlassian Document Format \(ADF\) |
|
|
||||||
| ↳ `value` | string | The content value in the specified format |
|
|
||||||
| ↳ `representation` | string | Content representation type |
|
|
||||||
| ↳ `webUrl` | string | URL to view the blog post in Confluence |
|
|
||||||
| `nextCursor` | string | Cursor for fetching the next page of results |
|
| `nextCursor` | string | Cursor for fetching the next page of results |
|
||||||
|
|
||||||
### `confluence_get_blogpost`
|
### `confluence_get_blogpost`
|
||||||
@@ -589,19 +523,19 @@ Get a specific Confluence blog post by ID, including its content.
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | ISO 8601 timestamp of the operation |
|
||||||
| `id` | string | Unique blog post identifier |
|
| `id` | string | Blog post ID |
|
||||||
| `title` | string | Blog post title |
|
| `title` | string | Blog post title |
|
||||||
| `status` | string | Blog post status \(e.g., current, draft\) |
|
| `status` | string | Blog post status |
|
||||||
| `spaceId` | string | ID of the space containing the blog post |
|
| `spaceId` | string | Space ID |
|
||||||
| `authorId` | string | Account ID of the blog post author |
|
| `authorId` | string | Author account ID |
|
||||||
| `createdAt` | string | ISO 8601 timestamp when the blog post was created |
|
| `createdAt` | string | Creation timestamp |
|
||||||
| `version` | object | Blog post version information |
|
| `version` | object | Version information |
|
||||||
| ↳ `number` | number | Version number |
|
| ↳ `number` | number | Version number |
|
||||||
| ↳ `message` | string | Version message |
|
| ↳ `message` | string | Version message |
|
||||||
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
||||||
| ↳ `authorId` | string | Account ID of the version author |
|
| ↳ `authorId` | string | Account ID of the version author |
|
||||||
| ↳ `createdAt` | string | ISO 8601 timestamp of version creation |
|
| ↳ `createdAt` | string | ISO 8601 timestamp of version creation |
|
||||||
| `body` | object | Blog post body content |
|
| `body` | object | Blog post body content in requested format\(s\) |
|
||||||
| ↳ `storage` | object | Body in storage format \(Confluence markup\) |
|
| ↳ `storage` | object | Body in storage format \(Confluence markup\) |
|
||||||
| ↳ `value` | string | The content value in the specified format |
|
| ↳ `value` | string | The content value in the specified format |
|
||||||
| ↳ `representation` | string | Content representation type |
|
| ↳ `representation` | string | Content representation type |
|
||||||
@@ -611,7 +545,7 @@ Get a specific Confluence blog post by ID, including its content.
|
|||||||
| ↳ `atlas_doc_format` | object | Body in Atlassian Document Format \(ADF\) |
|
| ↳ `atlas_doc_format` | object | Body in Atlassian Document Format \(ADF\) |
|
||||||
| ↳ `value` | string | The content value in the specified format |
|
| ↳ `value` | string | The content value in the specified format |
|
||||||
| ↳ `representation` | string | Content representation type |
|
| ↳ `representation` | string | Content representation type |
|
||||||
| `webUrl` | string | URL to view the blog post in Confluence |
|
| `webUrl` | string | URL to view the blog post |
|
||||||
|
|
||||||
### `confluence_create_blogpost`
|
### `confluence_create_blogpost`
|
||||||
|
|
||||||
@@ -633,18 +567,11 @@ Create a new blog post in a Confluence space.
|
|||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | ISO 8601 timestamp of the operation |
|
||||||
| `id` | string | Unique blog post identifier |
|
| `id` | string | Created blog post ID |
|
||||||
| `title` | string | Blog post title |
|
| `title` | string | Blog post title |
|
||||||
| `status` | string | Blog post status \(e.g., current, draft\) |
|
| `status` | string | Blog post status |
|
||||||
| `spaceId` | string | ID of the space containing the blog post |
|
| `spaceId` | string | Space ID |
|
||||||
| `authorId` | string | Account ID of the blog post author |
|
| `authorId` | string | Author account ID |
|
||||||
| `createdAt` | string | ISO 8601 timestamp when the blog post was created |
|
|
||||||
| `version` | object | Blog post version information |
|
|
||||||
| ↳ `number` | number | Version number |
|
|
||||||
| ↳ `message` | string | Version message |
|
|
||||||
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
|
||||||
| ↳ `authorId` | string | Account ID of the version author |
|
|
||||||
| ↳ `createdAt` | string | ISO 8601 timestamp of version creation |
|
|
||||||
| `body` | object | Blog post body content |
|
| `body` | object | Blog post body content |
|
||||||
| ↳ `storage` | object | Body in storage format \(Confluence markup\) |
|
| ↳ `storage` | object | Body in storage format \(Confluence markup\) |
|
||||||
| ↳ `value` | string | The content value in the specified format |
|
| ↳ `value` | string | The content value in the specified format |
|
||||||
@@ -655,71 +582,13 @@ Create a new blog post in a Confluence space.
|
|||||||
| ↳ `atlas_doc_format` | object | Body in Atlassian Document Format \(ADF\) |
|
| ↳ `atlas_doc_format` | object | Body in Atlassian Document Format \(ADF\) |
|
||||||
| ↳ `value` | string | The content value in the specified format |
|
| ↳ `value` | string | The content value in the specified format |
|
||||||
| ↳ `representation` | string | Content representation type |
|
| ↳ `representation` | string | Content representation type |
|
||||||
| `webUrl` | string | URL to view the blog post in Confluence |
|
|
||||||
|
|
||||||
### `confluence_update_blogpost`
|
|
||||||
|
|
||||||
Update an existing Confluence blog post title, content, or status.
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Confluence domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `blogPostId` | string | Yes | The ID of the blog post to update |
|
|
||||||
| `title` | string | No | New title for the blog post |
|
|
||||||
| `content` | string | No | New content for the blog post in Confluence storage format |
|
|
||||||
| `status` | string | No | Blog post status: current or draft |
|
|
||||||
| `cloudId` | string | No | Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain. |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
|
||||||
| `id` | string | Unique blog post identifier |
|
|
||||||
| `title` | string | Blog post title |
|
|
||||||
| `status` | string | Blog post status \(e.g., current, draft\) |
|
|
||||||
| `spaceId` | string | ID of the space containing the blog post |
|
|
||||||
| `authorId` | string | Account ID of the blog post author |
|
|
||||||
| `createdAt` | string | ISO 8601 timestamp when the blog post was created |
|
|
||||||
| `version` | object | Blog post version information |
|
| `version` | object | Blog post version information |
|
||||||
| ↳ `number` | number | Version number |
|
| ↳ `number` | number | Version number |
|
||||||
| ↳ `message` | string | Version message |
|
| ↳ `message` | string | Version message |
|
||||||
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
||||||
| ↳ `authorId` | string | Account ID of the version author |
|
| ↳ `authorId` | string | Account ID of the version author |
|
||||||
| ↳ `createdAt` | string | ISO 8601 timestamp of version creation |
|
| ↳ `createdAt` | string | ISO 8601 timestamp of version creation |
|
||||||
| `body` | object | Blog post body content |
|
| `webUrl` | string | URL to view the blog post |
|
||||||
| ↳ `storage` | object | Body in storage format \(Confluence markup\) |
|
|
||||||
| ↳ `value` | string | The content value in the specified format |
|
|
||||||
| ↳ `representation` | string | Content representation type |
|
|
||||||
| ↳ `view` | object | Body in view format \(rendered HTML\) |
|
|
||||||
| ↳ `value` | string | The content value in the specified format |
|
|
||||||
| ↳ `representation` | string | Content representation type |
|
|
||||||
| ↳ `atlas_doc_format` | object | Body in Atlassian Document Format \(ADF\) |
|
|
||||||
| ↳ `value` | string | The content value in the specified format |
|
|
||||||
| ↳ `representation` | string | Content representation type |
|
|
||||||
| `webUrl` | string | URL to view the blog post in Confluence |
|
|
||||||
|
|
||||||
### `confluence_delete_blogpost`
|
|
||||||
|
|
||||||
Delete a Confluence blog post.
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Confluence domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `blogPostId` | string | Yes | The ID of the blog post to delete |
|
|
||||||
| `cloudId` | string | No | Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain. |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
|
||||||
| `deleted` | boolean | Deletion status |
|
|
||||||
| `blogPostId` | string | Deleted blog post ID |
|
|
||||||
|
|
||||||
### `confluence_list_blogposts_in_space`
|
### `confluence_list_blogposts_in_space`
|
||||||
|
|
||||||
@@ -743,13 +612,13 @@ List all blog posts within a specific Confluence space.
|
|||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | ISO 8601 timestamp of the operation |
|
||||||
| `blogPosts` | array | Array of blog posts in the space |
|
| `blogPosts` | array | Array of blog posts in the space |
|
||||||
| ↳ `id` | string | Unique blog post identifier |
|
| ↳ `id` | string | Blog post ID |
|
||||||
| ↳ `title` | string | Blog post title |
|
| ↳ `title` | string | Blog post title |
|
||||||
| ↳ `status` | string | Blog post status \(e.g., current, draft\) |
|
| ↳ `status` | string | Blog post status |
|
||||||
| ↳ `spaceId` | string | ID of the space containing the blog post |
|
| ↳ `spaceId` | string | Space ID |
|
||||||
| ↳ `authorId` | string | Account ID of the blog post author |
|
| ↳ `authorId` | string | Author account ID |
|
||||||
| ↳ `createdAt` | string | ISO 8601 timestamp when the blog post was created |
|
| ↳ `createdAt` | string | Creation timestamp |
|
||||||
| ↳ `version` | object | Blog post version information |
|
| ↳ `version` | object | Version information |
|
||||||
| ↳ `number` | number | Version number |
|
| ↳ `number` | number | Version number |
|
||||||
| ↳ `message` | string | Version message |
|
| ↳ `message` | string | Version message |
|
||||||
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
||||||
@@ -765,7 +634,7 @@ List all blog posts within a specific Confluence space.
|
|||||||
| ↳ `atlas_doc_format` | object | Body in Atlassian Document Format \(ADF\) |
|
| ↳ `atlas_doc_format` | object | Body in Atlassian Document Format \(ADF\) |
|
||||||
| ↳ `value` | string | The content value in the specified format |
|
| ↳ `value` | string | The content value in the specified format |
|
||||||
| ↳ `representation` | string | Content representation type |
|
| ↳ `representation` | string | Content representation type |
|
||||||
| ↳ `webUrl` | string | URL to view the blog post in Confluence |
|
| ↳ `webUrl` | string | URL to view the blog post |
|
||||||
| `nextCursor` | string | Cursor for fetching the next page of results |
|
| `nextCursor` | string | Cursor for fetching the next page of results |
|
||||||
|
|
||||||
### `confluence_create_comment`
|
### `confluence_create_comment`
|
||||||
@@ -785,7 +654,7 @@ Add a comment to a Confluence page.
|
|||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | Timestamp of creation |
|
||||||
| `commentId` | string | Created comment ID |
|
| `commentId` | string | Created comment ID |
|
||||||
| `pageId` | string | Page ID |
|
| `pageId` | string | Page ID |
|
||||||
|
|
||||||
@@ -846,9 +715,9 @@ Update an existing comment on a Confluence page.
|
|||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | Timestamp of update |
|
||||||
| `updated` | boolean | Update status |
|
|
||||||
| `commentId` | string | Updated comment ID |
|
| `commentId` | string | Updated comment ID |
|
||||||
|
| `updated` | boolean | Update status |
|
||||||
|
|
||||||
### `confluence_delete_comment`
|
### `confluence_delete_comment`
|
||||||
|
|
||||||
@@ -866,9 +735,9 @@ Delete a comment from a Confluence page.
|
|||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | Timestamp of deletion |
|
||||||
| `deleted` | boolean | Deletion status |
|
|
||||||
| `commentId` | string | Deleted comment ID |
|
| `commentId` | string | Deleted comment ID |
|
||||||
|
| `deleted` | boolean | Deletion status |
|
||||||
|
|
||||||
### `confluence_upload_attachment`
|
### `confluence_upload_attachment`
|
||||||
|
|
||||||
@@ -889,7 +758,7 @@ Upload a file as an attachment to a Confluence page.
|
|||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | Timestamp of upload |
|
||||||
| `attachmentId` | string | Uploaded attachment ID |
|
| `attachmentId` | string | Uploaded attachment ID |
|
||||||
| `title` | string | Attachment file name |
|
| `title` | string | Attachment file name |
|
||||||
| `fileSize` | number | File size in bytes |
|
| `fileSize` | number | File size in bytes |
|
||||||
@@ -951,9 +820,9 @@ Delete an attachment from a Confluence page (moves to trash).
|
|||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | Timestamp of deletion |
|
||||||
| `deleted` | boolean | Deletion status |
|
|
||||||
| `attachmentId` | string | Deleted attachment ID |
|
| `attachmentId` | string | Deleted attachment ID |
|
||||||
|
| `deleted` | boolean | Deletion status |
|
||||||
|
|
||||||
### `confluence_list_labels`
|
### `confluence_list_labels`
|
||||||
|
|
||||||
@@ -973,7 +842,7 @@ List all labels on a Confluence page.
|
|||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
| `ts` | string | Timestamp of retrieval |
|
||||||
| `labels` | array | Array of labels on the page |
|
| `labels` | array | Array of labels on the page |
|
||||||
| ↳ `id` | string | Unique label identifier |
|
| ↳ `id` | string | Unique label identifier |
|
||||||
| ↳ `name` | string | Label name |
|
| ↳ `name` | string | Label name |
|
||||||
@@ -1003,90 +872,6 @@ Add a label to a Confluence page for organization and categorization.
|
|||||||
| `labelName` | string | Name of the added label |
|
| `labelName` | string | Name of the added label |
|
||||||
| `labelId` | string | ID of the added label |
|
| `labelId` | string | ID of the added label |
|
||||||
|
|
||||||
### `confluence_delete_label`
|
|
||||||
|
|
||||||
Remove a label from a Confluence page.
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Confluence domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `pageId` | string | Yes | Confluence page ID to remove the label from |
|
|
||||||
| `labelName` | string | Yes | Name of the label to remove |
|
|
||||||
| `cloudId` | string | No | Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain. |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
|
||||||
| `pageId` | string | Page ID the label was removed from |
|
|
||||||
| `labelName` | string | Name of the removed label |
|
|
||||||
| `deleted` | boolean | Deletion status |
|
|
||||||
|
|
||||||
### `confluence_get_pages_by_label`
|
|
||||||
|
|
||||||
Retrieve all pages that have a specific label applied.
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Confluence domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `labelId` | string | Yes | The ID of the label to get pages for |
|
|
||||||
| `limit` | number | No | Maximum number of pages to return \(default: 50, max: 250\) |
|
|
||||||
| `cursor` | string | No | Pagination cursor from previous response |
|
|
||||||
| `cloudId` | string | No | Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain. |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
|
||||||
| `labelId` | string | ID of the label |
|
|
||||||
| `pages` | array | Array of pages with this label |
|
|
||||||
| ↳ `id` | string | Unique page identifier |
|
|
||||||
| ↳ `title` | string | Page title |
|
|
||||||
| ↳ `status` | string | Page status \(e.g., current, archived, trashed, draft\) |
|
|
||||||
| ↳ `spaceId` | string | ID of the space containing the page |
|
|
||||||
| ↳ `parentId` | string | ID of the parent page \(null if top-level\) |
|
|
||||||
| ↳ `authorId` | string | Account ID of the page author |
|
|
||||||
| ↳ `createdAt` | string | ISO 8601 timestamp when the page was created |
|
|
||||||
| ↳ `version` | object | Page version information |
|
|
||||||
| ↳ `number` | number | Version number |
|
|
||||||
| ↳ `message` | string | Version message |
|
|
||||||
| ↳ `minorEdit` | boolean | Whether this is a minor edit |
|
|
||||||
| ↳ `authorId` | string | Account ID of the version author |
|
|
||||||
| ↳ `createdAt` | string | ISO 8601 timestamp of version creation |
|
|
||||||
| `nextCursor` | string | Cursor for fetching the next page of results |
|
|
||||||
|
|
||||||
### `confluence_list_space_labels`
|
|
||||||
|
|
||||||
List all labels associated with a Confluence space.
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Confluence domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `spaceId` | string | Yes | The ID of the Confluence space to list labels from |
|
|
||||||
| `limit` | number | No | Maximum number of labels to return \(default: 25, max: 250\) |
|
|
||||||
| `cursor` | string | No | Pagination cursor from previous response |
|
|
||||||
| `cloudId` | string | No | Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain. |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | ISO 8601 timestamp of the operation |
|
|
||||||
| `spaceId` | string | ID of the space |
|
|
||||||
| `labels` | array | Array of labels on the space |
|
|
||||||
| ↳ `id` | string | Unique label identifier |
|
|
||||||
| ↳ `name` | string | Label name |
|
|
||||||
| ↳ `prefix` | string | Label prefix/type \(e.g., global, my, team\) |
|
|
||||||
| `nextCursor` | string | Cursor for fetching the next page of results |
|
|
||||||
|
|
||||||
### `confluence_get_space`
|
### `confluence_get_space`
|
||||||
|
|
||||||
Get details about a specific Confluence space.
|
Get details about a specific Confluence space.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ With Sim’s Jira Service Management integration, you can create, monitor, and u
|
|||||||
|
|
||||||
## Usage Instructions
|
## Usage Instructions
|
||||||
|
|
||||||
Integrate with Jira Service Management for IT service management. Create and manage service requests, handle customers and organizations, track SLAs, and manage queues. Can also trigger workflows based on Jira Service Management webhook events.
|
Integrate with Jira Service Management for IT service management. Create and manage service requests, handle customers and organizations, track SLAs, and manage queues.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -66,31 +66,6 @@ Get all service desks from Jira Service Management
|
|||||||
| `total` | number | Total number of service desks |
|
| `total` | number | Total number of service desks |
|
||||||
| `isLastPage` | boolean | Whether this is the last page |
|
| `isLastPage` | boolean | Whether this is the last page |
|
||||||
|
|
||||||
### `jsm_get_service_desk`
|
|
||||||
|
|
||||||
Get a specific service desk by ID in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `serviceDeskId` | string | Yes | Service Desk ID \(e.g., "1", "2"\) |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `id` | string | Service desk ID |
|
|
||||||
| `projectId` | string | Associated Jira project ID |
|
|
||||||
| `projectName` | string | Associated project name |
|
|
||||||
| `projectKey` | string | Associated project key |
|
|
||||||
| `name` | string | Service desk name |
|
|
||||||
| `description` | string | Service desk description |
|
|
||||||
| `leadDisplayName` | string | Project lead display name |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
|
|
||||||
### `jsm_get_request_types`
|
### `jsm_get_request_types`
|
||||||
|
|
||||||
Get request types for a service desk in Jira Service Management
|
Get request types for a service desk in Jira Service Management
|
||||||
@@ -126,39 +101,6 @@ Get request types for a service desk in Jira Service Management
|
|||||||
| `total` | number | Total number of request types |
|
| `total` | number | Total number of request types |
|
||||||
| `isLastPage` | boolean | Whether this is the last page |
|
| `isLastPage` | boolean | Whether this is the last page |
|
||||||
|
|
||||||
### `jsm_get_request_type_fields`
|
|
||||||
|
|
||||||
Get the fields required to create a request of a specific type in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `serviceDeskId` | string | Yes | Service Desk ID \(e.g., "1", "2"\) |
|
|
||||||
| `requestTypeId` | string | Yes | Request Type ID \(e.g., "10", "15"\) |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `serviceDeskId` | string | Service desk ID |
|
|
||||||
| `requestTypeId` | string | Request type ID |
|
|
||||||
| `canAddRequestParticipants` | boolean | Whether participants can be added to requests of this type |
|
|
||||||
| `canRaiseOnBehalfOf` | boolean | Whether requests can be raised on behalf of another user |
|
|
||||||
| `requestTypeFields` | array | List of fields for this request type |
|
|
||||||
| ↳ `fieldId` | string | Field identifier \(e.g., summary, description, customfield_10010\) |
|
|
||||||
| ↳ `name` | string | Human-readable field name |
|
|
||||||
| ↳ `description` | string | Help text for the field |
|
|
||||||
| ↳ `required` | boolean | Whether the field is required |
|
|
||||||
| ↳ `visible` | boolean | Whether the field is visible |
|
|
||||||
| ↳ `validValues` | json | Allowed values for select fields |
|
|
||||||
| ↳ `presetValues` | json | Pre-populated values |
|
|
||||||
| ↳ `defaultValues` | json | Default values for the field |
|
|
||||||
| ↳ `jiraSchema` | json | Jira field schema with type, system, custom, customId |
|
|
||||||
|
|
||||||
### `jsm_create_request`
|
### `jsm_create_request`
|
||||||
|
|
||||||
Create a new service request in Jira Service Management
|
Create a new service request in Jira Service Management
|
||||||
@@ -280,59 +222,6 @@ Get multiple service requests from Jira Service Management
|
|||||||
| `total` | number | Total number of requests in current page |
|
| `total` | number | Total number of requests in current page |
|
||||||
| `isLastPage` | boolean | Whether this is the last page |
|
| `isLastPage` | boolean | Whether this is the last page |
|
||||||
|
|
||||||
### `jsm_get_request_status`
|
|
||||||
|
|
||||||
Get status history for a service request in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., SD-123\) |
|
|
||||||
| `start` | number | No | Start index for pagination \(e.g., 0, 50, 100\) |
|
|
||||||
| `limit` | number | No | Maximum results to return \(e.g., 10, 25, 50\) |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `issueIdOrKey` | string | Issue ID or key |
|
|
||||||
| `statuses` | array | Status history entries |
|
|
||||||
| ↳ `status` | string | Status name |
|
|
||||||
| ↳ `statusCategory` | string | Status category \(NEW, INDETERMINATE, DONE\) |
|
|
||||||
| ↳ `statusDate` | json | Status change date with iso8601, friendly, epochMillis |
|
|
||||||
| `total` | number | Total number of status entries |
|
|
||||||
| `isLastPage` | boolean | Whether this is the last page |
|
|
||||||
|
|
||||||
### `jsm_get_request_attachments`
|
|
||||||
|
|
||||||
Get attachments for a service request in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., SD-123\) |
|
|
||||||
| `includeAttachments` | boolean | No | Download attachment file contents and include them as files in the output |
|
|
||||||
| `start` | number | No | Start index for pagination \(e.g., 0, 50, 100\) |
|
|
||||||
| `limit` | number | No | Maximum results to return \(e.g., 10, 25, 50\) |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `issueIdOrKey` | string | Issue ID or key |
|
|
||||||
| `attachments` | array | List of attachments |
|
|
||||||
| `total` | number | Total number of attachments |
|
|
||||||
| `isLastPage` | boolean | Whether this is the last page |
|
|
||||||
| `files` | file[] | Downloaded attachment files \(only when includeAttachments is true\) |
|
|
||||||
|
|
||||||
### `jsm_add_comment`
|
### `jsm_add_comment`
|
||||||
|
|
||||||
Add a comment (public or internal) to a service request in Jira Service Management
|
Add a comment (public or internal) to a service request in Jira Service Management
|
||||||
@@ -452,53 +341,6 @@ Add customers to a service desk in Jira Service Management
|
|||||||
| `serviceDeskId` | string | Service desk ID |
|
| `serviceDeskId` | string | Service desk ID |
|
||||||
| `success` | boolean | Whether customers were added successfully |
|
| `success` | boolean | Whether customers were added successfully |
|
||||||
|
|
||||||
### `jsm_remove_customer`
|
|
||||||
|
|
||||||
Remove customers from a service desk in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `serviceDeskId` | string | Yes | Service Desk ID \(e.g., "1", "2"\) |
|
|
||||||
| `accountIds` | string | No | Comma-separated Atlassian account IDs to remove |
|
|
||||||
| `emails` | string | No | Comma-separated email addresses to remove |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `serviceDeskId` | string | Service desk ID |
|
|
||||||
| `success` | boolean | Whether customers were removed successfully |
|
|
||||||
|
|
||||||
### `jsm_create_customer`
|
|
||||||
|
|
||||||
Create a new customer in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `email` | string | Yes | Email address for the new customer |
|
|
||||||
| `displayName` | string | Yes | Display name for the new customer |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `accountId` | string | Account ID of the created customer |
|
|
||||||
| `displayName` | string | Display name of the created customer |
|
|
||||||
| `emailAddress` | string | Email address of the created customer |
|
|
||||||
| `active` | boolean | Whether the customer account is active |
|
|
||||||
| `timeZone` | string | Customer timezone |
|
|
||||||
| `success` | boolean | Whether the customer was created successfully |
|
|
||||||
|
|
||||||
### `jsm_get_organizations`
|
### `jsm_get_organizations`
|
||||||
|
|
||||||
Get organizations for a service desk in Jira Service Management
|
Get organizations for a service desk in Jira Service Management
|
||||||
@@ -524,26 +366,6 @@ Get organizations for a service desk in Jira Service Management
|
|||||||
| `total` | number | Total number of organizations |
|
| `total` | number | Total number of organizations |
|
||||||
| `isLastPage` | boolean | Whether this is the last page |
|
| `isLastPage` | boolean | Whether this is the last page |
|
||||||
|
|
||||||
### `jsm_get_organization`
|
|
||||||
|
|
||||||
Get a specific organization by ID in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `organizationId` | string | Yes | Organization ID to retrieve |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `id` | string | Organization ID |
|
|
||||||
| `name` | string | Organization name |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
|
|
||||||
### `jsm_create_organization`
|
### `jsm_create_organization`
|
||||||
|
|
||||||
Create a new organization in Jira Service Management
|
Create a new organization in Jira Service Management
|
||||||
@@ -587,119 +409,6 @@ Add an organization to a service desk in Jira Service Management
|
|||||||
| `organizationId` | string | Organization ID added |
|
| `organizationId` | string | Organization ID added |
|
||||||
| `success` | boolean | Whether the operation succeeded |
|
| `success` | boolean | Whether the operation succeeded |
|
||||||
|
|
||||||
### `jsm_remove_organization`
|
|
||||||
|
|
||||||
Remove an organization from a service desk in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `serviceDeskId` | string | Yes | Service Desk ID \(e.g., "1", "2"\) |
|
|
||||||
| `organizationId` | string | Yes | Organization ID to remove from the service desk |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `serviceDeskId` | string | Service Desk ID |
|
|
||||||
| `organizationId` | string | Organization ID removed |
|
|
||||||
| `success` | boolean | Whether the operation succeeded |
|
|
||||||
|
|
||||||
### `jsm_delete_organization`
|
|
||||||
|
|
||||||
Delete an organization in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `organizationId` | string | Yes | Organization ID to delete |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `organizationId` | string | ID of the deleted organization |
|
|
||||||
| `success` | boolean | Whether the organization was deleted |
|
|
||||||
|
|
||||||
### `jsm_get_organization_users`
|
|
||||||
|
|
||||||
Get users in an organization in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `organizationId` | string | Yes | Organization ID to get users from |
|
|
||||||
| `start` | number | No | Start index for pagination \(e.g., 0, 50, 100\) |
|
|
||||||
| `limit` | number | No | Maximum results to return \(e.g., 10, 25, 50\) |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `organizationId` | string | Organization ID |
|
|
||||||
| `users` | array | List of users in the organization |
|
|
||||||
| ↳ `accountId` | string | Atlassian account ID |
|
|
||||||
| ↳ `displayName` | string | Display name |
|
|
||||||
| ↳ `emailAddress` | string | Email address |
|
|
||||||
| ↳ `active` | boolean | Whether the account is active |
|
|
||||||
| ↳ `timeZone` | string | User timezone |
|
|
||||||
| `total` | number | Total number of users |
|
|
||||||
| `isLastPage` | boolean | Whether this is the last page |
|
|
||||||
|
|
||||||
### `jsm_add_organization_users`
|
|
||||||
|
|
||||||
Add users to an organization in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `organizationId` | string | Yes | Organization ID to add users to |
|
|
||||||
| `accountIds` | string | Yes | Comma-separated account IDs to add to the organization |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `organizationId` | string | Organization ID |
|
|
||||||
| `success` | boolean | Whether users were added successfully |
|
|
||||||
|
|
||||||
### `jsm_remove_organization_users`
|
|
||||||
|
|
||||||
Remove users from an organization in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `organizationId` | string | Yes | Organization ID to remove users from |
|
|
||||||
| `accountIds` | string | Yes | Comma-separated account IDs to remove from the organization |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `organizationId` | string | Organization ID |
|
|
||||||
| `success` | boolean | Whether users were removed successfully |
|
|
||||||
|
|
||||||
### `jsm_get_queues`
|
### `jsm_get_queues`
|
||||||
|
|
||||||
Get queues for a service desk in Jira Service Management
|
Get queues for a service desk in Jira Service Management
|
||||||
@@ -729,51 +438,6 @@ Get queues for a service desk in Jira Service Management
|
|||||||
| `total` | number | Total number of queues |
|
| `total` | number | Total number of queues |
|
||||||
| `isLastPage` | boolean | Whether this is the last page |
|
| `isLastPage` | boolean | Whether this is the last page |
|
||||||
|
|
||||||
### `jsm_get_queue_issues`
|
|
||||||
|
|
||||||
Get issues in a specific queue for a service desk in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `serviceDeskId` | string | Yes | Service Desk ID \(e.g., "1", "2"\) |
|
|
||||||
| `queueId` | string | Yes | Queue ID to get issues from |
|
|
||||||
| `start` | number | No | Start index for pagination \(e.g., 0, 50, 100\) |
|
|
||||||
| `limit` | number | No | Maximum results to return \(e.g., 10, 25, 50\) |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `serviceDeskId` | string | Service desk ID |
|
|
||||||
| `queueId` | string | Queue ID |
|
|
||||||
| `issues` | array | List of issues in the queue |
|
|
||||||
| ↳ `issueId` | string | Jira issue ID |
|
|
||||||
| ↳ `issueKey` | string | Issue key \(e.g., SD-123\) |
|
|
||||||
| ↳ `requestTypeId` | string | Request type ID |
|
|
||||||
| ↳ `serviceDeskId` | string | Service desk ID |
|
|
||||||
| ↳ `createdDate` | json | Creation date with iso8601, friendly, epochMillis |
|
|
||||||
| ↳ `currentStatus` | object | Current request status |
|
|
||||||
| ↳ `status` | string | Status name |
|
|
||||||
| ↳ `statusCategory` | string | Status category \(NEW, INDETERMINATE, DONE\) |
|
|
||||||
| ↳ `statusDate` | json | Status change date with iso8601, friendly, epochMillis |
|
|
||||||
| ↳ `reporter` | object | Reporter user details |
|
|
||||||
| ↳ `accountId` | string | Atlassian account ID |
|
|
||||||
| ↳ `displayName` | string | User display name |
|
|
||||||
| ↳ `emailAddress` | string | User email address |
|
|
||||||
| ↳ `active` | boolean | Whether the account is active |
|
|
||||||
| ↳ `requestFieldValues` | array | Request field values |
|
|
||||||
| ↳ `fieldId` | string | Field identifier |
|
|
||||||
| ↳ `label` | string | Human-readable field label |
|
|
||||||
| ↳ `value` | json | Field value |
|
|
||||||
| ↳ `renderedValue` | json | HTML-rendered field value |
|
|
||||||
| `total` | number | Total number of issues in the queue |
|
|
||||||
| `isLastPage` | boolean | Whether this is the last page |
|
|
||||||
|
|
||||||
### `jsm_get_sla`
|
### `jsm_get_sla`
|
||||||
|
|
||||||
Get SLA information for a service request in Jira Service Management
|
Get SLA information for a service request in Jira Service Management
|
||||||
@@ -905,32 +569,6 @@ Add participants to a request in Jira Service Management
|
|||||||
| ↳ `active` | boolean | Whether the account is active |
|
| ↳ `active` | boolean | Whether the account is active |
|
||||||
| `success` | boolean | Whether the operation succeeded |
|
| `success` | boolean | Whether the operation succeeded |
|
||||||
|
|
||||||
### `jsm_remove_participants`
|
|
||||||
|
|
||||||
Remove participants from a request in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., SD-123\) |
|
|
||||||
| `accountIds` | string | Yes | Comma-separated account IDs to remove as participants |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `issueIdOrKey` | string | Issue ID or key |
|
|
||||||
| `participants` | array | Remaining participants after removal |
|
|
||||||
| ↳ `accountId` | string | Atlassian account ID |
|
|
||||||
| ↳ `displayName` | string | Display name |
|
|
||||||
| ↳ `emailAddress` | string | Email address |
|
|
||||||
| ↳ `active` | boolean | Whether the account is active |
|
|
||||||
| `success` | boolean | Whether the operation succeeded |
|
|
||||||
|
|
||||||
### `jsm_get_approvals`
|
### `jsm_get_approvals`
|
||||||
|
|
||||||
Get approvals for a request in Jira Service Management
|
Get approvals for a request in Jira Service Management
|
||||||
@@ -1006,9 +644,9 @@ Approve or decline an approval request in Jira Service Management
|
|||||||
| `approval` | json | The approval object |
|
| `approval` | json | The approval object |
|
||||||
| `success` | boolean | Whether the operation succeeded |
|
| `success` | boolean | Whether the operation succeeded |
|
||||||
|
|
||||||
### `jsm_get_feedback`
|
### `jsm_get_request_type_fields`
|
||||||
|
|
||||||
Get CSAT feedback for a service request in Jira Service Management
|
Get the fields required to create a request of a specific type in Jira Service Management
|
||||||
|
|
||||||
#### Input
|
#### Input
|
||||||
|
|
||||||
@@ -1016,152 +654,27 @@ Get CSAT feedback for a service request in Jira Service Management
|
|||||||
| --------- | ---- | -------- | ----------- |
|
| --------- | ---- | -------- | ----------- |
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
||||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., SD-123\) |
|
| `serviceDeskId` | string | Yes | Service Desk ID \(e.g., "1", "2"\) |
|
||||||
|
| `requestTypeId` | string | Yes | Request Type ID \(e.g., "10", "15"\) |
|
||||||
|
|
||||||
#### Output
|
#### Output
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
| --------- | ---- | ----------- |
|
| --------- | ---- | ----------- |
|
||||||
| `ts` | string | Timestamp of the operation |
|
| `ts` | string | Timestamp of the operation |
|
||||||
| `issueIdOrKey` | string | Issue ID or key |
|
| `serviceDeskId` | string | Service desk ID |
|
||||||
| `rating` | number | CSAT rating \(1-5\) |
|
| `requestTypeId` | string | Request type ID |
|
||||||
| `comment` | string | Feedback comment |
|
| `canAddRequestParticipants` | boolean | Whether participants can be added to requests of this type |
|
||||||
| `type` | string | Feedback type \(e.g., csat\) |
|
| `canRaiseOnBehalfOf` | boolean | Whether requests can be raised on behalf of another user |
|
||||||
|
| `requestTypeFields` | array | List of fields for this request type |
|
||||||
### `jsm_add_feedback`
|
| ↳ `fieldId` | string | Field identifier \(e.g., summary, description, customfield_10010\) |
|
||||||
|
| ↳ `name` | string | Human-readable field name |
|
||||||
Add CSAT feedback to a service request in Jira Service Management
|
| ↳ `description` | string | Help text for the field |
|
||||||
|
| ↳ `required` | boolean | Whether the field is required |
|
||||||
#### Input
|
| ↳ `visible` | boolean | Whether the field is visible |
|
||||||
|
| ↳ `validValues` | json | Allowed values for select fields |
|
||||||
| Parameter | Type | Required | Description |
|
| ↳ `presetValues` | json | Pre-populated values |
|
||||||
| --------- | ---- | -------- | ----------- |
|
| ↳ `defaultValues` | json | Default values for the field |
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
| ↳ `jiraSchema` | json | Jira field schema with type, system, custom, customId |
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., SD-123\) |
|
|
||||||
| `rating` | number | Yes | CSAT rating \(1-5\) |
|
|
||||||
| `comment` | string | No | Optional feedback comment |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `issueIdOrKey` | string | Issue ID or key |
|
|
||||||
| `rating` | number | CSAT rating submitted |
|
|
||||||
| `comment` | string | Feedback comment |
|
|
||||||
| `type` | string | Feedback type |
|
|
||||||
| `success` | boolean | Whether feedback was submitted successfully |
|
|
||||||
|
|
||||||
### `jsm_delete_feedback`
|
|
||||||
|
|
||||||
Delete CSAT feedback from a service request in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., SD-123\) |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `issueIdOrKey` | string | Issue ID or key |
|
|
||||||
| `success` | boolean | Whether feedback was deleted |
|
|
||||||
|
|
||||||
### `jsm_get_notification`
|
|
||||||
|
|
||||||
Get notification subscription status for a request in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., SD-123\) |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `issueIdOrKey` | string | Issue ID or key |
|
|
||||||
| `subscribed` | boolean | Whether currently subscribed to notifications |
|
|
||||||
|
|
||||||
### `jsm_subscribe_notification`
|
|
||||||
|
|
||||||
Subscribe to notifications for a request in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., SD-123\) |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `issueIdOrKey` | string | Issue ID or key |
|
|
||||||
| `success` | boolean | Whether subscription was successful |
|
|
||||||
|
|
||||||
### `jsm_unsubscribe_notification`
|
|
||||||
|
|
||||||
Unsubscribe from notifications for a request in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., SD-123\) |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `issueIdOrKey` | string | Issue ID or key |
|
|
||||||
| `success` | boolean | Whether unsubscription was successful |
|
|
||||||
|
|
||||||
### `jsm_search_knowledge_base`
|
|
||||||
|
|
||||||
Search knowledge base articles in Jira Service Management
|
|
||||||
|
|
||||||
#### Input
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
| --------- | ---- | -------- | ----------- |
|
|
||||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
|
||||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
|
||||||
| `serviceDeskId` | string | No | Service Desk ID to search within \(optional, searches globally if omitted\) |
|
|
||||||
| `query` | string | Yes | Search query for knowledge base articles |
|
|
||||||
| `highlight` | boolean | No | Whether to highlight matching text in results |
|
|
||||||
| `start` | number | No | Start index for pagination \(e.g., 0, 50, 100\) |
|
|
||||||
| `limit` | number | No | Maximum results to return \(e.g., 10, 25, 50\) |
|
|
||||||
|
|
||||||
#### Output
|
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
|
||||||
| --------- | ---- | ----------- |
|
|
||||||
| `ts` | string | Timestamp of the operation |
|
|
||||||
| `articles` | array | List of knowledge base articles |
|
|
||||||
| ↳ `title` | string | Article title |
|
|
||||||
| ↳ `excerpt` | string | Article excerpt/summary |
|
|
||||||
| ↳ `sourceType` | string | Source type \(e.g., confluence\) |
|
|
||||||
| ↳ `sourcePageId` | string | Source page ID |
|
|
||||||
| ↳ `sourceSpaceKey` | string | Source space key |
|
|
||||||
| ↳ `contentUrl` | string | URL to rendered content |
|
|
||||||
| `total` | number | Total number of articles found |
|
|
||||||
| `isLastPage` | boolean | Whether this is the last page |
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ Estos atajos cambian entre las pestañas del panel en el lado derecho del lienzo
|
|||||||
|
|
||||||
| Atajo | Acción |
|
| Atajo | Acción |
|
||||||
|----------|--------|
|
|----------|--------|
|
||||||
|
| `C` | Enfocar pestaña Copilot |
|
||||||
|
| `T` | Enfocar pestaña Barra de herramientas |
|
||||||
|
| `E` | Enfocar pestaña Editor |
|
||||||
| `Mod` + `F` | Enfocar búsqueda de Barra de herramientas |
|
| `Mod` + `F` | Enfocar búsqueda de Barra de herramientas |
|
||||||
|
|
||||||
## Navegación global
|
## Navegación global
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ Ces raccourcis permettent de basculer entre les onglets du panneau sur le côté
|
|||||||
|
|
||||||
| Raccourci | Action |
|
| Raccourci | Action |
|
||||||
|----------|--------|
|
|----------|--------|
|
||||||
|
| `C` | Activer l'onglet Copilot |
|
||||||
|
| `T` | Activer l'onglet Barre d'outils |
|
||||||
|
| `E` | Activer l'onglet Éditeur |
|
||||||
| `Mod` + `F` | Activer la recherche dans la barre d'outils |
|
| `Mod` + `F` | Activer la recherche dans la barre d'outils |
|
||||||
|
|
||||||
## Navigation globale
|
## Navigation globale
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ import { Callout } from 'fumadocs-ui/components/callout'
|
|||||||
|
|
||||||
| ショートカット | 操作 |
|
| ショートカット | 操作 |
|
||||||
|----------|--------|
|
|----------|--------|
|
||||||
|
| `C` | Copilotタブにフォーカス |
|
||||||
|
| `T` | Toolbarタブにフォーカス |
|
||||||
|
| `E` | Editorタブにフォーカス |
|
||||||
| `Mod` + `F` | Toolbar検索にフォーカス |
|
| `Mod` + `F` | Toolbar検索にフォーカス |
|
||||||
|
|
||||||
## グローバルナビゲーション
|
## グローバルナビゲーション
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ import { Callout } from 'fumadocs-ui/components/callout'
|
|||||||
|
|
||||||
| 快捷键 | 操作 |
|
| 快捷键 | 操作 |
|
||||||
|----------|--------|
|
|----------|--------|
|
||||||
|
| `C` | 聚焦 Copilot 标签页 |
|
||||||
|
| `T` | 聚焦 Toolbar 标签页 |
|
||||||
|
| `E` | 聚焦 Editor 标签页 |
|
||||||
| `Mod` + `F` | 聚焦 Toolbar 搜索 |
|
| `Mod` + `F` | 聚焦 Toolbar 搜索 |
|
||||||
|
|
||||||
## 全局导航
|
## 全局导航
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ const ChatMessageSchema = z.object({
|
|||||||
workflowId: z.string().optional(),
|
workflowId: z.string().optional(),
|
||||||
knowledgeId: z.string().optional(),
|
knowledgeId: z.string().optional(),
|
||||||
blockId: z.string().optional(),
|
blockId: z.string().optional(),
|
||||||
blockIds: z.array(z.string()).optional(),
|
|
||||||
templateId: z.string().optional(),
|
templateId: z.string().optional(),
|
||||||
executionId: z.string().optional(),
|
executionId: z.string().optional(),
|
||||||
// For workflow_block, provide both workflowId and blockId
|
// For workflow_block, provide both workflowId and blockId
|
||||||
@@ -160,20 +159,6 @@ export async function POST(req: NextRequest) {
|
|||||||
commands,
|
commands,
|
||||||
} = ChatMessageSchema.parse(body)
|
} = ChatMessageSchema.parse(body)
|
||||||
|
|
||||||
const normalizedContexts = Array.isArray(contexts)
|
|
||||||
? contexts.map((ctx) => {
|
|
||||||
if (ctx.kind !== 'blocks') return ctx
|
|
||||||
if (Array.isArray(ctx.blockIds) && ctx.blockIds.length > 0) return ctx
|
|
||||||
if (ctx.blockId) {
|
|
||||||
return {
|
|
||||||
...ctx,
|
|
||||||
blockIds: [ctx.blockId],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ctx
|
|
||||||
})
|
|
||||||
: contexts
|
|
||||||
|
|
||||||
// Resolve workflowId - if not provided, use first workflow or find by name
|
// Resolve workflowId - if not provided, use first workflow or find by name
|
||||||
const resolved = await resolveWorkflowIdForUser(
|
const resolved = await resolveWorkflowIdForUser(
|
||||||
authenticatedUserId,
|
authenticatedUserId,
|
||||||
@@ -191,10 +176,10 @@ export async function POST(req: NextRequest) {
|
|||||||
const userMessageIdToUse = userMessageId || crypto.randomUUID()
|
const userMessageIdToUse = userMessageId || crypto.randomUUID()
|
||||||
try {
|
try {
|
||||||
logger.info(`[${tracker.requestId}] Received chat POST`, {
|
logger.info(`[${tracker.requestId}] Received chat POST`, {
|
||||||
hasContexts: Array.isArray(normalizedContexts),
|
hasContexts: Array.isArray(contexts),
|
||||||
contextsCount: Array.isArray(normalizedContexts) ? normalizedContexts.length : 0,
|
contextsCount: Array.isArray(contexts) ? contexts.length : 0,
|
||||||
contextsPreview: Array.isArray(normalizedContexts)
|
contextsPreview: Array.isArray(contexts)
|
||||||
? normalizedContexts.map((c: any) => ({
|
? contexts.map((c: any) => ({
|
||||||
kind: c?.kind,
|
kind: c?.kind,
|
||||||
chatId: c?.chatId,
|
chatId: c?.chatId,
|
||||||
workflowId: c?.workflowId,
|
workflowId: c?.workflowId,
|
||||||
@@ -206,25 +191,17 @@ export async function POST(req: NextRequest) {
|
|||||||
} catch {}
|
} catch {}
|
||||||
// Preprocess contexts server-side
|
// Preprocess contexts server-side
|
||||||
let agentContexts: Array<{ type: string; content: string }> = []
|
let agentContexts: Array<{ type: string; content: string }> = []
|
||||||
if (Array.isArray(normalizedContexts) && normalizedContexts.length > 0) {
|
if (Array.isArray(contexts) && contexts.length > 0) {
|
||||||
try {
|
try {
|
||||||
const { processContextsServer } = await import('@/lib/copilot/process-contents')
|
const { processContextsServer } = await import('@/lib/copilot/process-contents')
|
||||||
const processed = await processContextsServer(
|
const processed = await processContextsServer(contexts as any, authenticatedUserId, message)
|
||||||
normalizedContexts as any,
|
|
||||||
authenticatedUserId,
|
|
||||||
message
|
|
||||||
)
|
|
||||||
agentContexts = processed
|
agentContexts = processed
|
||||||
logger.info(`[${tracker.requestId}] Contexts processed for request`, {
|
logger.info(`[${tracker.requestId}] Contexts processed for request`, {
|
||||||
processedCount: agentContexts.length,
|
processedCount: agentContexts.length,
|
||||||
kinds: agentContexts.map((c) => c.type),
|
kinds: agentContexts.map((c) => c.type),
|
||||||
lengthPreview: agentContexts.map((c) => c.content?.length ?? 0),
|
lengthPreview: agentContexts.map((c) => c.content?.length ?? 0),
|
||||||
})
|
})
|
||||||
if (
|
if (Array.isArray(contexts) && contexts.length > 0 && agentContexts.length === 0) {
|
||||||
Array.isArray(normalizedContexts) &&
|
|
||||||
normalizedContexts.length > 0 &&
|
|
||||||
agentContexts.length === 0
|
|
||||||
) {
|
|
||||||
logger.warn(
|
logger.warn(
|
||||||
`[${tracker.requestId}] Contexts provided but none processed. Check executionId for logs contexts.`
|
`[${tracker.requestId}] Contexts provided but none processed. Check executionId for logs contexts.`
|
||||||
)
|
)
|
||||||
@@ -269,13 +246,11 @@ export async function POST(req: NextRequest) {
|
|||||||
mode,
|
mode,
|
||||||
model: selectedModel,
|
model: selectedModel,
|
||||||
provider,
|
provider,
|
||||||
conversationId: effectiveConversationId,
|
|
||||||
conversationHistory,
|
conversationHistory,
|
||||||
contexts: agentContexts,
|
contexts: agentContexts,
|
||||||
fileAttachments,
|
fileAttachments,
|
||||||
commands,
|
commands,
|
||||||
chatId: actualChatId,
|
chatId: actualChatId,
|
||||||
prefetch,
|
|
||||||
implicitFeedback,
|
implicitFeedback,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -457,15 +432,10 @@ export async function POST(req: NextRequest) {
|
|||||||
content: message,
|
content: message,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
...(fileAttachments && fileAttachments.length > 0 && { fileAttachments }),
|
...(fileAttachments && fileAttachments.length > 0 && { fileAttachments }),
|
||||||
...(Array.isArray(normalizedContexts) &&
|
...(Array.isArray(contexts) && contexts.length > 0 && { contexts }),
|
||||||
normalizedContexts.length > 0 && {
|
...(Array.isArray(contexts) &&
|
||||||
contexts: normalizedContexts,
|
contexts.length > 0 && {
|
||||||
}),
|
contentBlocks: [{ type: 'contexts', contexts: contexts as any, timestamp: Date.now() }],
|
||||||
...(Array.isArray(normalizedContexts) &&
|
|
||||||
normalizedContexts.length > 0 && {
|
|
||||||
contentBlocks: [
|
|
||||||
{ type: 'contexts', contexts: normalizedContexts as any, timestamp: Date.now() },
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,45 +38,6 @@ const createBlogPostSchema = z.object({
|
|||||||
status: z.enum(['current', 'draft']).optional(),
|
status: z.enum(['current', 'draft']).optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateBlogPostSchema = z
|
|
||||||
.object({
|
|
||||||
domain: z.string().min(1, 'Domain is required'),
|
|
||||||
accessToken: z.string().min(1, 'Access token is required'),
|
|
||||||
cloudId: z.string().optional(),
|
|
||||||
blogPostId: z.string().min(1, 'Blog post ID is required'),
|
|
||||||
title: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
status: z.enum(['current', 'draft']).optional(),
|
|
||||||
})
|
|
||||||
.refine(
|
|
||||||
(data) => {
|
|
||||||
const validation = validateAlphanumericId(data.blogPostId, 'blogPostId', 255)
|
|
||||||
return validation.isValid
|
|
||||||
},
|
|
||||||
(data) => {
|
|
||||||
const validation = validateAlphanumericId(data.blogPostId, 'blogPostId', 255)
|
|
||||||
return { message: validation.error || 'Invalid blog post ID', path: ['blogPostId'] }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const deleteBlogPostSchema = z
|
|
||||||
.object({
|
|
||||||
domain: z.string().min(1, 'Domain is required'),
|
|
||||||
accessToken: z.string().min(1, 'Access token is required'),
|
|
||||||
cloudId: z.string().optional(),
|
|
||||||
blogPostId: z.string().min(1, 'Blog post ID is required'),
|
|
||||||
})
|
|
||||||
.refine(
|
|
||||||
(data) => {
|
|
||||||
const validation = validateAlphanumericId(data.blogPostId, 'blogPostId', 255)
|
|
||||||
return validation.isValid
|
|
||||||
},
|
|
||||||
(data) => {
|
|
||||||
const validation = validateAlphanumericId(data.blogPostId, 'blogPostId', 255)
|
|
||||||
return { message: validation.error || 'Invalid blog post ID', path: ['blogPostId'] }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all blog posts or get a specific blog post
|
* List all blog posts or get a specific blog post
|
||||||
*/
|
*/
|
||||||
@@ -322,174 +283,3 @@ export async function POST(request: NextRequest) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a blog post
|
|
||||||
*/
|
|
||||||
export async function PUT(request: NextRequest) {
|
|
||||||
try {
|
|
||||||
const auth = await checkSessionOrInternalAuth(request)
|
|
||||||
if (!auth.success || !auth.userId) {
|
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = await request.json()
|
|
||||||
|
|
||||||
const validation = updateBlogPostSchema.safeParse(body)
|
|
||||||
if (!validation.success) {
|
|
||||||
const firstError = validation.error.errors[0]
|
|
||||||
return NextResponse.json({ error: firstError.message }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
domain,
|
|
||||||
accessToken,
|
|
||||||
cloudId: providedCloudId,
|
|
||||||
blogPostId,
|
|
||||||
title,
|
|
||||||
content,
|
|
||||||
status,
|
|
||||||
} = validation.data
|
|
||||||
|
|
||||||
const cloudId = providedCloudId || (await getConfluenceCloudId(domain, accessToken))
|
|
||||||
|
|
||||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
|
||||||
if (!cloudIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const blogPostUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/blogposts/${blogPostId}`
|
|
||||||
|
|
||||||
const currentResponse = await fetch(blogPostUrl, {
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!currentResponse.ok) {
|
|
||||||
const errorData = await currentResponse.json().catch(() => null)
|
|
||||||
const errorMessage =
|
|
||||||
errorData?.message || `Failed to fetch blog post for update (${currentResponse.status})`
|
|
||||||
return NextResponse.json({ error: errorMessage }, { status: currentResponse.status })
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentPost = await currentResponse.json()
|
|
||||||
const currentVersion = currentPost.version.number
|
|
||||||
|
|
||||||
const updateBody: Record<string, unknown> = {
|
|
||||||
id: blogPostId,
|
|
||||||
version: {
|
|
||||||
number: currentVersion + 1,
|
|
||||||
message: 'Updated via Sim',
|
|
||||||
},
|
|
||||||
status: status || currentPost.status || 'current',
|
|
||||||
title: title || currentPost.title,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content) {
|
|
||||||
updateBody.body = {
|
|
||||||
representation: 'storage',
|
|
||||||
value: content,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(blogPostUrl, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(updateBody),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json().catch(() => null)
|
|
||||||
logger.error('Confluence API error response:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: JSON.stringify(errorData, null, 2),
|
|
||||||
})
|
|
||||||
const errorMessage = errorData?.message || `Failed to update blog post (${response.status})`
|
|
||||||
return NextResponse.json({ error: errorMessage }, { status: response.status })
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
return NextResponse.json({
|
|
||||||
id: data.id,
|
|
||||||
title: data.title,
|
|
||||||
status: data.status ?? null,
|
|
||||||
spaceId: data.spaceId ?? null,
|
|
||||||
authorId: data.authorId ?? null,
|
|
||||||
createdAt: data.createdAt ?? null,
|
|
||||||
version: data.version ?? null,
|
|
||||||
body: data.body ?? null,
|
|
||||||
webUrl: data._links?.webui ?? null,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error updating blog post:', error)
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: (error as Error).message || 'Internal server error' },
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a blog post
|
|
||||||
*/
|
|
||||||
export async function DELETE(request: NextRequest) {
|
|
||||||
try {
|
|
||||||
const auth = await checkSessionOrInternalAuth(request)
|
|
||||||
if (!auth.success || !auth.userId) {
|
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = await request.json()
|
|
||||||
|
|
||||||
const validation = deleteBlogPostSchema.safeParse(body)
|
|
||||||
if (!validation.success) {
|
|
||||||
const firstError = validation.error.errors[0]
|
|
||||||
return NextResponse.json({ error: firstError.message }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const { domain, accessToken, cloudId: providedCloudId, blogPostId } = validation.data
|
|
||||||
|
|
||||||
const cloudId = providedCloudId || (await getConfluenceCloudId(domain, accessToken))
|
|
||||||
|
|
||||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
|
||||||
if (!cloudIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/blogposts/${blogPostId}`
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json().catch(() => null)
|
|
||||||
logger.error('Confluence API error response:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: JSON.stringify(errorData, null, 2),
|
|
||||||
})
|
|
||||||
const errorMessage = errorData?.message || `Failed to delete blog post (${response.status})`
|
|
||||||
return NextResponse.json({ error: errorMessage }, { status: response.status })
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({ blogPostId, deleted: true })
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error deleting blog post:', error)
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: (error as Error).message || 'Internal server error' },
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -191,84 +191,3 @@ export async function GET(request: NextRequest) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete a label from a page
|
|
||||||
export async function DELETE(request: NextRequest) {
|
|
||||||
try {
|
|
||||||
const auth = await checkSessionOrInternalAuth(request)
|
|
||||||
if (!auth.success || !auth.userId) {
|
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
domain,
|
|
||||||
accessToken,
|
|
||||||
cloudId: providedCloudId,
|
|
||||||
pageId,
|
|
||||||
labelName,
|
|
||||||
} = await request.json()
|
|
||||||
|
|
||||||
if (!domain) {
|
|
||||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pageId) {
|
|
||||||
return NextResponse.json({ error: 'Page ID is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!labelName) {
|
|
||||||
return NextResponse.json({ error: 'Label name is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageIdValidation = validateAlphanumericId(pageId, 'pageId', 255)
|
|
||||||
if (!pageIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: pageIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloudId = providedCloudId || (await getConfluenceCloudId(domain, accessToken))
|
|
||||||
|
|
||||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
|
||||||
if (!cloudIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const encodedLabel = encodeURIComponent(labelName.trim())
|
|
||||||
const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/rest/api/content/${pageId}/label?name=${encodedLabel}`
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json().catch(() => null)
|
|
||||||
logger.error('Confluence API error response:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: JSON.stringify(errorData, null, 2),
|
|
||||||
})
|
|
||||||
const errorMessage =
|
|
||||||
errorData?.message || `Failed to delete Confluence label (${response.status})`
|
|
||||||
return NextResponse.json({ error: errorMessage }, { status: response.status })
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
pageId,
|
|
||||||
labelName,
|
|
||||||
deleted: true,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error deleting Confluence label:', error)
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: (error as Error).message || 'Internal server error' },
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
|
||||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
|
||||||
import { getConfluenceCloudId } from '@/tools/confluence/utils'
|
|
||||||
|
|
||||||
const logger = createLogger('ConfluencePagesByLabelAPI')
|
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
|
||||||
try {
|
|
||||||
const auth = await checkSessionOrInternalAuth(request)
|
|
||||||
if (!auth.success || !auth.userId) {
|
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const { searchParams } = new URL(request.url)
|
|
||||||
const domain = searchParams.get('domain')
|
|
||||||
const accessToken = searchParams.get('accessToken')
|
|
||||||
const labelId = searchParams.get('labelId')
|
|
||||||
const providedCloudId = searchParams.get('cloudId')
|
|
||||||
const limit = searchParams.get('limit') || '50'
|
|
||||||
const cursor = searchParams.get('cursor')
|
|
||||||
|
|
||||||
if (!domain) {
|
|
||||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!labelId) {
|
|
||||||
return NextResponse.json({ error: 'Label ID is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const labelIdValidation = validateAlphanumericId(labelId, 'labelId', 255)
|
|
||||||
if (!labelIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: labelIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloudId = providedCloudId || (await getConfluenceCloudId(domain, accessToken))
|
|
||||||
|
|
||||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
|
||||||
if (!cloudIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryParams = new URLSearchParams()
|
|
||||||
queryParams.append('limit', String(Math.min(Number(limit), 250)))
|
|
||||||
if (cursor) {
|
|
||||||
queryParams.append('cursor', cursor)
|
|
||||||
}
|
|
||||||
const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/labels/${labelId}/pages?${queryParams.toString()}`
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json().catch(() => null)
|
|
||||||
logger.error('Confluence API error response:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: JSON.stringify(errorData, null, 2),
|
|
||||||
})
|
|
||||||
const errorMessage = errorData?.message || `Failed to get pages by label (${response.status})`
|
|
||||||
return NextResponse.json({ error: errorMessage }, { status: response.status })
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
const pages = (data.results || []).map((page: any) => ({
|
|
||||||
id: page.id,
|
|
||||||
title: page.title,
|
|
||||||
status: page.status ?? null,
|
|
||||||
spaceId: page.spaceId ?? null,
|
|
||||||
parentId: page.parentId ?? null,
|
|
||||||
authorId: page.authorId ?? null,
|
|
||||||
createdAt: page.createdAt ?? null,
|
|
||||||
version: page.version ?? null,
|
|
||||||
}))
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
pages,
|
|
||||||
labelId,
|
|
||||||
nextCursor: data._links?.next
|
|
||||||
? new URL(data._links.next, 'https://placeholder').searchParams.get('cursor')
|
|
||||||
: null,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error getting pages by label:', error)
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: (error as Error).message || 'Internal server error' },
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
|
||||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
|
||||||
import { getConfluenceCloudId } from '@/tools/confluence/utils'
|
|
||||||
|
|
||||||
const logger = createLogger('ConfluenceSpaceLabelsAPI')
|
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
|
||||||
try {
|
|
||||||
const auth = await checkSessionOrInternalAuth(request)
|
|
||||||
if (!auth.success || !auth.userId) {
|
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const { searchParams } = new URL(request.url)
|
|
||||||
const domain = searchParams.get('domain')
|
|
||||||
const accessToken = searchParams.get('accessToken')
|
|
||||||
const spaceId = searchParams.get('spaceId')
|
|
||||||
const providedCloudId = searchParams.get('cloudId')
|
|
||||||
const limit = searchParams.get('limit') || '25'
|
|
||||||
const cursor = searchParams.get('cursor')
|
|
||||||
|
|
||||||
if (!domain) {
|
|
||||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!spaceId) {
|
|
||||||
return NextResponse.json({ error: 'Space ID is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const spaceIdValidation = validateAlphanumericId(spaceId, 'spaceId', 255)
|
|
||||||
if (!spaceIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: spaceIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloudId = providedCloudId || (await getConfluenceCloudId(domain, accessToken))
|
|
||||||
|
|
||||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
|
||||||
if (!cloudIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryParams = new URLSearchParams()
|
|
||||||
queryParams.append('limit', String(Math.min(Number(limit), 250)))
|
|
||||||
if (cursor) {
|
|
||||||
queryParams.append('cursor', cursor)
|
|
||||||
}
|
|
||||||
const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/spaces/${spaceId}/labels?${queryParams.toString()}`
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json().catch(() => null)
|
|
||||||
logger.error('Confluence API error response:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: JSON.stringify(errorData, null, 2),
|
|
||||||
})
|
|
||||||
const errorMessage = errorData?.message || `Failed to list space labels (${response.status})`
|
|
||||||
return NextResponse.json({ error: errorMessage }, { status: response.status })
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
const labels = (data.results || []).map((label: any) => ({
|
|
||||||
id: label.id,
|
|
||||||
name: label.name,
|
|
||||||
prefix: label.prefix || 'global',
|
|
||||||
}))
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
labels,
|
|
||||||
spaceId,
|
|
||||||
nextCursor: data._links?.next
|
|
||||||
? new URL(data._links.next, 'https://placeholder').searchParams.get('cursor')
|
|
||||||
: null,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error listing space labels:', error)
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: (error as Error).message || 'Internal server error' },
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
|
||||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
|
||||||
import {
|
|
||||||
downloadJsmAttachments,
|
|
||||||
getJiraCloudId,
|
|
||||||
getJsmApiBaseUrl,
|
|
||||||
getJsmHeaders,
|
|
||||||
} from '@/tools/jsm/utils'
|
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
|
||||||
|
|
||||||
const logger = createLogger('JsmAttachmentsAPI')
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
const auth = await checkInternalAuth(request)
|
|
||||||
if (!auth.success || !auth.userId) {
|
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = await request.json()
|
|
||||||
const {
|
|
||||||
domain,
|
|
||||||
accessToken,
|
|
||||||
cloudId: cloudIdParam,
|
|
||||||
issueIdOrKey,
|
|
||||||
includeAttachments,
|
|
||||||
start,
|
|
||||||
limit,
|
|
||||||
} = body
|
|
||||||
|
|
||||||
if (!domain) {
|
|
||||||
logger.error('Missing domain in request')
|
|
||||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
logger.error('Missing access token in request')
|
|
||||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!issueIdOrKey) {
|
|
||||||
logger.error('Missing issueIdOrKey in request')
|
|
||||||
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
|
||||||
if (!issueIdOrKeyValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
|
||||||
|
|
||||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
|
||||||
if (!cloudIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseUrl = getJsmApiBaseUrl(cloudId)
|
|
||||||
const params = new URLSearchParams()
|
|
||||||
if (start) params.append('start', start)
|
|
||||||
if (limit) params.append('limit', limit)
|
|
||||||
|
|
||||||
const url = `${baseUrl}/request/${issueIdOrKey}/attachment${params.toString() ? `?${params.toString()}` : ''}`
|
|
||||||
|
|
||||||
logger.info('Fetching request attachments from:', url)
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText },
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
const rawAttachments = data.values || []
|
|
||||||
|
|
||||||
const attachments = rawAttachments.map((att: Record<string, unknown>) => ({
|
|
||||||
filename: att.filename ?? '',
|
|
||||||
author: att.author
|
|
||||||
? {
|
|
||||||
accountId: (att.author as Record<string, unknown>).accountId ?? '',
|
|
||||||
displayName: (att.author as Record<string, unknown>).displayName ?? '',
|
|
||||||
active: (att.author as Record<string, unknown>).active ?? true,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
created: att.created ?? null,
|
|
||||||
size: att.size ?? 0,
|
|
||||||
mimeType: att.mimeType ?? '',
|
|
||||||
}))
|
|
||||||
|
|
||||||
let files: Array<{ name: string; mimeType: string; data: string; size: number }> | undefined
|
|
||||||
|
|
||||||
if (includeAttachments && rawAttachments.length > 0) {
|
|
||||||
const downloadable = rawAttachments
|
|
||||||
.filter((att: Record<string, unknown>) => {
|
|
||||||
const links = att._links as Record<string, string> | undefined
|
|
||||||
return links?.content
|
|
||||||
})
|
|
||||||
.map((att: Record<string, unknown>) => ({
|
|
||||||
contentUrl: (att._links as Record<string, string>).content as string,
|
|
||||||
filename: (att.filename as string) ?? '',
|
|
||||||
mimeType: (att.mimeType as string) ?? '',
|
|
||||||
size: (att.size as number) ?? 0,
|
|
||||||
}))
|
|
||||||
|
|
||||||
if (downloadable.length > 0) {
|
|
||||||
files = await downloadJsmAttachments(downloadable, accessToken)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
issueIdOrKey,
|
|
||||||
attachments,
|
|
||||||
total: data.size || 0,
|
|
||||||
isLastPage: data.isLastPage ?? true,
|
|
||||||
...(files && files.length > 0 ? { files } : {}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error fetching attachments:', {
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: error instanceof Error ? error.message : 'Internal server error',
|
|
||||||
success: false,
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
|
||||||
import { validateJiraCloudId } from '@/lib/core/security/input-validation'
|
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
|
||||||
|
|
||||||
const logger = createLogger('JsmCustomerAPI')
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
const auth = await checkInternalAuth(request)
|
|
||||||
if (!auth.success || !auth.userId) {
|
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = await request.json()
|
|
||||||
const { domain, accessToken, cloudId: cloudIdParam, email, displayName } = body
|
|
||||||
|
|
||||||
if (!domain) {
|
|
||||||
logger.error('Missing domain in request')
|
|
||||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
logger.error('Missing access token in request')
|
|
||||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!email) {
|
|
||||||
logger.error('Missing email in request')
|
|
||||||
return NextResponse.json({ error: 'Email is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!displayName) {
|
|
||||||
logger.error('Missing displayName in request')
|
|
||||||
return NextResponse.json({ error: 'Display name is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
|
||||||
|
|
||||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
|
||||||
if (!cloudIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseUrl = getJsmApiBaseUrl(cloudId)
|
|
||||||
const url = `${baseUrl}/customer`
|
|
||||||
|
|
||||||
logger.info('Creating customer:', { email, displayName })
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
body: JSON.stringify({ email, displayName }),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText },
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
accountId: data.accountId ?? '',
|
|
||||||
displayName: data.displayName ?? '',
|
|
||||||
emailAddress: data.emailAddress ?? '',
|
|
||||||
active: data.active ?? true,
|
|
||||||
timeZone: data.timeZone ?? null,
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error creating customer:', {
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: error instanceof Error ? error.message : 'Internal server error',
|
|
||||||
success: false,
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -57,8 +57,6 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const baseUrl = getJsmApiBaseUrl(cloudId)
|
const baseUrl = getJsmApiBaseUrl(cloudId)
|
||||||
|
|
||||||
const { action: customerAction } = body
|
|
||||||
|
|
||||||
const rawIds = accountIds || emails
|
const rawIds = accountIds || emails
|
||||||
const parsedAccountIds = rawIds
|
const parsedAccountIds = rawIds
|
||||||
? typeof rawIds === 'string'
|
? typeof rawIds === 'string'
|
||||||
@@ -71,50 +69,7 @@ export async function POST(request: NextRequest) {
|
|||||||
: []
|
: []
|
||||||
: []
|
: []
|
||||||
|
|
||||||
const isRemoveOperation = customerAction === 'remove'
|
const isAddOperation = parsedAccountIds.length > 0
|
||||||
const isAddOperation = !isRemoveOperation && parsedAccountIds.length > 0
|
|
||||||
|
|
||||||
if (isRemoveOperation) {
|
|
||||||
if (parsedAccountIds.length === 0) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Account IDs or emails are required for removal' },
|
|
||||||
{ status: 400 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = `${baseUrl}/servicedesk/${serviceDeskId}/customer`
|
|
||||||
|
|
||||||
logger.info('Removing customers from:', url, { accountIds: parsedAccountIds })
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
body: JSON.stringify({ accountIds: parsedAccountIds }),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.status === 204 || response.ok) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
serviceDeskId,
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText },
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAddOperation) {
|
if (isAddOperation) {
|
||||||
const url = `${baseUrl}/servicedesk/${serviceDeskId}/customer`
|
const url = `${baseUrl}/servicedesk/${serviceDeskId}/customer`
|
||||||
|
|||||||
@@ -1,219 +0,0 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
|
||||||
import {
|
|
||||||
validateEnum,
|
|
||||||
validateJiraCloudId,
|
|
||||||
validateJiraIssueKey,
|
|
||||||
} from '@/lib/core/security/input-validation'
|
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
|
||||||
|
|
||||||
const logger = createLogger('JsmFeedbackAPI')
|
|
||||||
|
|
||||||
const VALID_ACTIONS = ['get', 'add', 'delete'] as const
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
const auth = await checkInternalAuth(request)
|
|
||||||
if (!auth.success || !auth.userId) {
|
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = await request.json()
|
|
||||||
const {
|
|
||||||
domain,
|
|
||||||
accessToken,
|
|
||||||
cloudId: cloudIdParam,
|
|
||||||
action,
|
|
||||||
issueIdOrKey,
|
|
||||||
rating,
|
|
||||||
comment,
|
|
||||||
} = body
|
|
||||||
|
|
||||||
if (!domain) {
|
|
||||||
logger.error('Missing domain in request')
|
|
||||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
logger.error('Missing access token in request')
|
|
||||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!issueIdOrKey) {
|
|
||||||
logger.error('Missing issueIdOrKey in request')
|
|
||||||
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!action) {
|
|
||||||
logger.error('Missing action in request')
|
|
||||||
return NextResponse.json({ error: 'Action is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionValidation = validateEnum(action, VALID_ACTIONS, 'action')
|
|
||||||
if (!actionValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: actionValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
|
||||||
if (!issueIdOrKeyValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
|
||||||
|
|
||||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
|
||||||
if (!cloudIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseUrl = getJsmApiBaseUrl(cloudId)
|
|
||||||
const url = `${baseUrl}/request/${issueIdOrKey}/feedback`
|
|
||||||
|
|
||||||
if (action === 'get') {
|
|
||||||
logger.info('Fetching feedback from:', url)
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: `JSM API error: ${response.status} ${response.statusText}`,
|
|
||||||
details: errorText,
|
|
||||||
},
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
issueIdOrKey,
|
|
||||||
rating: data.rating ?? null,
|
|
||||||
comment: data.comment?.body ?? null,
|
|
||||||
type: data.type ?? null,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'add') {
|
|
||||||
if (rating === undefined || rating === null) {
|
|
||||||
logger.error('Missing rating in request')
|
|
||||||
return NextResponse.json({ error: 'Rating is required (1-5)' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('Adding feedback to:', url, { rating })
|
|
||||||
|
|
||||||
const feedbackBody: Record<string, unknown> = {
|
|
||||||
rating: Number(rating),
|
|
||||||
type: 'csat',
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comment) {
|
|
||||||
feedbackBody.comment = { body: comment }
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
body: JSON.stringify(feedbackBody),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: `JSM API error: ${response.status} ${response.statusText}`,
|
|
||||||
details: errorText,
|
|
||||||
},
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
issueIdOrKey,
|
|
||||||
rating: data.rating ?? Number(rating),
|
|
||||||
comment: data.comment?.body ?? comment ?? null,
|
|
||||||
type: data.type ?? 'csat',
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'delete') {
|
|
||||||
logger.info('Deleting feedback from:', url)
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.status === 204 || response.ok) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
issueIdOrKey,
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: `JSM API error: ${response.status} ${response.statusText}`,
|
|
||||||
details: errorText,
|
|
||||||
},
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error in feedback operation:', {
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: error instanceof Error ? error.message : 'Internal server error',
|
|
||||||
success: false,
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
|
||||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
|
||||||
|
|
||||||
const logger = createLogger('JsmKnowledgeBaseAPI')
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
const auth = await checkInternalAuth(request)
|
|
||||||
if (!auth.success || !auth.userId) {
|
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = await request.json()
|
|
||||||
const {
|
|
||||||
domain,
|
|
||||||
accessToken,
|
|
||||||
cloudId: cloudIdParam,
|
|
||||||
serviceDeskId,
|
|
||||||
query,
|
|
||||||
highlight,
|
|
||||||
start,
|
|
||||||
limit,
|
|
||||||
} = body
|
|
||||||
|
|
||||||
if (!domain) {
|
|
||||||
logger.error('Missing domain in request')
|
|
||||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
logger.error('Missing access token in request')
|
|
||||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!query) {
|
|
||||||
logger.error('Missing query in request')
|
|
||||||
return NextResponse.json({ error: 'Search query is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serviceDeskId) {
|
|
||||||
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
|
|
||||||
if (!serviceDeskIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
|
||||||
|
|
||||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
|
||||||
if (!cloudIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseUrl = getJsmApiBaseUrl(cloudId)
|
|
||||||
const params = new URLSearchParams()
|
|
||||||
params.append('query', query)
|
|
||||||
if (highlight !== undefined) params.append('highlight', String(highlight))
|
|
||||||
if (start) params.append('start', start)
|
|
||||||
if (limit) params.append('limit', limit)
|
|
||||||
|
|
||||||
const basePath = serviceDeskId
|
|
||||||
? `${baseUrl}/servicedesk/${serviceDeskId}/knowledgebase/article`
|
|
||||||
: `${baseUrl}/knowledgebase/article`
|
|
||||||
|
|
||||||
const url = `${basePath}?${params.toString()}`
|
|
||||||
|
|
||||||
logger.info('Searching knowledge base:', url)
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText },
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
const articles = (data.values || []).map((article: Record<string, unknown>) => ({
|
|
||||||
title: (article.title as string) ?? '',
|
|
||||||
excerpt: (article.excerpt as string) ?? '',
|
|
||||||
sourceType: (article.source as Record<string, unknown>)?.type ?? '',
|
|
||||||
sourcePageId: (article.source as Record<string, unknown>)?.pageId ?? null,
|
|
||||||
sourceSpaceKey: (article.source as Record<string, unknown>)?.spaceKey ?? null,
|
|
||||||
contentUrl: (article.content as Record<string, unknown>)?.iframeSrc ?? null,
|
|
||||||
}))
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
articles,
|
|
||||||
total: data.size || 0,
|
|
||||||
isLastPage: data.isLastPage ?? true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error searching knowledge base:', {
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: error instanceof Error ? error.message : 'Internal server error',
|
|
||||||
success: false,
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
|
||||||
import {
|
|
||||||
validateEnum,
|
|
||||||
validateJiraCloudId,
|
|
||||||
validateJiraIssueKey,
|
|
||||||
} from '@/lib/core/security/input-validation'
|
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
|
||||||
|
|
||||||
const logger = createLogger('JsmNotificationAPI')
|
|
||||||
|
|
||||||
const VALID_ACTIONS = ['get', 'subscribe', 'unsubscribe'] as const
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
const auth = await checkInternalAuth(request)
|
|
||||||
if (!auth.success || !auth.userId) {
|
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = await request.json()
|
|
||||||
const { domain, accessToken, cloudId: cloudIdParam, action, issueIdOrKey } = body
|
|
||||||
|
|
||||||
if (!domain) {
|
|
||||||
logger.error('Missing domain in request')
|
|
||||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
logger.error('Missing access token in request')
|
|
||||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!issueIdOrKey) {
|
|
||||||
logger.error('Missing issueIdOrKey in request')
|
|
||||||
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!action) {
|
|
||||||
logger.error('Missing action in request')
|
|
||||||
return NextResponse.json({ error: 'Action is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionValidation = validateEnum(action, VALID_ACTIONS, 'action')
|
|
||||||
if (!actionValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: actionValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
|
||||||
if (!issueIdOrKeyValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
|
||||||
|
|
||||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
|
||||||
if (!cloudIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseUrl = getJsmApiBaseUrl(cloudId)
|
|
||||||
const url = `${baseUrl}/request/${issueIdOrKey}/notification`
|
|
||||||
|
|
||||||
if (action === 'get') {
|
|
||||||
logger.info('Fetching notification status from:', url)
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: `JSM API error: ${response.status} ${response.statusText}`,
|
|
||||||
details: errorText,
|
|
||||||
},
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
issueIdOrKey,
|
|
||||||
subscribed: data.subscribed ?? false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'subscribe') {
|
|
||||||
logger.info('Subscribing to notifications:', url)
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.status === 204 || response.ok) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
issueIdOrKey,
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: `JSM API error: ${response.status} ${response.statusText}`,
|
|
||||||
details: errorText,
|
|
||||||
},
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'unsubscribe') {
|
|
||||||
logger.info('Unsubscribing from notifications:', url)
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.status === 204 || response.ok) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
issueIdOrKey,
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: `JSM API error: ${response.status} ${response.statusText}`,
|
|
||||||
details: errorText,
|
|
||||||
},
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error in notification operation:', {
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: error instanceof Error ? error.message : 'Internal server error',
|
|
||||||
success: false,
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,13 +12,7 @@ export const dynamic = 'force-dynamic'
|
|||||||
|
|
||||||
const logger = createLogger('JsmOrganizationAPI')
|
const logger = createLogger('JsmOrganizationAPI')
|
||||||
|
|
||||||
const VALID_ACTIONS = [
|
const VALID_ACTIONS = ['create', 'add_to_service_desk'] as const
|
||||||
'create',
|
|
||||||
'add_to_service_desk',
|
|
||||||
'remove_from_service_desk',
|
|
||||||
'delete',
|
|
||||||
'get',
|
|
||||||
] as const
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
const auth = await checkInternalAuth(request)
|
const auth = await checkInternalAuth(request)
|
||||||
@@ -165,152 +159,6 @@ export async function POST(request: NextRequest) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action === 'remove_from_service_desk') {
|
|
||||||
if (!serviceDeskId) {
|
|
||||||
logger.error('Missing serviceDeskId in request')
|
|
||||||
return NextResponse.json({ error: 'Service Desk ID is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!organizationId) {
|
|
||||||
logger.error('Missing organizationId in request')
|
|
||||||
return NextResponse.json({ error: 'Organization ID is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
|
|
||||||
if (!serviceDeskIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const organizationIdValidation = validateAlphanumericId(organizationId, 'organizationId')
|
|
||||||
if (!organizationIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: organizationIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = `${baseUrl}/servicedesk/${serviceDeskId}/organization`
|
|
||||||
|
|
||||||
logger.info('Removing organization from service desk:', { serviceDeskId, organizationId })
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
body: JSON.stringify({ organizationId: Number.parseInt(organizationId, 10) }),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.status === 204 || response.ok) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
serviceDeskId,
|
|
||||||
organizationId,
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText },
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'delete') {
|
|
||||||
if (!organizationId) {
|
|
||||||
logger.error('Missing organizationId in request')
|
|
||||||
return NextResponse.json({ error: 'Organization ID is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const organizationIdValidation = validateAlphanumericId(organizationId, 'organizationId')
|
|
||||||
if (!organizationIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: organizationIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = `${baseUrl}/organization/${organizationId}`
|
|
||||||
|
|
||||||
logger.info('Deleting organization:', { organizationId })
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.status === 204 || response.ok) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
organizationId,
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText },
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'get') {
|
|
||||||
if (!organizationId) {
|
|
||||||
logger.error('Missing organizationId in request')
|
|
||||||
return NextResponse.json({ error: 'Organization ID is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const organizationIdValidation = validateAlphanumericId(organizationId, 'organizationId')
|
|
||||||
if (!organizationIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: organizationIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = `${baseUrl}/organization/${organizationId}`
|
|
||||||
|
|
||||||
logger.info('Fetching organization:', { organizationId })
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText },
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
id: data.id ?? '',
|
|
||||||
name: data.name ?? '',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error in organization operation:', {
|
logger.error('Error in organization operation:', {
|
||||||
|
|||||||
@@ -1,190 +0,0 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
|
||||||
import {
|
|
||||||
validateAlphanumericId,
|
|
||||||
validateEnum,
|
|
||||||
validateJiraCloudId,
|
|
||||||
} from '@/lib/core/security/input-validation'
|
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
|
||||||
|
|
||||||
const logger = createLogger('JsmOrganizationUsersAPI')
|
|
||||||
|
|
||||||
const VALID_ACTIONS = ['get', 'add', 'remove'] as const
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
const auth = await checkInternalAuth(request)
|
|
||||||
if (!auth.success || !auth.userId) {
|
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = await request.json()
|
|
||||||
const {
|
|
||||||
domain,
|
|
||||||
accessToken,
|
|
||||||
cloudId: cloudIdParam,
|
|
||||||
action,
|
|
||||||
organizationId,
|
|
||||||
accountIds,
|
|
||||||
start,
|
|
||||||
limit,
|
|
||||||
} = body
|
|
||||||
|
|
||||||
if (!domain) {
|
|
||||||
logger.error('Missing domain in request')
|
|
||||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
logger.error('Missing access token in request')
|
|
||||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!organizationId) {
|
|
||||||
logger.error('Missing organizationId in request')
|
|
||||||
return NextResponse.json({ error: 'Organization ID is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!action) {
|
|
||||||
logger.error('Missing action in request')
|
|
||||||
return NextResponse.json({ error: 'Action is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionValidation = validateEnum(action, VALID_ACTIONS, 'action')
|
|
||||||
if (!actionValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: actionValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const organizationIdValidation = validateAlphanumericId(organizationId, 'organizationId')
|
|
||||||
if (!organizationIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: organizationIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
|
||||||
|
|
||||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
|
||||||
if (!cloudIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseUrl = getJsmApiBaseUrl(cloudId)
|
|
||||||
const url = `${baseUrl}/organization/${organizationId}/user`
|
|
||||||
|
|
||||||
if (action === 'get') {
|
|
||||||
const params = new URLSearchParams()
|
|
||||||
if (start) params.append('start', start)
|
|
||||||
if (limit) params.append('limit', limit)
|
|
||||||
|
|
||||||
const getUrl = `${url}${params.toString() ? `?${params.toString()}` : ''}`
|
|
||||||
|
|
||||||
logger.info('Fetching organization users from:', getUrl)
|
|
||||||
|
|
||||||
const response = await fetch(getUrl, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: `JSM API error: ${response.status} ${response.statusText}`,
|
|
||||||
details: errorText,
|
|
||||||
},
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
organizationId,
|
|
||||||
users: data.values || [],
|
|
||||||
total: data.size || 0,
|
|
||||||
isLastPage: data.isLastPage ?? true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === 'add' || action === 'remove') {
|
|
||||||
if (!accountIds) {
|
|
||||||
logger.error('Missing accountIds in request')
|
|
||||||
return NextResponse.json({ error: 'Account IDs are required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedAccountIds =
|
|
||||||
typeof accountIds === 'string'
|
|
||||||
? accountIds
|
|
||||||
.split(',')
|
|
||||||
.map((id: string) => id.trim())
|
|
||||||
.filter((id: string) => id)
|
|
||||||
: accountIds
|
|
||||||
|
|
||||||
logger.info(`${action === 'add' ? 'Adding' : 'Removing'} organization users:`, {
|
|
||||||
organizationId,
|
|
||||||
accountIds: parsedAccountIds,
|
|
||||||
})
|
|
||||||
|
|
||||||
const method = action === 'add' ? 'POST' : 'DELETE'
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method,
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
body: JSON.stringify({ accountIds: parsedAccountIds }),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.status === 204 || response.ok) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
organizationId,
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: `JSM API error: ${response.status} ${response.statusText}`,
|
|
||||||
details: errorText,
|
|
||||||
},
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error in organization users operation:', {
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: error instanceof Error ? error.message : 'Internal server error',
|
|
||||||
success: false,
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,7 @@ export const dynamic = 'force-dynamic'
|
|||||||
|
|
||||||
const logger = createLogger('JsmParticipantsAPI')
|
const logger = createLogger('JsmParticipantsAPI')
|
||||||
|
|
||||||
const VALID_ACTIONS = ['get', 'add', 'remove'] as const
|
const VALID_ACTIONS = ['get', 'add'] as const
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
const auth = await checkInternalAuth(request)
|
const auth = await checkInternalAuth(request)
|
||||||
@@ -113,7 +113,7 @@ export async function POST(request: NextRequest) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (action === 'add' || action === 'remove') {
|
if (action === 'add') {
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
logger.error('Missing accountIds in request')
|
logger.error('Missing accountIds in request')
|
||||||
return NextResponse.json({ error: 'Account IDs are required' }, { status: 400 })
|
return NextResponse.json({ error: 'Account IDs are required' }, { status: 400 })
|
||||||
@@ -128,19 +128,16 @@ export async function POST(request: NextRequest) {
|
|||||||
: accountIds
|
: accountIds
|
||||||
|
|
||||||
const url = `${baseUrl}/request/${issueIdOrKey}/participant`
|
const url = `${baseUrl}/request/${issueIdOrKey}/participant`
|
||||||
const method = action === 'add' ? 'POST' : 'DELETE'
|
|
||||||
|
|
||||||
logger.info(`${action === 'add' ? 'Adding' : 'Removing'} participants:`, url, {
|
logger.info('Adding participants to:', url, { accountIds: parsedAccountIds })
|
||||||
accountIds: parsedAccountIds,
|
|
||||||
})
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method,
|
method: 'POST',
|
||||||
headers: getJsmHeaders(accessToken),
|
headers: getJsmHeaders(accessToken),
|
||||||
body: JSON.stringify({ accountIds: parsedAccountIds }),
|
body: JSON.stringify({ accountIds: parsedAccountIds }),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok && response.status !== 204) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text()
|
const errorText = await response.text()
|
||||||
logger.error('JSM API error:', {
|
logger.error('JSM API error:', {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
@@ -154,22 +151,14 @@ export async function POST(request: NextRequest) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let participants: unknown[] = []
|
const data = await response.json()
|
||||||
if (response.status !== 204) {
|
|
||||||
try {
|
|
||||||
const data = await response.json()
|
|
||||||
participants = data.values || []
|
|
||||||
} catch {
|
|
||||||
// DELETE may return empty body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
output: {
|
output: {
|
||||||
ts: new Date().toISOString(),
|
ts: new Date().toISOString(),
|
||||||
issueIdOrKey,
|
issueIdOrKey,
|
||||||
participants,
|
participants: data.values || [],
|
||||||
success: true,
|
success: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
|
||||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
|
||||||
|
|
||||||
const logger = createLogger('JsmQueueIssuesAPI')
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
const auth = await checkInternalAuth(request)
|
|
||||||
if (!auth.success || !auth.userId) {
|
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = await request.json()
|
|
||||||
const {
|
|
||||||
domain,
|
|
||||||
accessToken,
|
|
||||||
cloudId: cloudIdParam,
|
|
||||||
serviceDeskId,
|
|
||||||
queueId,
|
|
||||||
start,
|
|
||||||
limit,
|
|
||||||
} = body
|
|
||||||
|
|
||||||
if (!domain) {
|
|
||||||
logger.error('Missing domain in request')
|
|
||||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
logger.error('Missing access token in request')
|
|
||||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!serviceDeskId) {
|
|
||||||
logger.error('Missing serviceDeskId in request')
|
|
||||||
return NextResponse.json({ error: 'Service Desk ID is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!queueId) {
|
|
||||||
logger.error('Missing queueId in request')
|
|
||||||
return NextResponse.json({ error: 'Queue ID is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
|
|
||||||
if (!serviceDeskIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const queueIdValidation = validateAlphanumericId(queueId, 'queueId')
|
|
||||||
if (!queueIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: queueIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
|
||||||
|
|
||||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
|
||||||
if (!cloudIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseUrl = getJsmApiBaseUrl(cloudId)
|
|
||||||
const params = new URLSearchParams()
|
|
||||||
if (start) params.append('start', start)
|
|
||||||
if (limit) params.append('limit', limit)
|
|
||||||
|
|
||||||
const url = `${baseUrl}/servicedesk/${serviceDeskId}/queue/${queueId}/issue${params.toString() ? `?${params.toString()}` : ''}`
|
|
||||||
|
|
||||||
logger.info('Fetching queue issues from:', url)
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText },
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
serviceDeskId,
|
|
||||||
queueId,
|
|
||||||
issues: data.values || [],
|
|
||||||
total: data.size || 0,
|
|
||||||
isLastPage: data.isLastPage ?? true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error fetching queue issues:', {
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: error instanceof Error ? error.message : 'Internal server error',
|
|
||||||
success: false,
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
|
||||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
|
||||||
|
|
||||||
const logger = createLogger('JsmRequestStatusAPI')
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
|
||||||
const auth = await checkInternalAuth(request)
|
|
||||||
if (!auth.success || !auth.userId) {
|
|
||||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = await request.json()
|
|
||||||
const { domain, accessToken, cloudId: cloudIdParam, issueIdOrKey, start, limit } = body
|
|
||||||
|
|
||||||
if (!domain) {
|
|
||||||
logger.error('Missing domain in request')
|
|
||||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
logger.error('Missing access token in request')
|
|
||||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!issueIdOrKey) {
|
|
||||||
logger.error('Missing issueIdOrKey in request')
|
|
||||||
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
|
||||||
if (!issueIdOrKeyValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
|
||||||
|
|
||||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
|
||||||
if (!cloudIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseUrl = getJsmApiBaseUrl(cloudId)
|
|
||||||
const params = new URLSearchParams()
|
|
||||||
if (start) params.append('start', start)
|
|
||||||
if (limit) params.append('limit', limit)
|
|
||||||
|
|
||||||
const url = `${baseUrl}/request/${issueIdOrKey}/status${params.toString() ? `?${params.toString()}` : ''}`
|
|
||||||
|
|
||||||
logger.info('Fetching request status history from:', url)
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText },
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
issueIdOrKey,
|
|
||||||
statuses: data.values || [],
|
|
||||||
total: data.size || 0,
|
|
||||||
isLastPage: data.isLastPage ?? true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error fetching request status:', {
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error: error instanceof Error ? error.message : 'Internal server error',
|
|
||||||
success: false,
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
import { validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||||
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -16,16 +16,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
const {
|
const { domain, accessToken, cloudId: cloudIdParam, expand, start, limit } = body
|
||||||
domain,
|
|
||||||
accessToken,
|
|
||||||
cloudId: cloudIdParam,
|
|
||||||
expand,
|
|
||||||
start,
|
|
||||||
limit,
|
|
||||||
serviceDeskId,
|
|
||||||
action,
|
|
||||||
} = body
|
|
||||||
|
|
||||||
if (!domain) {
|
if (!domain) {
|
||||||
logger.error('Missing domain in request')
|
logger.error('Missing domain in request')
|
||||||
@@ -46,52 +37,6 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const baseUrl = getJsmApiBaseUrl(cloudId)
|
const baseUrl = getJsmApiBaseUrl(cloudId)
|
||||||
|
|
||||||
if (action === 'get' && serviceDeskId) {
|
|
||||||
const serviceDeskIdValidation = validateAlphanumericId(serviceDeskId, 'serviceDeskId')
|
|
||||||
if (!serviceDeskIdValidation.isValid) {
|
|
||||||
return NextResponse.json({ error: serviceDeskIdValidation.error }, { status: 400 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = `${baseUrl}/servicedesk/${serviceDeskId}`
|
|
||||||
|
|
||||||
logger.info('Fetching service desk:', url)
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: getJsmHeaders(accessToken),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text()
|
|
||||||
logger.error('JSM API error:', {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
error: errorText,
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: `JSM API error: ${response.status} ${response.statusText}`, details: errorText },
|
|
||||||
{ status: response.status }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
id: data.id ?? '',
|
|
||||||
projectId: data.projectId ?? '',
|
|
||||||
projectName: data.projectName ?? '',
|
|
||||||
projectKey: data.projectKey ?? '',
|
|
||||||
name: data.projectName ?? '',
|
|
||||||
description: data.description ?? null,
|
|
||||||
leadDisplayName: data.leadDisplayName ?? null,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
if (expand) params.append('expand', expand)
|
if (expand) params.append('expand', expand)
|
||||||
if (start) params.append('start', start)
|
if (start) params.append('start', start)
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const isInternalCall = auth.authType === 'internal_jwt'
|
|
||||||
const userId = auth.userId || null
|
const userId = auth.userId || null
|
||||||
|
|
||||||
let workflowData = await getWorkflowById(workflowId)
|
let workflowData = await getWorkflowById(workflowId)
|
||||||
@@ -48,32 +47,29 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|||||||
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
|
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInternalCall && !userId) {
|
// Check if user has access to this workflow
|
||||||
// Internal system calls (e.g. workflow-in-workflow executor) may not carry a userId.
|
if (!userId) {
|
||||||
// These are already authenticated via internal JWT; allow read access.
|
|
||||||
logger.info(`[${requestId}] Internal API call for workflow ${workflowId}`)
|
|
||||||
} else if (!userId) {
|
|
||||||
logger.warn(`[${requestId}] Unauthorized access attempt for workflow ${workflowId}`)
|
logger.warn(`[${requestId}] Unauthorized access attempt for workflow ${workflowId}`)
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||||
} else {
|
}
|
||||||
const authorization = await authorizeWorkflowByWorkspacePermission({
|
|
||||||
workflowId,
|
|
||||||
userId,
|
|
||||||
action: 'read',
|
|
||||||
})
|
|
||||||
if (!authorization.workflow) {
|
|
||||||
logger.warn(`[${requestId}] Workflow ${workflowId} not found`)
|
|
||||||
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
|
|
||||||
}
|
|
||||||
|
|
||||||
workflowData = authorization.workflow
|
const authorization = await authorizeWorkflowByWorkspacePermission({
|
||||||
if (!authorization.allowed) {
|
workflowId,
|
||||||
logger.warn(`[${requestId}] User ${userId} denied access to workflow ${workflowId}`)
|
userId,
|
||||||
return NextResponse.json(
|
action: 'read',
|
||||||
{ error: authorization.message || 'Access denied' },
|
})
|
||||||
{ status: authorization.status }
|
if (!authorization.workflow) {
|
||||||
)
|
logger.warn(`[${requestId}] Workflow ${workflowId} not found`)
|
||||||
}
|
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowData = authorization.workflow
|
||||||
|
if (!authorization.allowed) {
|
||||||
|
logger.warn(`[${requestId}] User ${userId} denied access to workflow ${workflowId}`)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: authorization.message || 'Access denied' },
|
||||||
|
{ status: authorization.status }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(`[${requestId}] Attempting to load workflow ${workflowId} from normalized tables`)
|
logger.debug(`[${requestId}] Attempting to load workflow ${workflowId} from normalized tables`)
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ export type CommandId =
|
|||||||
| 'goto-logs'
|
| 'goto-logs'
|
||||||
| 'open-search'
|
| 'open-search'
|
||||||
| 'run-workflow'
|
| 'run-workflow'
|
||||||
|
| 'focus-copilot-tab'
|
||||||
|
| 'focus-toolbar-tab'
|
||||||
|
| 'focus-editor-tab'
|
||||||
| 'clear-terminal-console'
|
| 'clear-terminal-console'
|
||||||
| 'focus-toolbar-search'
|
| 'focus-toolbar-search'
|
||||||
| 'clear-notifications'
|
| 'clear-notifications'
|
||||||
@@ -72,6 +75,21 @@ export const COMMAND_DEFINITIONS: Record<CommandId, CommandDefinition> = {
|
|||||||
shortcut: 'Mod+Enter',
|
shortcut: 'Mod+Enter',
|
||||||
allowInEditable: false,
|
allowInEditable: false,
|
||||||
},
|
},
|
||||||
|
'focus-copilot-tab': {
|
||||||
|
id: 'focus-copilot-tab',
|
||||||
|
shortcut: 'C',
|
||||||
|
allowInEditable: false,
|
||||||
|
},
|
||||||
|
'focus-toolbar-tab': {
|
||||||
|
id: 'focus-toolbar-tab',
|
||||||
|
shortcut: 'T',
|
||||||
|
allowInEditable: false,
|
||||||
|
},
|
||||||
|
'focus-editor-tab': {
|
||||||
|
id: 'focus-editor-tab',
|
||||||
|
shortcut: 'E',
|
||||||
|
allowInEditable: false,
|
||||||
|
},
|
||||||
'clear-terminal-console': {
|
'clear-terminal-console': {
|
||||||
id: 'clear-terminal-console',
|
id: 'clear-terminal-console',
|
||||||
shortcut: 'Mod+D',
|
shortcut: 'Mod+D',
|
||||||
|
|||||||
@@ -57,21 +57,6 @@ export function useChangeDetection({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (block.triggerMode) {
|
|
||||||
const triggerConfigValue = blockSubValues?.triggerConfig
|
|
||||||
if (
|
|
||||||
triggerConfigValue &&
|
|
||||||
typeof triggerConfigValue === 'object' &&
|
|
||||||
!subBlocks.triggerConfig
|
|
||||||
) {
|
|
||||||
subBlocks.triggerConfig = {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: triggerConfigValue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
blocksWithSubBlocks[blockId] = {
|
blocksWithSubBlocks[blockId] = {
|
||||||
...block,
|
...block,
|
||||||
subBlocks,
|
subBlocks,
|
||||||
|
|||||||
@@ -139,46 +139,6 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
|
|||||||
'delete:issue-worklog:jira': 'Delete worklog entries from Jira issues',
|
'delete:issue-worklog:jira': 'Delete worklog entries from Jira issues',
|
||||||
'write:issue-link:jira': 'Create links between Jira issues',
|
'write:issue-link:jira': 'Create links between Jira issues',
|
||||||
'delete:issue-link:jira': 'Delete links between Jira issues',
|
'delete:issue-link:jira': 'Delete links between Jira issues',
|
||||||
'manage:jira-project': 'Manage Jira project components and versions',
|
|
||||||
'read:board-scope:jira-software': 'View Jira boards',
|
|
||||||
'write:board-scope:jira-software': 'Manage Jira boards and backlog',
|
|
||||||
'read:sprint:jira-software': 'View Jira sprints',
|
|
||||||
'write:sprint:jira-software': 'Create and manage Jira sprints',
|
|
||||||
'delete:sprint:jira-software': 'Delete Jira sprints',
|
|
||||||
'read:servicedesk:jira-service-management': 'View JSM service desks',
|
|
||||||
'read:requesttype:jira-service-management': 'View JSM request types',
|
|
||||||
'read:request:jira-service-management': 'View JSM service requests',
|
|
||||||
'write:request:jira-service-management': 'Create and update JSM service requests',
|
|
||||||
'read:request.comment:jira-service-management': 'View comments on JSM requests',
|
|
||||||
'write:request.comment:jira-service-management': 'Add comments to JSM requests',
|
|
||||||
'read:customer:jira-service-management': 'View JSM customers',
|
|
||||||
'write:customer:jira-service-management': 'Create and manage JSM customers',
|
|
||||||
'read:servicedesk.customer:jira-service-management': 'View service desk customers',
|
|
||||||
'write:servicedesk.customer:jira-service-management': 'Add customers to service desks',
|
|
||||||
'delete:servicedesk.customer:jira-service-management': 'Remove customers from service desks',
|
|
||||||
'read:organization:jira-service-management': 'View JSM organizations',
|
|
||||||
'write:organization:jira-service-management': 'Create and manage JSM organizations',
|
|
||||||
'delete:organization:jira-service-management': 'Delete JSM organizations',
|
|
||||||
'read:servicedesk.organization:jira-service-management': 'View service desk organizations',
|
|
||||||
'write:servicedesk.organization:jira-service-management': 'Add organizations to service desks',
|
|
||||||
'read:organization.user:jira-service-management': 'View organization users',
|
|
||||||
'write:organization.user:jira-service-management': 'Add users to organizations',
|
|
||||||
'read:queue:jira-service-management': 'View JSM queues and queue issues',
|
|
||||||
'read:request.sla:jira-service-management': 'View request SLA information',
|
|
||||||
'read:request.status:jira-service-management': 'View request status history',
|
|
||||||
'write:request.status:jira-service-management': 'Transition request status',
|
|
||||||
'read:request.participant:jira-service-management': 'View request participants',
|
|
||||||
'write:request.participant:jira-service-management': 'Add request participants',
|
|
||||||
'read:request.approval:jira-service-management': 'View request approvals',
|
|
||||||
'write:request.approval:jira-service-management': 'Respond to request approvals',
|
|
||||||
'read:request.feedback:jira-service-management': 'View request feedback',
|
|
||||||
'write:request.feedback:jira-service-management': 'Add request feedback',
|
|
||||||
'delete:request.feedback:jira-service-management': 'Delete request feedback',
|
|
||||||
'read:request.notification:jira-service-management': 'View request notification status',
|
|
||||||
'write:request.notification:jira-service-management': 'Subscribe to request notifications',
|
|
||||||
'delete:request.notification:jira-service-management': 'Unsubscribe from request notifications',
|
|
||||||
'read:request.attachment:jira-service-management': 'View request attachments',
|
|
||||||
'read:knowledgebase:jira-service-management': 'Search knowledge base articles',
|
|
||||||
'User.Read': 'Read Microsoft user',
|
'User.Read': 'Read Microsoft user',
|
||||||
'Chat.Read': 'Read Microsoft chats',
|
'Chat.Read': 'Read Microsoft chats',
|
||||||
'Chat.ReadWrite': 'Write to Microsoft chats',
|
'Chat.ReadWrite': 'Write to Microsoft chats',
|
||||||
|
|||||||
@@ -340,7 +340,13 @@ export const Panel = memo(function Panel() {
|
|||||||
* Register global keyboard shortcuts using the central commands registry.
|
* Register global keyboard shortcuts using the central commands registry.
|
||||||
*
|
*
|
||||||
* - Mod+Enter: Run / cancel workflow (matches the Run button behavior)
|
* - Mod+Enter: Run / cancel workflow (matches the Run button behavior)
|
||||||
|
* - C: Focus Copilot tab
|
||||||
|
* - T: Focus Toolbar tab
|
||||||
|
* - E: Focus Editor tab
|
||||||
* - Mod+F: Focus Toolbar tab and search input
|
* - Mod+F: Focus Toolbar tab and search input
|
||||||
|
*
|
||||||
|
* The tab-switching commands are disabled inside editable elements so typing
|
||||||
|
* in inputs or textareas is not interrupted.
|
||||||
*/
|
*/
|
||||||
useRegisterGlobalCommands(() =>
|
useRegisterGlobalCommands(() =>
|
||||||
createCommands([
|
createCommands([
|
||||||
@@ -357,6 +363,33 @@ export const Panel = memo(function Panel() {
|
|||||||
allowInEditable: false,
|
allowInEditable: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'focus-copilot-tab',
|
||||||
|
handler: () => {
|
||||||
|
setActiveTab('copilot')
|
||||||
|
},
|
||||||
|
overrides: {
|
||||||
|
allowInEditable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'focus-toolbar-tab',
|
||||||
|
handler: () => {
|
||||||
|
setActiveTab('toolbar')
|
||||||
|
},
|
||||||
|
overrides: {
|
||||||
|
allowInEditable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'focus-editor-tab',
|
||||||
|
handler: () => {
|
||||||
|
setActiveTab('editor')
|
||||||
|
},
|
||||||
|
overrides: {
|
||||||
|
allowInEditable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'focus-toolbar-search',
|
id: 'focus-toolbar-search',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
|
|||||||
@@ -589,7 +589,6 @@ export async function executeScheduleJob(payload: ScheduleExecutionPayload) {
|
|||||||
|
|
||||||
export const scheduleExecution = task({
|
export const scheduleExecution = task({
|
||||||
id: 'schedule-execution',
|
id: 'schedule-execution',
|
||||||
machine: 'medium-1x',
|
|
||||||
retry: {
|
retry: {
|
||||||
maxAttempts: 1,
|
maxAttempts: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -669,7 +669,6 @@ async function executeWebhookJobInternal(
|
|||||||
|
|
||||||
export const webhookExecution = task({
|
export const webhookExecution = task({
|
||||||
id: 'webhook-execution',
|
id: 'webhook-execution',
|
||||||
machine: 'medium-1x',
|
|
||||||
retry: {
|
retry: {
|
||||||
maxAttempts: 1,
|
maxAttempts: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -197,6 +197,5 @@ export async function executeWorkflowJob(payload: WorkflowExecutionPayload) {
|
|||||||
|
|
||||||
export const workflowExecutionTask = task({
|
export const workflowExecutionTask = task({
|
||||||
id: 'workflow-execution',
|
id: 'workflow-execution',
|
||||||
machine: 'medium-1x',
|
|
||||||
run: executeWorkflowJob,
|
run: executeWorkflowJob,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,11 +10,9 @@ import {
|
|||||||
getReasoningEffortValuesForModel,
|
getReasoningEffortValuesForModel,
|
||||||
getThinkingLevelsForModel,
|
getThinkingLevelsForModel,
|
||||||
getVerbosityValuesForModel,
|
getVerbosityValuesForModel,
|
||||||
MODELS_WITH_DEEP_RESEARCH,
|
|
||||||
MODELS_WITH_REASONING_EFFORT,
|
MODELS_WITH_REASONING_EFFORT,
|
||||||
MODELS_WITH_THINKING,
|
MODELS_WITH_THINKING,
|
||||||
MODELS_WITH_VERBOSITY,
|
MODELS_WITH_VERBOSITY,
|
||||||
MODELS_WITHOUT_MEMORY,
|
|
||||||
providers,
|
providers,
|
||||||
supportsTemperature,
|
supportsTemperature,
|
||||||
} from '@/providers/utils'
|
} from '@/providers/utils'
|
||||||
@@ -414,22 +412,12 @@ Return ONLY the JSON array.`,
|
|||||||
title: 'Tools',
|
title: 'Tools',
|
||||||
type: 'tool-input',
|
type: 'tool-input',
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
condition: {
|
|
||||||
field: 'model',
|
|
||||||
value: MODELS_WITH_DEEP_RESEARCH,
|
|
||||||
not: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'skills',
|
id: 'skills',
|
||||||
title: 'Skills',
|
title: 'Skills',
|
||||||
type: 'skill-input',
|
type: 'skill-input',
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
condition: {
|
|
||||||
field: 'model',
|
|
||||||
value: MODELS_WITH_DEEP_RESEARCH,
|
|
||||||
not: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'memoryType',
|
id: 'memoryType',
|
||||||
@@ -443,11 +431,6 @@ Return ONLY the JSON array.`,
|
|||||||
{ label: 'Sliding window (tokens)', id: 'sliding_window_tokens' },
|
{ label: 'Sliding window (tokens)', id: 'sliding_window_tokens' },
|
||||||
],
|
],
|
||||||
defaultValue: 'none',
|
defaultValue: 'none',
|
||||||
condition: {
|
|
||||||
field: 'model',
|
|
||||||
value: MODELS_WITHOUT_MEMORY,
|
|
||||||
not: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'conversationId',
|
id: 'conversationId',
|
||||||
@@ -461,7 +444,6 @@ Return ONLY the JSON array.`,
|
|||||||
condition: {
|
condition: {
|
||||||
field: 'memoryType',
|
field: 'memoryType',
|
||||||
value: ['conversation', 'sliding_window', 'sliding_window_tokens'],
|
value: ['conversation', 'sliding_window', 'sliding_window_tokens'],
|
||||||
and: { field: 'model', value: MODELS_WITHOUT_MEMORY, not: true },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -472,7 +454,6 @@ Return ONLY the JSON array.`,
|
|||||||
condition: {
|
condition: {
|
||||||
field: 'memoryType',
|
field: 'memoryType',
|
||||||
value: ['sliding_window'],
|
value: ['sliding_window'],
|
||||||
and: { field: 'model', value: MODELS_WITHOUT_MEMORY, not: true },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -483,7 +464,6 @@ Return ONLY the JSON array.`,
|
|||||||
condition: {
|
condition: {
|
||||||
field: 'memoryType',
|
field: 'memoryType',
|
||||||
value: ['sliding_window_tokens'],
|
value: ['sliding_window_tokens'],
|
||||||
and: { field: 'model', value: MODELS_WITHOUT_MEMORY, not: true },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -497,13 +477,9 @@ Return ONLY the JSON array.`,
|
|||||||
condition: () => ({
|
condition: () => ({
|
||||||
field: 'model',
|
field: 'model',
|
||||||
value: (() => {
|
value: (() => {
|
||||||
const deepResearch = new Set(MODELS_WITH_DEEP_RESEARCH.map((m) => m.toLowerCase()))
|
|
||||||
const allModels = Object.keys(getBaseModelProviders())
|
const allModels = Object.keys(getBaseModelProviders())
|
||||||
return allModels.filter(
|
return allModels.filter(
|
||||||
(model) =>
|
(model) => supportsTemperature(model) && getMaxTemperature(model) === 1
|
||||||
supportsTemperature(model) &&
|
|
||||||
getMaxTemperature(model) === 1 &&
|
|
||||||
!deepResearch.has(model.toLowerCase())
|
|
||||||
)
|
)
|
||||||
})(),
|
})(),
|
||||||
}),
|
}),
|
||||||
@@ -519,13 +495,9 @@ Return ONLY the JSON array.`,
|
|||||||
condition: () => ({
|
condition: () => ({
|
||||||
field: 'model',
|
field: 'model',
|
||||||
value: (() => {
|
value: (() => {
|
||||||
const deepResearch = new Set(MODELS_WITH_DEEP_RESEARCH.map((m) => m.toLowerCase()))
|
|
||||||
const allModels = Object.keys(getBaseModelProviders())
|
const allModels = Object.keys(getBaseModelProviders())
|
||||||
return allModels.filter(
|
return allModels.filter(
|
||||||
(model) =>
|
(model) => supportsTemperature(model) && getMaxTemperature(model) === 2
|
||||||
supportsTemperature(model) &&
|
|
||||||
getMaxTemperature(model) === 2 &&
|
|
||||||
!deepResearch.has(model.toLowerCase())
|
|
||||||
)
|
)
|
||||||
})(),
|
})(),
|
||||||
}),
|
}),
|
||||||
@@ -536,11 +508,6 @@ Return ONLY the JSON array.`,
|
|||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
placeholder: 'Enter max tokens (e.g., 4096)...',
|
placeholder: 'Enter max tokens (e.g., 4096)...',
|
||||||
mode: 'advanced',
|
mode: 'advanced',
|
||||||
condition: {
|
|
||||||
field: 'model',
|
|
||||||
value: MODELS_WITH_DEEP_RESEARCH,
|
|
||||||
not: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'responseFormat',
|
id: 'responseFormat',
|
||||||
@@ -548,11 +515,6 @@ Return ONLY the JSON array.`,
|
|||||||
type: 'code',
|
type: 'code',
|
||||||
placeholder: 'Enter JSON schema...',
|
placeholder: 'Enter JSON schema...',
|
||||||
language: 'json',
|
language: 'json',
|
||||||
condition: {
|
|
||||||
field: 'model',
|
|
||||||
value: MODELS_WITH_DEEP_RESEARCH,
|
|
||||||
not: true,
|
|
||||||
},
|
|
||||||
wandConfig: {
|
wandConfig: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
maintainHistory: true,
|
maintainHistory: true,
|
||||||
@@ -645,16 +607,6 @@ Example 3 (Array Input):
|
|||||||
generationType: 'json-schema',
|
generationType: 'json-schema',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'previousInteractionId',
|
|
||||||
title: 'Previous Interaction ID',
|
|
||||||
type: 'short-input',
|
|
||||||
placeholder: 'e.g., {{agent_1.interactionId}}',
|
|
||||||
condition: {
|
|
||||||
field: 'model',
|
|
||||||
value: MODELS_WITH_DEEP_RESEARCH,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
tools: {
|
tools: {
|
||||||
access: [
|
access: [
|
||||||
@@ -818,13 +770,5 @@ Example 3 (Array Input):
|
|||||||
description: 'Provider timing information',
|
description: 'Provider timing information',
|
||||||
},
|
},
|
||||||
cost: { type: 'json', description: 'Cost of the API call' },
|
cost: { type: 'json', description: 'Cost of the API call' },
|
||||||
interactionId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Interaction ID for multi-turn deep research follow-ups',
|
|
||||||
condition: {
|
|
||||||
field: 'model',
|
|
||||||
value: MODELS_WITH_DEEP_RESEARCH,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import type { BlockConfig } from '@/blocks/types'
|
|||||||
import { AuthMode } from '@/blocks/types'
|
import { AuthMode } from '@/blocks/types'
|
||||||
import { normalizeFileInput } from '@/blocks/utils'
|
import { normalizeFileInput } from '@/blocks/utils'
|
||||||
import type { ConfluenceResponse } from '@/tools/confluence/types'
|
import type { ConfluenceResponse } from '@/tools/confluence/types'
|
||||||
import { getTrigger } from '@/triggers'
|
|
||||||
|
|
||||||
export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
|
export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
|
||||||
type: 'confluence',
|
type: 'confluence',
|
||||||
@@ -395,8 +394,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
// Page Property Operations
|
// Page Property Operations
|
||||||
{ label: 'List Page Properties', id: 'list_page_properties' },
|
{ label: 'List Page Properties', id: 'list_page_properties' },
|
||||||
{ label: 'Create Page Property', id: 'create_page_property' },
|
{ label: 'Create Page Property', id: 'create_page_property' },
|
||||||
{ label: 'Update Page Property', id: 'update_page_property' },
|
|
||||||
{ label: 'Delete Page Property', id: 'delete_page_property' },
|
|
||||||
// Search Operations
|
// Search Operations
|
||||||
{ label: 'Search Content', id: 'search' },
|
{ label: 'Search Content', id: 'search' },
|
||||||
{ label: 'Search in Space', id: 'search_in_space' },
|
{ label: 'Search in Space', id: 'search_in_space' },
|
||||||
@@ -404,8 +401,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
{ label: 'List Blog Posts', id: 'list_blogposts' },
|
{ label: 'List Blog Posts', id: 'list_blogposts' },
|
||||||
{ label: 'Get Blog Post', id: 'get_blogpost' },
|
{ label: 'Get Blog Post', id: 'get_blogpost' },
|
||||||
{ label: 'Create Blog Post', id: 'create_blogpost' },
|
{ label: 'Create Blog Post', id: 'create_blogpost' },
|
||||||
{ label: 'Update Blog Post', id: 'update_blogpost' },
|
|
||||||
{ label: 'Delete Blog Post', id: 'delete_blogpost' },
|
|
||||||
{ label: 'List Blog Posts in Space', id: 'list_blogposts_in_space' },
|
{ label: 'List Blog Posts in Space', id: 'list_blogposts_in_space' },
|
||||||
// Comment Operations
|
// Comment Operations
|
||||||
{ label: 'Create Comment', id: 'create_comment' },
|
{ label: 'Create Comment', id: 'create_comment' },
|
||||||
@@ -419,9 +414,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
// Label Operations
|
// Label Operations
|
||||||
{ label: 'List Labels', id: 'list_labels' },
|
{ label: 'List Labels', id: 'list_labels' },
|
||||||
{ label: 'Add Label', id: 'add_label' },
|
{ label: 'Add Label', id: 'add_label' },
|
||||||
{ label: 'Delete Label', id: 'delete_label' },
|
|
||||||
{ label: 'Get Pages by Label', id: 'get_pages_by_label' },
|
|
||||||
{ label: 'List Space Labels', id: 'list_space_labels' },
|
|
||||||
// Space Operations
|
// Space Operations
|
||||||
{ label: 'Get Space', id: 'get_space' },
|
{ label: 'Get Space', id: 'get_space' },
|
||||||
{ label: 'List Spaces', id: 'list_spaces' },
|
{ label: 'List Spaces', id: 'list_spaces' },
|
||||||
@@ -488,16 +480,11 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
'list_pages_in_space',
|
'list_pages_in_space',
|
||||||
'list_blogposts',
|
'list_blogposts',
|
||||||
'get_blogpost',
|
'get_blogpost',
|
||||||
'create_blogpost',
|
|
||||||
'update_blogpost',
|
|
||||||
'delete_blogpost',
|
|
||||||
'list_blogposts_in_space',
|
'list_blogposts_in_space',
|
||||||
'search',
|
'search',
|
||||||
'search_in_space',
|
'search_in_space',
|
||||||
'get_space',
|
'get_space',
|
||||||
'list_spaces',
|
'list_spaces',
|
||||||
'get_pages_by_label',
|
|
||||||
'list_space_labels',
|
|
||||||
],
|
],
|
||||||
not: true,
|
not: true,
|
||||||
},
|
},
|
||||||
@@ -513,9 +500,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
'list_labels',
|
'list_labels',
|
||||||
'upload_attachment',
|
'upload_attachment',
|
||||||
'add_label',
|
'add_label',
|
||||||
'delete_label',
|
|
||||||
'delete_page_property',
|
|
||||||
'update_page_property',
|
|
||||||
'get_page_children',
|
'get_page_children',
|
||||||
'get_page_ancestors',
|
'get_page_ancestors',
|
||||||
'list_page_versions',
|
'list_page_versions',
|
||||||
@@ -538,16 +522,11 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
'list_pages_in_space',
|
'list_pages_in_space',
|
||||||
'list_blogposts',
|
'list_blogposts',
|
||||||
'get_blogpost',
|
'get_blogpost',
|
||||||
'create_blogpost',
|
|
||||||
'update_blogpost',
|
|
||||||
'delete_blogpost',
|
|
||||||
'list_blogposts_in_space',
|
'list_blogposts_in_space',
|
||||||
'search',
|
'search',
|
||||||
'search_in_space',
|
'search_in_space',
|
||||||
'get_space',
|
'get_space',
|
||||||
'list_spaces',
|
'list_spaces',
|
||||||
'get_pages_by_label',
|
|
||||||
'list_space_labels',
|
|
||||||
],
|
],
|
||||||
not: true,
|
not: true,
|
||||||
},
|
},
|
||||||
@@ -563,9 +542,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
'list_labels',
|
'list_labels',
|
||||||
'upload_attachment',
|
'upload_attachment',
|
||||||
'add_label',
|
'add_label',
|
||||||
'delete_label',
|
|
||||||
'delete_page_property',
|
|
||||||
'update_page_property',
|
|
||||||
'get_page_children',
|
'get_page_children',
|
||||||
'get_page_ancestors',
|
'get_page_ancestors',
|
||||||
'list_page_versions',
|
'list_page_versions',
|
||||||
@@ -590,7 +566,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
'search_in_space',
|
'search_in_space',
|
||||||
'create_blogpost',
|
'create_blogpost',
|
||||||
'list_blogposts_in_space',
|
'list_blogposts_in_space',
|
||||||
'list_space_labels',
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -600,10 +575,7 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
placeholder: 'Enter blog post ID',
|
placeholder: 'Enter blog post ID',
|
||||||
required: true,
|
required: true,
|
||||||
condition: {
|
condition: { field: 'operation', value: 'get_blogpost' },
|
||||||
field: 'operation',
|
|
||||||
value: ['get_blogpost', 'update_blogpost', 'delete_blogpost'],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'versionNumber',
|
id: 'versionNumber',
|
||||||
@@ -619,7 +591,7 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
placeholder: 'Enter property key/name',
|
placeholder: 'Enter property key/name',
|
||||||
required: true,
|
required: true,
|
||||||
condition: { field: 'operation', value: ['create_page_property', 'update_page_property'] },
|
condition: { field: 'operation', value: 'create_page_property' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'propertyValue',
|
id: 'propertyValue',
|
||||||
@@ -627,46 +599,21 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
type: 'long-input',
|
type: 'long-input',
|
||||||
placeholder: 'Enter property value (JSON supported)',
|
placeholder: 'Enter property value (JSON supported)',
|
||||||
required: true,
|
required: true,
|
||||||
condition: { field: 'operation', value: ['create_page_property', 'update_page_property'] },
|
condition: { field: 'operation', value: 'create_page_property' },
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'propertyId',
|
|
||||||
title: 'Property ID',
|
|
||||||
type: 'short-input',
|
|
||||||
placeholder: 'Enter property ID',
|
|
||||||
required: true,
|
|
||||||
condition: {
|
|
||||||
field: 'operation',
|
|
||||||
value: ['delete_page_property', 'update_page_property'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'propertyVersionNumber',
|
|
||||||
title: 'Property Version Number',
|
|
||||||
type: 'short-input',
|
|
||||||
placeholder: 'Enter current version number of the property',
|
|
||||||
required: true,
|
|
||||||
condition: { field: 'operation', value: 'update_page_property' },
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'title',
|
id: 'title',
|
||||||
title: 'Title',
|
title: 'Title',
|
||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
placeholder: 'Enter title',
|
placeholder: 'Enter title',
|
||||||
condition: {
|
condition: { field: 'operation', value: ['create', 'update', 'create_blogpost'] },
|
||||||
field: 'operation',
|
|
||||||
value: ['create', 'update', 'create_blogpost', 'update_blogpost'],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'content',
|
id: 'content',
|
||||||
title: 'Content',
|
title: 'Content',
|
||||||
type: 'long-input',
|
type: 'long-input',
|
||||||
placeholder: 'Enter content',
|
placeholder: 'Enter content',
|
||||||
condition: {
|
condition: { field: 'operation', value: ['create', 'update', 'create_blogpost'] },
|
||||||
field: 'operation',
|
|
||||||
value: ['create', 'update', 'create_blogpost', 'update_blogpost'],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'parentId',
|
id: 'parentId',
|
||||||
@@ -747,7 +694,7 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
placeholder: 'Enter label name',
|
placeholder: 'Enter label name',
|
||||||
required: true,
|
required: true,
|
||||||
condition: { field: 'operation', value: ['add_label', 'delete_label'] },
|
condition: { field: 'operation', value: 'add_label' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'labelPrefix',
|
id: 'labelPrefix',
|
||||||
@@ -762,14 +709,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
value: () => 'global',
|
value: () => 'global',
|
||||||
condition: { field: 'operation', value: 'add_label' },
|
condition: { field: 'operation', value: 'add_label' },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'labelId',
|
|
||||||
title: 'Label ID',
|
|
||||||
type: 'short-input',
|
|
||||||
placeholder: 'Enter label ID',
|
|
||||||
required: true,
|
|
||||||
condition: { field: 'operation', value: 'get_pages_by_label' },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'blogPostStatus',
|
id: 'blogPostStatus',
|
||||||
title: 'Status',
|
title: 'Status',
|
||||||
@@ -779,7 +718,7 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
{ label: 'Draft', id: 'draft' },
|
{ label: 'Draft', id: 'draft' },
|
||||||
],
|
],
|
||||||
value: () => 'current',
|
value: () => 'current',
|
||||||
condition: { field: 'operation', value: ['create_blogpost', 'update_blogpost'] },
|
condition: { field: 'operation', value: 'create_blogpost' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'purge',
|
id: 'purge',
|
||||||
@@ -820,8 +759,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
'list_page_versions',
|
'list_page_versions',
|
||||||
'list_page_properties',
|
'list_page_properties',
|
||||||
'list_labels',
|
'list_labels',
|
||||||
'get_pages_by_label',
|
|
||||||
'list_space_labels',
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -843,51 +780,10 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
'list_page_versions',
|
'list_page_versions',
|
||||||
'list_page_properties',
|
'list_page_properties',
|
||||||
'list_labels',
|
'list_labels',
|
||||||
'get_pages_by_label',
|
|
||||||
'list_space_labels',
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Trigger subBlocks
|
|
||||||
...getTrigger('confluence_page_created').subBlocks,
|
|
||||||
...getTrigger('confluence_page_updated').subBlocks,
|
|
||||||
...getTrigger('confluence_page_removed').subBlocks,
|
|
||||||
...getTrigger('confluence_page_moved').subBlocks,
|
|
||||||
...getTrigger('confluence_comment_created').subBlocks,
|
|
||||||
...getTrigger('confluence_comment_removed').subBlocks,
|
|
||||||
...getTrigger('confluence_blog_created').subBlocks,
|
|
||||||
...getTrigger('confluence_blog_updated').subBlocks,
|
|
||||||
...getTrigger('confluence_blog_removed').subBlocks,
|
|
||||||
...getTrigger('confluence_attachment_created').subBlocks,
|
|
||||||
...getTrigger('confluence_attachment_removed').subBlocks,
|
|
||||||
...getTrigger('confluence_space_created').subBlocks,
|
|
||||||
...getTrigger('confluence_space_updated').subBlocks,
|
|
||||||
...getTrigger('confluence_label_added').subBlocks,
|
|
||||||
...getTrigger('confluence_label_removed').subBlocks,
|
|
||||||
...getTrigger('confluence_webhook').subBlocks,
|
|
||||||
],
|
],
|
||||||
triggers: {
|
|
||||||
enabled: true,
|
|
||||||
available: [
|
|
||||||
'confluence_page_created',
|
|
||||||
'confluence_page_updated',
|
|
||||||
'confluence_page_removed',
|
|
||||||
'confluence_page_moved',
|
|
||||||
'confluence_comment_created',
|
|
||||||
'confluence_comment_removed',
|
|
||||||
'confluence_blog_created',
|
|
||||||
'confluence_blog_updated',
|
|
||||||
'confluence_blog_removed',
|
|
||||||
'confluence_attachment_created',
|
|
||||||
'confluence_attachment_removed',
|
|
||||||
'confluence_space_created',
|
|
||||||
'confluence_space_updated',
|
|
||||||
'confluence_label_added',
|
|
||||||
'confluence_label_removed',
|
|
||||||
'confluence_webhook',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
tools: {
|
tools: {
|
||||||
access: [
|
access: [
|
||||||
// Page Tools
|
// Page Tools
|
||||||
@@ -904,8 +800,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
// Property Tools
|
// Property Tools
|
||||||
'confluence_list_page_properties',
|
'confluence_list_page_properties',
|
||||||
'confluence_create_page_property',
|
'confluence_create_page_property',
|
||||||
'confluence_update_page_property',
|
|
||||||
'confluence_delete_page_property',
|
|
||||||
// Search Tools
|
// Search Tools
|
||||||
'confluence_search',
|
'confluence_search',
|
||||||
'confluence_search_in_space',
|
'confluence_search_in_space',
|
||||||
@@ -913,8 +807,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
'confluence_list_blogposts',
|
'confluence_list_blogposts',
|
||||||
'confluence_get_blogpost',
|
'confluence_get_blogpost',
|
||||||
'confluence_create_blogpost',
|
'confluence_create_blogpost',
|
||||||
'confluence_update_blogpost',
|
|
||||||
'confluence_delete_blogpost',
|
|
||||||
'confluence_list_blogposts_in_space',
|
'confluence_list_blogposts_in_space',
|
||||||
// Comment Tools
|
// Comment Tools
|
||||||
'confluence_create_comment',
|
'confluence_create_comment',
|
||||||
@@ -928,9 +820,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
// Label Tools
|
// Label Tools
|
||||||
'confluence_list_labels',
|
'confluence_list_labels',
|
||||||
'confluence_add_label',
|
'confluence_add_label',
|
||||||
'confluence_delete_label',
|
|
||||||
'confluence_get_pages_by_label',
|
|
||||||
'confluence_list_space_labels',
|
|
||||||
// Space Tools
|
// Space Tools
|
||||||
'confluence_get_space',
|
'confluence_get_space',
|
||||||
'confluence_list_spaces',
|
'confluence_list_spaces',
|
||||||
@@ -963,10 +852,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
return 'confluence_list_page_properties'
|
return 'confluence_list_page_properties'
|
||||||
case 'create_page_property':
|
case 'create_page_property':
|
||||||
return 'confluence_create_page_property'
|
return 'confluence_create_page_property'
|
||||||
case 'update_page_property':
|
|
||||||
return 'confluence_update_page_property'
|
|
||||||
case 'delete_page_property':
|
|
||||||
return 'confluence_delete_page_property'
|
|
||||||
// Search Operations
|
// Search Operations
|
||||||
case 'search':
|
case 'search':
|
||||||
return 'confluence_search'
|
return 'confluence_search'
|
||||||
@@ -979,10 +864,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
return 'confluence_get_blogpost'
|
return 'confluence_get_blogpost'
|
||||||
case 'create_blogpost':
|
case 'create_blogpost':
|
||||||
return 'confluence_create_blogpost'
|
return 'confluence_create_blogpost'
|
||||||
case 'update_blogpost':
|
|
||||||
return 'confluence_update_blogpost'
|
|
||||||
case 'delete_blogpost':
|
|
||||||
return 'confluence_delete_blogpost'
|
|
||||||
case 'list_blogposts_in_space':
|
case 'list_blogposts_in_space':
|
||||||
return 'confluence_list_blogposts_in_space'
|
return 'confluence_list_blogposts_in_space'
|
||||||
// Comment Operations
|
// Comment Operations
|
||||||
@@ -1006,12 +887,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
return 'confluence_list_labels'
|
return 'confluence_list_labels'
|
||||||
case 'add_label':
|
case 'add_label':
|
||||||
return 'confluence_add_label'
|
return 'confluence_add_label'
|
||||||
case 'delete_label':
|
|
||||||
return 'confluence_delete_label'
|
|
||||||
case 'get_pages_by_label':
|
|
||||||
return 'confluence_get_pages_by_label'
|
|
||||||
case 'list_space_labels':
|
|
||||||
return 'confluence_list_space_labels'
|
|
||||||
// Space Operations
|
// Space Operations
|
||||||
case 'get_space':
|
case 'get_space':
|
||||||
return 'confluence_get_space'
|
return 'confluence_get_space'
|
||||||
@@ -1033,10 +908,7 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
versionNumber,
|
versionNumber,
|
||||||
propertyKey,
|
propertyKey,
|
||||||
propertyValue,
|
propertyValue,
|
||||||
propertyId,
|
|
||||||
propertyVersionNumber,
|
|
||||||
labelPrefix,
|
labelPrefix,
|
||||||
labelId,
|
|
||||||
blogPostStatus,
|
blogPostStatus,
|
||||||
purge,
|
purge,
|
||||||
bodyFormat,
|
bodyFormat,
|
||||||
@@ -1066,25 +938,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operation === 'update_blogpost') {
|
|
||||||
return {
|
|
||||||
credential,
|
|
||||||
operation,
|
|
||||||
blogPostId,
|
|
||||||
status: blogPostStatus || undefined,
|
|
||||||
...rest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation === 'delete_blogpost') {
|
|
||||||
return {
|
|
||||||
credential,
|
|
||||||
operation,
|
|
||||||
blogPostId,
|
|
||||||
...rest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation === 'delete') {
|
if (operation === 'delete') {
|
||||||
return {
|
return {
|
||||||
credential,
|
credential,
|
||||||
@@ -1106,9 +959,7 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operations that support generic cursor pagination.
|
// Operations that support cursor pagination
|
||||||
// get_pages_by_label and list_space_labels have dedicated handlers
|
|
||||||
// below that pass cursor along with their required params (labelId, spaceId).
|
|
||||||
const supportsCursor = [
|
const supportsCursor = [
|
||||||
'list_attachments',
|
'list_attachments',
|
||||||
'list_spaces',
|
'list_spaces',
|
||||||
@@ -1145,53 +996,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operation === 'update_page_property') {
|
|
||||||
if (!propertyKey) {
|
|
||||||
throw new Error('Property key is required for this operation.')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
credential,
|
|
||||||
pageId: effectivePageId,
|
|
||||||
operation,
|
|
||||||
propertyId,
|
|
||||||
key: propertyKey,
|
|
||||||
value: propertyValue,
|
|
||||||
versionNumber: propertyVersionNumber
|
|
||||||
? Number.parseInt(String(propertyVersionNumber), 10)
|
|
||||||
: undefined,
|
|
||||||
...rest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation === 'delete_page_property') {
|
|
||||||
return {
|
|
||||||
credential,
|
|
||||||
pageId: effectivePageId,
|
|
||||||
operation,
|
|
||||||
propertyId,
|
|
||||||
...rest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation === 'get_pages_by_label') {
|
|
||||||
return {
|
|
||||||
credential,
|
|
||||||
operation,
|
|
||||||
labelId,
|
|
||||||
cursor: cursor || undefined,
|
|
||||||
...rest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation === 'list_space_labels') {
|
|
||||||
return {
|
|
||||||
credential,
|
|
||||||
operation,
|
|
||||||
cursor: cursor || undefined,
|
|
||||||
...rest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation === 'upload_attachment') {
|
if (operation === 'upload_attachment') {
|
||||||
const normalizedFile = normalizeFileInput(attachmentFile, { single: true })
|
const normalizedFile = normalizeFileInput(attachmentFile, { single: true })
|
||||||
if (!normalizedFile) {
|
if (!normalizedFile) {
|
||||||
@@ -1240,13 +1044,7 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
attachmentFileName: { type: 'string', description: 'Custom file name for attachment' },
|
attachmentFileName: { type: 'string', description: 'Custom file name for attachment' },
|
||||||
attachmentComment: { type: 'string', description: 'Comment for the attachment' },
|
attachmentComment: { type: 'string', description: 'Comment for the attachment' },
|
||||||
labelName: { type: 'string', description: 'Label name' },
|
labelName: { type: 'string', description: 'Label name' },
|
||||||
labelId: { type: 'string', description: 'Label identifier' },
|
|
||||||
labelPrefix: { type: 'string', description: 'Label prefix (global, my, team, system)' },
|
labelPrefix: { type: 'string', description: 'Label prefix (global, my, team, system)' },
|
||||||
propertyId: { type: 'string', description: 'Property identifier' },
|
|
||||||
propertyVersionNumber: {
|
|
||||||
type: 'number',
|
|
||||||
description: 'Current version number of the property',
|
|
||||||
},
|
|
||||||
blogPostStatus: { type: 'string', description: 'Blog post status (current or draft)' },
|
blogPostStatus: { type: 'string', description: 'Blog post status (current or draft)' },
|
||||||
purge: { type: 'boolean', description: 'Permanently delete instead of moving to trash' },
|
purge: { type: 'boolean', description: 'Permanently delete instead of moving to trash' },
|
||||||
bodyFormat: { type: 'string', description: 'Body format for comments' },
|
bodyFormat: { type: 'string', description: 'Body format for comments' },
|
||||||
@@ -1282,7 +1080,6 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
|||||||
// Label Results
|
// Label Results
|
||||||
labels: { type: 'array', description: 'List of labels' },
|
labels: { type: 'array', description: 'List of labels' },
|
||||||
labelName: { type: 'string', description: 'Label name' },
|
labelName: { type: 'string', description: 'Label name' },
|
||||||
labelId: { type: 'string', description: 'Label identifier' },
|
|
||||||
// Space Results
|
// Space Results
|
||||||
spaces: { type: 'array', description: 'List of spaces' },
|
spaces: { type: 'array', description: 'List of spaces' },
|
||||||
spaceId: { type: 'string', description: 'Space identifier' },
|
spaceId: { type: 'string', description: 'Space identifier' },
|
||||||
|
|||||||
@@ -93,12 +93,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
|
|||||||
'delete:issue-worklog:jira',
|
'delete:issue-worklog:jira',
|
||||||
'write:issue-link:jira',
|
'write:issue-link:jira',
|
||||||
'delete:issue-link:jira',
|
'delete:issue-link:jira',
|
||||||
'manage:jira-project',
|
|
||||||
'read:board-scope:jira-software',
|
|
||||||
'write:board-scope:jira-software',
|
|
||||||
'read:sprint:jira-software',
|
|
||||||
'write:sprint:jira-software',
|
|
||||||
'delete:sprint:jira-software',
|
|
||||||
],
|
],
|
||||||
placeholder: 'Select Jira account',
|
placeholder: 'Select Jira account',
|
||||||
},
|
},
|
||||||
@@ -701,32 +695,7 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
...getTrigger('jira_issue_updated').subBlocks,
|
...getTrigger('jira_issue_updated').subBlocks,
|
||||||
...getTrigger('jira_issue_deleted').subBlocks,
|
...getTrigger('jira_issue_deleted').subBlocks,
|
||||||
...getTrigger('jira_issue_commented').subBlocks,
|
...getTrigger('jira_issue_commented').subBlocks,
|
||||||
...getTrigger('jira_comment_updated').subBlocks,
|
|
||||||
...getTrigger('jira_comment_deleted').subBlocks,
|
|
||||||
...getTrigger('jira_worklog_created').subBlocks,
|
...getTrigger('jira_worklog_created').subBlocks,
|
||||||
...getTrigger('jira_worklog_updated').subBlocks,
|
|
||||||
...getTrigger('jira_worklog_deleted').subBlocks,
|
|
||||||
...getTrigger('jira_sprint_created').subBlocks,
|
|
||||||
...getTrigger('jira_sprint_started').subBlocks,
|
|
||||||
...getTrigger('jira_sprint_closed').subBlocks,
|
|
||||||
...getTrigger('jira_sprint_updated').subBlocks,
|
|
||||||
...getTrigger('jira_sprint_deleted').subBlocks,
|
|
||||||
...getTrigger('jira_project_created').subBlocks,
|
|
||||||
...getTrigger('jira_project_updated').subBlocks,
|
|
||||||
...getTrigger('jira_project_deleted').subBlocks,
|
|
||||||
...getTrigger('jira_version_created').subBlocks,
|
|
||||||
...getTrigger('jira_version_released').subBlocks,
|
|
||||||
...getTrigger('jira_version_unreleased').subBlocks,
|
|
||||||
...getTrigger('jira_version_updated').subBlocks,
|
|
||||||
...getTrigger('jira_version_deleted').subBlocks,
|
|
||||||
...getTrigger('jira_board_created').subBlocks,
|
|
||||||
...getTrigger('jira_board_updated').subBlocks,
|
|
||||||
...getTrigger('jira_board_deleted').subBlocks,
|
|
||||||
...getTrigger('jira_board_config_changed').subBlocks,
|
|
||||||
...getTrigger('jira_attachment_created').subBlocks,
|
|
||||||
...getTrigger('jira_attachment_deleted').subBlocks,
|
|
||||||
...getTrigger('jira_issuelink_created').subBlocks,
|
|
||||||
...getTrigger('jira_issuelink_deleted').subBlocks,
|
|
||||||
...getTrigger('jira_webhook').subBlocks,
|
...getTrigger('jira_webhook').subBlocks,
|
||||||
],
|
],
|
||||||
tools: {
|
tools: {
|
||||||
@@ -1271,32 +1240,7 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
'jira_issue_updated',
|
'jira_issue_updated',
|
||||||
'jira_issue_deleted',
|
'jira_issue_deleted',
|
||||||
'jira_issue_commented',
|
'jira_issue_commented',
|
||||||
'jira_comment_updated',
|
|
||||||
'jira_comment_deleted',
|
|
||||||
'jira_worklog_created',
|
'jira_worklog_created',
|
||||||
'jira_worklog_updated',
|
|
||||||
'jira_worklog_deleted',
|
|
||||||
'jira_sprint_created',
|
|
||||||
'jira_sprint_started',
|
|
||||||
'jira_sprint_closed',
|
|
||||||
'jira_sprint_updated',
|
|
||||||
'jira_sprint_deleted',
|
|
||||||
'jira_project_created',
|
|
||||||
'jira_project_updated',
|
|
||||||
'jira_project_deleted',
|
|
||||||
'jira_version_created',
|
|
||||||
'jira_version_released',
|
|
||||||
'jira_version_unreleased',
|
|
||||||
'jira_version_updated',
|
|
||||||
'jira_version_deleted',
|
|
||||||
'jira_board_created',
|
|
||||||
'jira_board_updated',
|
|
||||||
'jira_board_deleted',
|
|
||||||
'jira_board_config_changed',
|
|
||||||
'jira_attachment_created',
|
|
||||||
'jira_attachment_deleted',
|
|
||||||
'jira_issuelink_created',
|
|
||||||
'jira_issuelink_deleted',
|
|
||||||
'jira_webhook',
|
'jira_webhook',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,16 +2,14 @@ import { JiraServiceManagementIcon } from '@/components/icons'
|
|||||||
import type { BlockConfig } from '@/blocks/types'
|
import type { BlockConfig } from '@/blocks/types'
|
||||||
import { AuthMode } from '@/blocks/types'
|
import { AuthMode } from '@/blocks/types'
|
||||||
import type { JsmResponse } from '@/tools/jsm/types'
|
import type { JsmResponse } from '@/tools/jsm/types'
|
||||||
import { getTrigger } from '@/triggers'
|
|
||||||
|
|
||||||
export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
|
export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
|
||||||
type: 'jira_service_management',
|
type: 'jira_service_management',
|
||||||
name: 'Jira Service Management',
|
name: 'Jira Service Management',
|
||||||
description: 'Interact with Jira Service Management',
|
description: 'Interact with Jira Service Management',
|
||||||
authMode: AuthMode.OAuth,
|
authMode: AuthMode.OAuth,
|
||||||
triggerAllowed: true,
|
|
||||||
longDescription:
|
longDescription:
|
||||||
'Integrate with Jira Service Management for IT service management. Create and manage service requests, handle customers and organizations, track SLAs, and manage queues. Can also trigger workflows based on Jira Service Management webhook events.',
|
'Integrate with Jira Service Management for IT service management. Create and manage service requests, handle customers and organizations, track SLAs, and manage queues.',
|
||||||
docsLink: 'https://docs.sim.ai/tools/jira-service-management',
|
docsLink: 'https://docs.sim.ai/tools/jira-service-management',
|
||||||
category: 'tools',
|
category: 'tools',
|
||||||
bgColor: '#E0E0E0',
|
bgColor: '#E0E0E0',
|
||||||
@@ -23,46 +21,26 @@ export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
|
|||||||
type: 'dropdown',
|
type: 'dropdown',
|
||||||
options: [
|
options: [
|
||||||
{ label: 'Get Service Desks', id: 'get_service_desks' },
|
{ label: 'Get Service Desks', id: 'get_service_desks' },
|
||||||
{ label: 'Get Service Desk', id: 'get_service_desk' },
|
|
||||||
{ label: 'Get Request Types', id: 'get_request_types' },
|
{ label: 'Get Request Types', id: 'get_request_types' },
|
||||||
{ label: 'Get Request Type Fields', id: 'get_request_type_fields' },
|
|
||||||
{ label: 'Create Request', id: 'create_request' },
|
{ label: 'Create Request', id: 'create_request' },
|
||||||
{ label: 'Get Request', id: 'get_request' },
|
{ label: 'Get Request', id: 'get_request' },
|
||||||
{ label: 'Get Requests', id: 'get_requests' },
|
{ label: 'Get Requests', id: 'get_requests' },
|
||||||
{ label: 'Get Request Status', id: 'get_request_status' },
|
|
||||||
{ label: 'Get Request Attachments', id: 'get_request_attachments' },
|
|
||||||
{ label: 'Add Comment', id: 'add_comment' },
|
{ label: 'Add Comment', id: 'add_comment' },
|
||||||
{ label: 'Get Comments', id: 'get_comments' },
|
{ label: 'Get Comments', id: 'get_comments' },
|
||||||
{ label: 'Get Customers', id: 'get_customers' },
|
{ label: 'Get Customers', id: 'get_customers' },
|
||||||
{ label: 'Add Customer', id: 'add_customer' },
|
{ label: 'Add Customer', id: 'add_customer' },
|
||||||
{ label: 'Remove Customer', id: 'remove_customer' },
|
|
||||||
{ label: 'Create Customer', id: 'create_customer' },
|
|
||||||
{ label: 'Get Organizations', id: 'get_organizations' },
|
{ label: 'Get Organizations', id: 'get_organizations' },
|
||||||
{ label: 'Get Organization', id: 'get_organization' },
|
|
||||||
{ label: 'Create Organization', id: 'create_organization' },
|
{ label: 'Create Organization', id: 'create_organization' },
|
||||||
{ label: 'Add Organization', id: 'add_organization' },
|
{ label: 'Add Organization', id: 'add_organization' },
|
||||||
{ label: 'Remove Organization', id: 'remove_organization' },
|
|
||||||
{ label: 'Delete Organization', id: 'delete_organization' },
|
|
||||||
{ label: 'Get Organization Users', id: 'get_organization_users' },
|
|
||||||
{ label: 'Add Organization Users', id: 'add_organization_users' },
|
|
||||||
{ label: 'Remove Organization Users', id: 'remove_organization_users' },
|
|
||||||
{ label: 'Get Queues', id: 'get_queues' },
|
{ label: 'Get Queues', id: 'get_queues' },
|
||||||
{ label: 'Get Queue Issues', id: 'get_queue_issues' },
|
|
||||||
{ label: 'Get SLA', id: 'get_sla' },
|
{ label: 'Get SLA', id: 'get_sla' },
|
||||||
{ label: 'Get Transitions', id: 'get_transitions' },
|
{ label: 'Get Transitions', id: 'get_transitions' },
|
||||||
{ label: 'Transition Request', id: 'transition_request' },
|
{ label: 'Transition Request', id: 'transition_request' },
|
||||||
{ label: 'Get Participants', id: 'get_participants' },
|
{ label: 'Get Participants', id: 'get_participants' },
|
||||||
{ label: 'Add Participants', id: 'add_participants' },
|
{ label: 'Add Participants', id: 'add_participants' },
|
||||||
{ label: 'Remove Participants', id: 'remove_participants' },
|
|
||||||
{ label: 'Get Approvals', id: 'get_approvals' },
|
{ label: 'Get Approvals', id: 'get_approvals' },
|
||||||
{ label: 'Answer Approval', id: 'answer_approval' },
|
{ label: 'Answer Approval', id: 'answer_approval' },
|
||||||
{ label: 'Get Feedback', id: 'get_feedback' },
|
{ label: 'Get Request Type Fields', id: 'get_request_type_fields' },
|
||||||
{ label: 'Add Feedback', id: 'add_feedback' },
|
|
||||||
{ label: 'Delete Feedback', id: 'delete_feedback' },
|
|
||||||
{ label: 'Get Notification', id: 'get_notification' },
|
|
||||||
{ label: 'Subscribe Notification', id: 'subscribe_notification' },
|
|
||||||
{ label: 'Unsubscribe Notification', id: 'unsubscribe_notification' },
|
|
||||||
{ label: 'Search Knowledge Base', id: 'search_knowledge_base' },
|
|
||||||
],
|
],
|
||||||
value: () => 'get_service_desks',
|
value: () => 'get_service_desks',
|
||||||
},
|
},
|
||||||
@@ -114,18 +92,6 @@ export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
|
|||||||
'write:request.participant:jira-service-management',
|
'write:request.participant:jira-service-management',
|
||||||
'read:request.approval:jira-service-management',
|
'read:request.approval:jira-service-management',
|
||||||
'write:request.approval:jira-service-management',
|
'write:request.approval:jira-service-management',
|
||||||
'read:request.feedback:jira-service-management',
|
|
||||||
'write:request.feedback:jira-service-management',
|
|
||||||
'delete:request.feedback:jira-service-management',
|
|
||||||
'read:request.notification:jira-service-management',
|
|
||||||
'write:request.notification:jira-service-management',
|
|
||||||
'delete:request.notification:jira-service-management',
|
|
||||||
'read:request.attachment:jira-service-management',
|
|
||||||
'read:knowledgebase:jira-service-management',
|
|
||||||
'read:organization.user:jira-service-management',
|
|
||||||
'write:organization.user:jira-service-management',
|
|
||||||
'delete:organization:jira-service-management',
|
|
||||||
'delete:servicedesk.customer:jira-service-management',
|
|
||||||
],
|
],
|
||||||
placeholder: 'Select Jira account',
|
placeholder: 'Select Jira account',
|
||||||
},
|
},
|
||||||
@@ -137,20 +103,15 @@ export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
|
|||||||
condition: {
|
condition: {
|
||||||
field: 'operation',
|
field: 'operation',
|
||||||
value: [
|
value: [
|
||||||
'get_service_desk',
|
|
||||||
'get_request_types',
|
'get_request_types',
|
||||||
'create_request',
|
'create_request',
|
||||||
'get_customers',
|
'get_customers',
|
||||||
'add_customer',
|
'add_customer',
|
||||||
'remove_customer',
|
|
||||||
'get_organizations',
|
'get_organizations',
|
||||||
'add_organization',
|
'add_organization',
|
||||||
'remove_organization',
|
|
||||||
'get_queues',
|
'get_queues',
|
||||||
'get_queue_issues',
|
|
||||||
'get_requests',
|
'get_requests',
|
||||||
'get_request_type_fields',
|
'get_request_type_fields',
|
||||||
'search_knowledge_base',
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -172,8 +133,6 @@ export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
|
|||||||
field: 'operation',
|
field: 'operation',
|
||||||
value: [
|
value: [
|
||||||
'get_request',
|
'get_request',
|
||||||
'get_request_status',
|
|
||||||
'get_request_attachments',
|
|
||||||
'add_comment',
|
'add_comment',
|
||||||
'get_comments',
|
'get_comments',
|
||||||
'get_sla',
|
'get_sla',
|
||||||
@@ -181,15 +140,8 @@ export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
|
|||||||
'transition_request',
|
'transition_request',
|
||||||
'get_participants',
|
'get_participants',
|
||||||
'add_participants',
|
'add_participants',
|
||||||
'remove_participants',
|
|
||||||
'get_approvals',
|
'get_approvals',
|
||||||
'answer_approval',
|
'answer_approval',
|
||||||
'get_feedback',
|
|
||||||
'add_feedback',
|
|
||||||
'delete_feedback',
|
|
||||||
'get_notification',
|
|
||||||
'subscribe_notification',
|
|
||||||
'unsubscribe_notification',
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -321,15 +273,7 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
required: true,
|
required: true,
|
||||||
placeholder: 'Comma-separated Atlassian account IDs',
|
placeholder: 'Comma-separated Atlassian account IDs',
|
||||||
condition: {
|
condition: { field: 'operation', value: 'add_customer' },
|
||||||
field: 'operation',
|
|
||||||
value: [
|
|
||||||
'add_customer',
|
|
||||||
'remove_customer',
|
|
||||||
'add_organization_users',
|
|
||||||
'remove_organization_users',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'customerQuery',
|
id: 'customerQuery',
|
||||||
@@ -422,18 +366,7 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
required: true,
|
required: true,
|
||||||
placeholder: 'Enter organization ID',
|
placeholder: 'Enter organization ID',
|
||||||
condition: {
|
condition: { field: 'operation', value: 'add_organization' },
|
||||||
field: 'operation',
|
|
||||||
value: [
|
|
||||||
'add_organization',
|
|
||||||
'remove_organization',
|
|
||||||
'delete_organization',
|
|
||||||
'get_organization',
|
|
||||||
'get_organization_users',
|
|
||||||
'add_organization_users',
|
|
||||||
'remove_organization_users',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'participantAccountIds',
|
id: 'participantAccountIds',
|
||||||
@@ -441,7 +374,7 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
type: 'short-input',
|
type: 'short-input',
|
||||||
required: true,
|
required: true,
|
||||||
placeholder: 'Comma-separated account IDs',
|
placeholder: 'Comma-separated account IDs',
|
||||||
condition: { field: 'operation', value: ['add_participants', 'remove_participants'] },
|
condition: { field: 'operation', value: 'add_participants' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'approvalId',
|
id: 'approvalId',
|
||||||
@@ -473,165 +406,55 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
'get_service_desks',
|
'get_service_desks',
|
||||||
'get_request_types',
|
'get_request_types',
|
||||||
'get_requests',
|
'get_requests',
|
||||||
'get_request_status',
|
|
||||||
'get_request_attachments',
|
|
||||||
'get_comments',
|
'get_comments',
|
||||||
'get_customers',
|
'get_customers',
|
||||||
'get_organizations',
|
'get_organizations',
|
||||||
'get_organization_users',
|
|
||||||
'get_queues',
|
'get_queues',
|
||||||
'get_queue_issues',
|
|
||||||
'get_sla',
|
'get_sla',
|
||||||
'get_transitions',
|
'get_transitions',
|
||||||
'get_participants',
|
'get_participants',
|
||||||
'get_approvals',
|
'get_approvals',
|
||||||
'search_knowledge_base',
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'queueId',
|
|
||||||
title: 'Queue ID',
|
|
||||||
type: 'short-input',
|
|
||||||
required: true,
|
|
||||||
placeholder: 'Enter queue ID',
|
|
||||||
condition: { field: 'operation', value: 'get_queue_issues' },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'customerEmail',
|
|
||||||
title: 'Customer Email',
|
|
||||||
type: 'short-input',
|
|
||||||
required: true,
|
|
||||||
placeholder: 'Enter customer email address',
|
|
||||||
condition: { field: 'operation', value: 'create_customer' },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'customerDisplayName',
|
|
||||||
title: 'Display Name',
|
|
||||||
type: 'short-input',
|
|
||||||
required: true,
|
|
||||||
placeholder: 'Enter customer display name',
|
|
||||||
condition: { field: 'operation', value: 'create_customer' },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'knowledgeBaseQuery',
|
|
||||||
title: 'Search Query',
|
|
||||||
type: 'short-input',
|
|
||||||
required: true,
|
|
||||||
placeholder: 'Search knowledge base articles',
|
|
||||||
condition: { field: 'operation', value: 'search_knowledge_base' },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'feedbackRating',
|
|
||||||
title: 'Rating',
|
|
||||||
type: 'dropdown',
|
|
||||||
options: [
|
|
||||||
{ label: '1 - Very Unsatisfied', id: '1' },
|
|
||||||
{ label: '2 - Unsatisfied', id: '2' },
|
|
||||||
{ label: '3 - Neutral', id: '3' },
|
|
||||||
{ label: '4 - Satisfied', id: '4' },
|
|
||||||
{ label: '5 - Very Satisfied', id: '5' },
|
|
||||||
],
|
|
||||||
value: () => '5',
|
|
||||||
condition: { field: 'operation', value: 'add_feedback' },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'feedbackComment',
|
|
||||||
title: 'Feedback Comment',
|
|
||||||
type: 'long-input',
|
|
||||||
placeholder: 'Optional feedback comment',
|
|
||||||
condition: { field: 'operation', value: 'add_feedback' },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'includeAttachments',
|
|
||||||
title: 'Include File Content',
|
|
||||||
type: 'dropdown',
|
|
||||||
options: [
|
|
||||||
{ label: 'No', id: 'false' },
|
|
||||||
{ label: 'Yes', id: 'true' },
|
|
||||||
],
|
|
||||||
value: () => 'false',
|
|
||||||
condition: { field: 'operation', value: 'get_request_attachments' },
|
|
||||||
},
|
|
||||||
// Trigger SubBlocks
|
|
||||||
...getTrigger('jsm_request_created').subBlocks,
|
|
||||||
...getTrigger('jsm_request_updated').subBlocks,
|
|
||||||
...getTrigger('jsm_request_deleted').subBlocks,
|
|
||||||
...getTrigger('jsm_request_commented').subBlocks,
|
|
||||||
...getTrigger('jsm_comment_updated').subBlocks,
|
|
||||||
...getTrigger('jsm_comment_deleted').subBlocks,
|
|
||||||
...getTrigger('jsm_worklog_created').subBlocks,
|
|
||||||
...getTrigger('jsm_worklog_updated').subBlocks,
|
|
||||||
...getTrigger('jsm_worklog_deleted').subBlocks,
|
|
||||||
...getTrigger('jsm_attachment_created').subBlocks,
|
|
||||||
...getTrigger('jsm_attachment_deleted').subBlocks,
|
|
||||||
...getTrigger('jsm_webhook').subBlocks,
|
|
||||||
],
|
],
|
||||||
tools: {
|
tools: {
|
||||||
access: [
|
access: [
|
||||||
'jsm_get_service_desks',
|
'jsm_get_service_desks',
|
||||||
'jsm_get_service_desk',
|
|
||||||
'jsm_get_request_types',
|
'jsm_get_request_types',
|
||||||
'jsm_get_request_type_fields',
|
|
||||||
'jsm_create_request',
|
'jsm_create_request',
|
||||||
'jsm_get_request',
|
'jsm_get_request',
|
||||||
'jsm_get_requests',
|
'jsm_get_requests',
|
||||||
'jsm_get_request_status',
|
|
||||||
'jsm_get_request_attachments',
|
|
||||||
'jsm_add_comment',
|
'jsm_add_comment',
|
||||||
'jsm_get_comments',
|
'jsm_get_comments',
|
||||||
'jsm_get_customers',
|
'jsm_get_customers',
|
||||||
'jsm_add_customer',
|
'jsm_add_customer',
|
||||||
'jsm_remove_customer',
|
|
||||||
'jsm_create_customer',
|
|
||||||
'jsm_get_organizations',
|
'jsm_get_organizations',
|
||||||
'jsm_get_organization',
|
|
||||||
'jsm_create_organization',
|
'jsm_create_organization',
|
||||||
'jsm_add_organization',
|
'jsm_add_organization',
|
||||||
'jsm_remove_organization',
|
|
||||||
'jsm_delete_organization',
|
|
||||||
'jsm_get_organization_users',
|
|
||||||
'jsm_add_organization_users',
|
|
||||||
'jsm_remove_organization_users',
|
|
||||||
'jsm_get_queues',
|
'jsm_get_queues',
|
||||||
'jsm_get_queue_issues',
|
|
||||||
'jsm_get_sla',
|
'jsm_get_sla',
|
||||||
'jsm_get_transitions',
|
'jsm_get_transitions',
|
||||||
'jsm_transition_request',
|
'jsm_transition_request',
|
||||||
'jsm_get_participants',
|
'jsm_get_participants',
|
||||||
'jsm_add_participants',
|
'jsm_add_participants',
|
||||||
'jsm_remove_participants',
|
|
||||||
'jsm_get_approvals',
|
'jsm_get_approvals',
|
||||||
'jsm_answer_approval',
|
'jsm_answer_approval',
|
||||||
'jsm_get_feedback',
|
'jsm_get_request_type_fields',
|
||||||
'jsm_add_feedback',
|
|
||||||
'jsm_delete_feedback',
|
|
||||||
'jsm_get_notification',
|
|
||||||
'jsm_subscribe_notification',
|
|
||||||
'jsm_unsubscribe_notification',
|
|
||||||
'jsm_search_knowledge_base',
|
|
||||||
],
|
],
|
||||||
config: {
|
config: {
|
||||||
tool: (params) => {
|
tool: (params) => {
|
||||||
switch (params.operation) {
|
switch (params.operation) {
|
||||||
case 'get_service_desks':
|
case 'get_service_desks':
|
||||||
return 'jsm_get_service_desks'
|
return 'jsm_get_service_desks'
|
||||||
case 'get_service_desk':
|
|
||||||
return 'jsm_get_service_desk'
|
|
||||||
case 'get_request_types':
|
case 'get_request_types':
|
||||||
return 'jsm_get_request_types'
|
return 'jsm_get_request_types'
|
||||||
case 'get_request_type_fields':
|
|
||||||
return 'jsm_get_request_type_fields'
|
|
||||||
case 'create_request':
|
case 'create_request':
|
||||||
return 'jsm_create_request'
|
return 'jsm_create_request'
|
||||||
case 'get_request':
|
case 'get_request':
|
||||||
return 'jsm_get_request'
|
return 'jsm_get_request'
|
||||||
case 'get_requests':
|
case 'get_requests':
|
||||||
return 'jsm_get_requests'
|
return 'jsm_get_requests'
|
||||||
case 'get_request_status':
|
|
||||||
return 'jsm_get_request_status'
|
|
||||||
case 'get_request_attachments':
|
|
||||||
return 'jsm_get_request_attachments'
|
|
||||||
case 'add_comment':
|
case 'add_comment':
|
||||||
return 'jsm_add_comment'
|
return 'jsm_add_comment'
|
||||||
case 'get_comments':
|
case 'get_comments':
|
||||||
@@ -640,32 +463,14 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
return 'jsm_get_customers'
|
return 'jsm_get_customers'
|
||||||
case 'add_customer':
|
case 'add_customer':
|
||||||
return 'jsm_add_customer'
|
return 'jsm_add_customer'
|
||||||
case 'remove_customer':
|
|
||||||
return 'jsm_remove_customer'
|
|
||||||
case 'create_customer':
|
|
||||||
return 'jsm_create_customer'
|
|
||||||
case 'get_organizations':
|
case 'get_organizations':
|
||||||
return 'jsm_get_organizations'
|
return 'jsm_get_organizations'
|
||||||
case 'get_organization':
|
|
||||||
return 'jsm_get_organization'
|
|
||||||
case 'create_organization':
|
case 'create_organization':
|
||||||
return 'jsm_create_organization'
|
return 'jsm_create_organization'
|
||||||
case 'add_organization':
|
case 'add_organization':
|
||||||
return 'jsm_add_organization'
|
return 'jsm_add_organization'
|
||||||
case 'remove_organization':
|
|
||||||
return 'jsm_remove_organization'
|
|
||||||
case 'delete_organization':
|
|
||||||
return 'jsm_delete_organization'
|
|
||||||
case 'get_organization_users':
|
|
||||||
return 'jsm_get_organization_users'
|
|
||||||
case 'add_organization_users':
|
|
||||||
return 'jsm_add_organization_users'
|
|
||||||
case 'remove_organization_users':
|
|
||||||
return 'jsm_remove_organization_users'
|
|
||||||
case 'get_queues':
|
case 'get_queues':
|
||||||
return 'jsm_get_queues'
|
return 'jsm_get_queues'
|
||||||
case 'get_queue_issues':
|
|
||||||
return 'jsm_get_queue_issues'
|
|
||||||
case 'get_sla':
|
case 'get_sla':
|
||||||
return 'jsm_get_sla'
|
return 'jsm_get_sla'
|
||||||
case 'get_transitions':
|
case 'get_transitions':
|
||||||
@@ -676,26 +481,12 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
return 'jsm_get_participants'
|
return 'jsm_get_participants'
|
||||||
case 'add_participants':
|
case 'add_participants':
|
||||||
return 'jsm_add_participants'
|
return 'jsm_add_participants'
|
||||||
case 'remove_participants':
|
|
||||||
return 'jsm_remove_participants'
|
|
||||||
case 'get_approvals':
|
case 'get_approvals':
|
||||||
return 'jsm_get_approvals'
|
return 'jsm_get_approvals'
|
||||||
case 'answer_approval':
|
case 'answer_approval':
|
||||||
return 'jsm_answer_approval'
|
return 'jsm_answer_approval'
|
||||||
case 'get_feedback':
|
case 'get_request_type_fields':
|
||||||
return 'jsm_get_feedback'
|
return 'jsm_get_request_type_fields'
|
||||||
case 'add_feedback':
|
|
||||||
return 'jsm_add_feedback'
|
|
||||||
case 'delete_feedback':
|
|
||||||
return 'jsm_delete_feedback'
|
|
||||||
case 'get_notification':
|
|
||||||
return 'jsm_get_notification'
|
|
||||||
case 'subscribe_notification':
|
|
||||||
return 'jsm_subscribe_notification'
|
|
||||||
case 'unsubscribe_notification':
|
|
||||||
return 'jsm_unsubscribe_notification'
|
|
||||||
case 'search_knowledge_base':
|
|
||||||
return 'jsm_search_knowledge_base'
|
|
||||||
default:
|
default:
|
||||||
return 'jsm_get_service_desks'
|
return 'jsm_get_service_desks'
|
||||||
}
|
}
|
||||||
@@ -940,204 +731,6 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
serviceDeskId: params.serviceDeskId,
|
serviceDeskId: params.serviceDeskId,
|
||||||
requestTypeId: params.requestTypeId,
|
requestTypeId: params.requestTypeId,
|
||||||
}
|
}
|
||||||
case 'get_service_desk':
|
|
||||||
if (!params.serviceDeskId) {
|
|
||||||
throw new Error('Service Desk ID is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
serviceDeskId: params.serviceDeskId,
|
|
||||||
}
|
|
||||||
case 'get_request_status':
|
|
||||||
if (!params.issueIdOrKey) {
|
|
||||||
throw new Error('Issue ID or key is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
issueIdOrKey: params.issueIdOrKey,
|
|
||||||
limit: params.maxResults ? Number.parseInt(params.maxResults) : undefined,
|
|
||||||
}
|
|
||||||
case 'get_request_attachments':
|
|
||||||
if (!params.issueIdOrKey) {
|
|
||||||
throw new Error('Issue ID or key is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
issueIdOrKey: params.issueIdOrKey,
|
|
||||||
includeAttachments: params.includeAttachments === 'true',
|
|
||||||
limit: params.maxResults ? Number.parseInt(params.maxResults) : undefined,
|
|
||||||
}
|
|
||||||
case 'remove_customer': {
|
|
||||||
if (!params.serviceDeskId) {
|
|
||||||
throw new Error('Service Desk ID is required')
|
|
||||||
}
|
|
||||||
if (!params.accountIds) {
|
|
||||||
throw new Error('Account IDs are required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
serviceDeskId: params.serviceDeskId,
|
|
||||||
accountIds: params.accountIds,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'create_customer':
|
|
||||||
if (!params.customerEmail) {
|
|
||||||
throw new Error('Customer email is required')
|
|
||||||
}
|
|
||||||
if (!params.customerDisplayName) {
|
|
||||||
throw new Error('Customer display name is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
email: params.customerEmail,
|
|
||||||
displayName: params.customerDisplayName,
|
|
||||||
}
|
|
||||||
case 'get_organization':
|
|
||||||
if (!params.organizationId) {
|
|
||||||
throw new Error('Organization ID is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
organizationId: params.organizationId,
|
|
||||||
}
|
|
||||||
case 'remove_organization':
|
|
||||||
if (!params.serviceDeskId) {
|
|
||||||
throw new Error('Service Desk ID is required')
|
|
||||||
}
|
|
||||||
if (!params.organizationId) {
|
|
||||||
throw new Error('Organization ID is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
serviceDeskId: params.serviceDeskId,
|
|
||||||
organizationId: params.organizationId,
|
|
||||||
}
|
|
||||||
case 'delete_organization':
|
|
||||||
if (!params.organizationId) {
|
|
||||||
throw new Error('Organization ID is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
organizationId: params.organizationId,
|
|
||||||
}
|
|
||||||
case 'get_organization_users':
|
|
||||||
if (!params.organizationId) {
|
|
||||||
throw new Error('Organization ID is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
organizationId: params.organizationId,
|
|
||||||
limit: params.maxResults ? Number.parseInt(params.maxResults) : undefined,
|
|
||||||
}
|
|
||||||
case 'add_organization_users':
|
|
||||||
if (!params.organizationId) {
|
|
||||||
throw new Error('Organization ID is required')
|
|
||||||
}
|
|
||||||
if (!params.accountIds) {
|
|
||||||
throw new Error('Account IDs are required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
organizationId: params.organizationId,
|
|
||||||
accountIds: params.accountIds,
|
|
||||||
}
|
|
||||||
case 'remove_organization_users':
|
|
||||||
if (!params.organizationId) {
|
|
||||||
throw new Error('Organization ID is required')
|
|
||||||
}
|
|
||||||
if (!params.accountIds) {
|
|
||||||
throw new Error('Account IDs are required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
organizationId: params.organizationId,
|
|
||||||
accountIds: params.accountIds,
|
|
||||||
}
|
|
||||||
case 'get_queue_issues':
|
|
||||||
if (!params.serviceDeskId) {
|
|
||||||
throw new Error('Service Desk ID is required')
|
|
||||||
}
|
|
||||||
if (!params.queueId) {
|
|
||||||
throw new Error('Queue ID is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
serviceDeskId: params.serviceDeskId,
|
|
||||||
queueId: params.queueId,
|
|
||||||
limit: params.maxResults ? Number.parseInt(params.maxResults) : undefined,
|
|
||||||
}
|
|
||||||
case 'remove_participants':
|
|
||||||
if (!params.issueIdOrKey) {
|
|
||||||
throw new Error('Issue ID or key is required')
|
|
||||||
}
|
|
||||||
if (!params.participantAccountIds) {
|
|
||||||
throw new Error('Account IDs are required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
issueIdOrKey: params.issueIdOrKey,
|
|
||||||
accountIds: params.participantAccountIds,
|
|
||||||
}
|
|
||||||
case 'get_feedback':
|
|
||||||
if (!params.issueIdOrKey) {
|
|
||||||
throw new Error('Issue ID or key is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
issueIdOrKey: params.issueIdOrKey,
|
|
||||||
}
|
|
||||||
case 'add_feedback':
|
|
||||||
if (!params.issueIdOrKey) {
|
|
||||||
throw new Error('Issue ID or key is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
issueIdOrKey: params.issueIdOrKey,
|
|
||||||
rating: Number.parseInt(params.feedbackRating || '5'),
|
|
||||||
comment: params.feedbackComment,
|
|
||||||
}
|
|
||||||
case 'delete_feedback':
|
|
||||||
if (!params.issueIdOrKey) {
|
|
||||||
throw new Error('Issue ID or key is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
issueIdOrKey: params.issueIdOrKey,
|
|
||||||
}
|
|
||||||
case 'get_notification':
|
|
||||||
if (!params.issueIdOrKey) {
|
|
||||||
throw new Error('Issue ID or key is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
issueIdOrKey: params.issueIdOrKey,
|
|
||||||
}
|
|
||||||
case 'subscribe_notification':
|
|
||||||
if (!params.issueIdOrKey) {
|
|
||||||
throw new Error('Issue ID or key is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
issueIdOrKey: params.issueIdOrKey,
|
|
||||||
}
|
|
||||||
case 'unsubscribe_notification':
|
|
||||||
if (!params.issueIdOrKey) {
|
|
||||||
throw new Error('Issue ID or key is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
issueIdOrKey: params.issueIdOrKey,
|
|
||||||
}
|
|
||||||
case 'search_knowledge_base':
|
|
||||||
if (!params.knowledgeBaseQuery) {
|
|
||||||
throw new Error('Search query is required')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...baseParams,
|
|
||||||
serviceDeskId: params.serviceDeskId,
|
|
||||||
query: params.knowledgeBaseQuery,
|
|
||||||
limit: params.maxResults ? Number.parseInt(params.maxResults) : undefined,
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return baseParams
|
return baseParams
|
||||||
}
|
}
|
||||||
@@ -1186,16 +779,6 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
searchQuery: { type: 'string', description: 'Filter request types by name' },
|
searchQuery: { type: 'string', description: 'Filter request types by name' },
|
||||||
groupId: { type: 'string', description: 'Filter by request type group ID' },
|
groupId: { type: 'string', description: 'Filter by request type group ID' },
|
||||||
expand: { type: 'string', description: 'Comma-separated fields to expand' },
|
expand: { type: 'string', description: 'Comma-separated fields to expand' },
|
||||||
queueId: { type: 'string', description: 'Queue ID' },
|
|
||||||
customerEmail: { type: 'string', description: 'Customer email address' },
|
|
||||||
customerDisplayName: { type: 'string', description: 'Customer display name' },
|
|
||||||
knowledgeBaseQuery: { type: 'string', description: 'Knowledge base search query' },
|
|
||||||
feedbackRating: { type: 'string', description: 'CSAT feedback rating (1-5)' },
|
|
||||||
feedbackComment: { type: 'string', description: 'CSAT feedback comment' },
|
|
||||||
includeAttachments: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Whether to download attachment file content',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||||
@@ -1227,19 +810,6 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
total: { type: 'number', description: 'Total count' },
|
total: { type: 'number', description: 'Total count' },
|
||||||
isLastPage: { type: 'boolean', description: 'Whether this is the last page' },
|
isLastPage: { type: 'boolean', description: 'Whether this is the last page' },
|
||||||
requestTypeFields: { type: 'json', description: 'Array of request type fields' },
|
requestTypeFields: { type: 'json', description: 'Array of request type fields' },
|
||||||
rating: { type: 'number', description: 'CSAT feedback rating' },
|
|
||||||
subscribed: { type: 'boolean', description: 'Whether subscribed to notifications' },
|
|
||||||
articles: { type: 'json', description: 'Array of knowledge base articles' },
|
|
||||||
statuses: { type: 'json', description: 'Array of request status history entries' },
|
|
||||||
attachments: { type: 'json', description: 'Array of attachment metadata' },
|
|
||||||
issues: { type: 'json', description: 'Array of queue issues' },
|
|
||||||
users: { type: 'json', description: 'Array of organization users' },
|
|
||||||
id: { type: 'string', description: 'Resource ID' },
|
|
||||||
projectId: { type: 'string', description: 'Service desk project ID' },
|
|
||||||
projectName: { type: 'string', description: 'Service desk project name' },
|
|
||||||
projectKey: { type: 'string', description: 'Service desk project key' },
|
|
||||||
email: { type: 'string', description: 'Customer email address' },
|
|
||||||
displayName: { type: 'string', description: 'Customer display name' },
|
|
||||||
canAddRequestParticipants: {
|
canAddRequestParticipants: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'Whether participants can be added to this request type',
|
description: 'Whether participants can be added to this request type',
|
||||||
@@ -1248,36 +818,5 @@ Return ONLY the comment text - no explanations.`,
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'Whether requests can be raised on behalf of another user',
|
description: 'Whether requests can be raised on behalf of another user',
|
||||||
},
|
},
|
||||||
// Trigger outputs (from webhook events)
|
|
||||||
webhookEvent: { type: 'string', description: 'Webhook event type' },
|
|
||||||
issue: { type: 'json', description: 'Complete issue object from webhook' },
|
|
||||||
changelog: { type: 'json', description: 'Changelog object (for update events)' },
|
|
||||||
comment: { type: 'json', description: 'Comment object (for comment events)' },
|
|
||||||
worklog: { type: 'json', description: 'Worklog object (for worklog events)' },
|
|
||||||
attachment: { type: 'json', description: 'Attachment metadata (for attachment events)' },
|
|
||||||
files: {
|
|
||||||
type: 'file[]',
|
|
||||||
description:
|
|
||||||
'Downloaded file attachments (if includeFiles is enabled and Jira credentials are provided)',
|
|
||||||
},
|
|
||||||
user: { type: 'json', description: 'User object who triggered the event' },
|
|
||||||
webhook: { type: 'json', description: 'Complete webhook payload' },
|
|
||||||
},
|
|
||||||
triggers: {
|
|
||||||
enabled: true,
|
|
||||||
available: [
|
|
||||||
'jsm_request_created',
|
|
||||||
'jsm_request_updated',
|
|
||||||
'jsm_request_deleted',
|
|
||||||
'jsm_request_commented',
|
|
||||||
'jsm_comment_updated',
|
|
||||||
'jsm_comment_deleted',
|
|
||||||
'jsm_worklog_created',
|
|
||||||
'jsm_worklog_updated',
|
|
||||||
'jsm_worklog_deleted',
|
|
||||||
'jsm_attachment_created',
|
|
||||||
'jsm_attachment_deleted',
|
|
||||||
'jsm_webhook',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
slug: enterprise
|
slug: enterprise
|
||||||
title: 'Build with Sim for Enterprise'
|
title: 'Build with Sim for Enterprise'
|
||||||
description: 'Access control, BYOK, self-hosted deployments, on-prem Copilot, SSO & SAML, whitelabeling, Admin API, and flexible data retention—enterprise features for teams with strict security and compliance requirements.'
|
description: 'Access control, BYOK, self-hosted deployments, on-prem Copilot, SSO & SAML, whitelabeling, Admin API, and flexible data retention—enterprise features for teams with strict security and compliance requirements.'
|
||||||
date: 2026-02-11
|
date: 2026-01-23
|
||||||
updated: 2026-02-11
|
updated: 2026-01-23
|
||||||
authors:
|
authors:
|
||||||
- vik
|
- vik
|
||||||
readingTime: 10
|
readingTime: 10
|
||||||
@@ -13,8 +13,8 @@ ogAlt: 'Sim Enterprise features overview'
|
|||||||
about: ['Enterprise Software', 'Security', 'Compliance', 'Self-Hosting']
|
about: ['Enterprise Software', 'Security', 'Compliance', 'Self-Hosting']
|
||||||
timeRequired: PT10M
|
timeRequired: PT10M
|
||||||
canonical: https://sim.ai/studio/enterprise
|
canonical: https://sim.ai/studio/enterprise
|
||||||
featured: true
|
featured: false
|
||||||
draft: false
|
draft: true
|
||||||
---
|
---
|
||||||
|
|
||||||
We've been working with security teams at larger organizations to bring Sim into environments with strict compliance and data handling requirements. This post covers the enterprise capabilities we've built: granular access control, bring-your-own-keys, self-hosted deployments, on-prem Copilot, SSO & SAML, whitelabeling, compliance, and programmatic management via the Admin API.
|
We've been working with security teams at larger organizations to bring Sim into environments with strict compliance and data handling requirements. This post covers the enterprise capabilities we've built: granular access control, bring-your-own-keys, self-hosted deployments, on-prem Copilot, SSO & SAML, whitelabeling, compliance, and programmatic management via the Admin API.
|
||||||
|
|||||||
@@ -999,7 +999,6 @@ export class AgentBlockHandler implements BlockHandler {
|
|||||||
reasoningEffort: inputs.reasoningEffort,
|
reasoningEffort: inputs.reasoningEffort,
|
||||||
verbosity: inputs.verbosity,
|
verbosity: inputs.verbosity,
|
||||||
thinkingLevel: inputs.thinkingLevel,
|
thinkingLevel: inputs.thinkingLevel,
|
||||||
previousInteractionId: inputs.previousInteractionId,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1070,7 +1069,6 @@ export class AgentBlockHandler implements BlockHandler {
|
|||||||
reasoningEffort: providerRequest.reasoningEffort,
|
reasoningEffort: providerRequest.reasoningEffort,
|
||||||
verbosity: providerRequest.verbosity,
|
verbosity: providerRequest.verbosity,
|
||||||
thinkingLevel: providerRequest.thinkingLevel,
|
thinkingLevel: providerRequest.thinkingLevel,
|
||||||
previousInteractionId: providerRequest.previousInteractionId,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.processProviderResponse(response, block, responseFormat)
|
return this.processProviderResponse(response, block, responseFormat)
|
||||||
@@ -1271,7 +1269,6 @@ export class AgentBlockHandler implements BlockHandler {
|
|||||||
content: result.content,
|
content: result.content,
|
||||||
model: result.model,
|
model: result.model,
|
||||||
...this.createResponseMetadata(result),
|
...this.createResponseMetadata(result),
|
||||||
...(result.interactionId && { interactionId: result.interactionId }),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ export interface AgentInputs {
|
|||||||
conversationId?: string // Required for all non-none memory types
|
conversationId?: string // Required for all non-none memory types
|
||||||
slidingWindowSize?: string // For message-based sliding window
|
slidingWindowSize?: string // For message-based sliding window
|
||||||
slidingWindowTokens?: string // For token-based sliding window
|
slidingWindowTokens?: string // For token-based sliding window
|
||||||
// Deep research multi-turn
|
|
||||||
previousInteractionId?: string // Interactions API previous interaction reference
|
|
||||||
// LLM parameters
|
// LLM parameters
|
||||||
temperature?: string
|
temperature?: string
|
||||||
maxTokens?: string
|
maxTokens?: string
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ export interface BuildPayloadParams {
|
|||||||
fileAttachments?: Array<{ id: string; key: string; size: number; [key: string]: unknown }>
|
fileAttachments?: Array<{ id: string; key: string; size: number; [key: string]: unknown }>
|
||||||
commands?: string[]
|
commands?: string[]
|
||||||
chatId?: string
|
chatId?: string
|
||||||
conversationId?: string
|
|
||||||
prefetch?: boolean
|
|
||||||
implicitFeedback?: string
|
implicitFeedback?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,10 +64,6 @@ export async function buildCopilotRequestPayload(
|
|||||||
fileAttachments,
|
fileAttachments,
|
||||||
commands,
|
commands,
|
||||||
chatId,
|
chatId,
|
||||||
conversationId,
|
|
||||||
prefetch,
|
|
||||||
conversationHistory,
|
|
||||||
implicitFeedback,
|
|
||||||
} = params
|
} = params
|
||||||
|
|
||||||
const selectedModel = options.selectedModel
|
const selectedModel = options.selectedModel
|
||||||
@@ -160,12 +154,6 @@ export async function buildCopilotRequestPayload(
|
|||||||
version: SIM_AGENT_VERSION,
|
version: SIM_AGENT_VERSION,
|
||||||
...(contexts && contexts.length > 0 ? { context: contexts } : {}),
|
...(contexts && contexts.length > 0 ? { context: contexts } : {}),
|
||||||
...(chatId ? { chatId } : {}),
|
...(chatId ? { chatId } : {}),
|
||||||
...(conversationId ? { conversationId } : {}),
|
|
||||||
...(Array.isArray(conversationHistory) && conversationHistory.length > 0
|
|
||||||
? { conversationHistory }
|
|
||||||
: {}),
|
|
||||||
...(typeof prefetch === 'boolean' ? { prefetch } : {}),
|
|
||||||
...(implicitFeedback ? { implicitFeedback } : {}),
|
|
||||||
...(processedFileContents.length > 0 ? { fileAttachments: processedFileContents } : {}),
|
...(processedFileContents.length > 0 ? { fileAttachments: processedFileContents } : {}),
|
||||||
...(integrationTools.length > 0 ? { integrationTools } : {}),
|
...(integrationTools.length > 0 ? { integrationTools } : {}),
|
||||||
...(credentials ? { credentials } : {}),
|
...(credentials ? { credentials } : {}),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { db } from '@sim/db'
|
import { db } from '@sim/db'
|
||||||
import { customTools, workflow } from '@sim/db/schema'
|
import { workflow } from '@sim/db/schema'
|
||||||
import { createLogger } from '@sim/logger'
|
import { createLogger } from '@sim/logger'
|
||||||
import { and, desc, eq, isNull, or } from 'drizzle-orm'
|
import { eq } from 'drizzle-orm'
|
||||||
import { SIM_AGENT_API_URL } from '@/lib/copilot/constants'
|
import { SIM_AGENT_API_URL } from '@/lib/copilot/constants'
|
||||||
import type {
|
import type {
|
||||||
ExecutionContext,
|
ExecutionContext,
|
||||||
@@ -12,7 +12,6 @@ import { routeExecution } from '@/lib/copilot/tools/server/router'
|
|||||||
import { env } from '@/lib/core/config/env'
|
import { env } from '@/lib/core/config/env'
|
||||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||||
import { getEffectiveDecryptedEnv } from '@/lib/environment/utils'
|
import { getEffectiveDecryptedEnv } from '@/lib/environment/utils'
|
||||||
import { upsertCustomTools } from '@/lib/workflows/custom-tools/operations'
|
|
||||||
import { getTool, resolveToolId } from '@/tools/utils'
|
import { getTool, resolveToolId } from '@/tools/utils'
|
||||||
import {
|
import {
|
||||||
executeCheckDeploymentStatus,
|
executeCheckDeploymentStatus,
|
||||||
@@ -77,247 +76,6 @@ import {
|
|||||||
|
|
||||||
const logger = createLogger('CopilotToolExecutor')
|
const logger = createLogger('CopilotToolExecutor')
|
||||||
|
|
||||||
type ManageCustomToolOperation = 'add' | 'edit' | 'delete' | 'list'
|
|
||||||
|
|
||||||
interface ManageCustomToolSchema {
|
|
||||||
type: 'function'
|
|
||||||
function: {
|
|
||||||
name: string
|
|
||||||
description?: string
|
|
||||||
parameters: Record<string, unknown>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ManageCustomToolParams {
|
|
||||||
operation?: string
|
|
||||||
toolId?: string
|
|
||||||
schema?: ManageCustomToolSchema
|
|
||||||
code?: string
|
|
||||||
title?: string
|
|
||||||
workspaceId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
async function executeManageCustomTool(
|
|
||||||
rawParams: Record<string, unknown>,
|
|
||||||
context: ExecutionContext
|
|
||||||
): Promise<ToolCallResult> {
|
|
||||||
const params = rawParams as ManageCustomToolParams
|
|
||||||
const operation = String(params.operation || '').toLowerCase() as ManageCustomToolOperation
|
|
||||||
const workspaceId = params.workspaceId || context.workspaceId
|
|
||||||
|
|
||||||
if (!operation) {
|
|
||||||
return { success: false, error: "Missing required 'operation' argument" }
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (operation === 'list') {
|
|
||||||
const toolsForUser = workspaceId
|
|
||||||
? await db
|
|
||||||
.select()
|
|
||||||
.from(customTools)
|
|
||||||
.where(
|
|
||||||
or(
|
|
||||||
eq(customTools.workspaceId, workspaceId),
|
|
||||||
and(isNull(customTools.workspaceId), eq(customTools.userId, context.userId))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.orderBy(desc(customTools.createdAt))
|
|
||||||
: await db
|
|
||||||
.select()
|
|
||||||
.from(customTools)
|
|
||||||
.where(and(isNull(customTools.workspaceId), eq(customTools.userId, context.userId)))
|
|
||||||
.orderBy(desc(customTools.createdAt))
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
success: true,
|
|
||||||
operation,
|
|
||||||
tools: toolsForUser,
|
|
||||||
count: toolsForUser.length,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation === 'add') {
|
|
||||||
if (!workspaceId) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "workspaceId is required for operation 'add'",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!params.schema || !params.code) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "Both 'schema' and 'code' are required for operation 'add'",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = params.title || params.schema.function?.name
|
|
||||||
if (!title) {
|
|
||||||
return { success: false, error: "Missing tool title or schema.function.name for 'add'" }
|
|
||||||
}
|
|
||||||
|
|
||||||
const resultTools = await upsertCustomTools({
|
|
||||||
tools: [
|
|
||||||
{
|
|
||||||
title,
|
|
||||||
schema: params.schema,
|
|
||||||
code: params.code,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
userId: context.userId,
|
|
||||||
})
|
|
||||||
const created = resultTools.find((tool) => tool.title === title)
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
success: true,
|
|
||||||
operation,
|
|
||||||
toolId: created?.id,
|
|
||||||
title,
|
|
||||||
message: `Created custom tool "${title}"`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation === 'edit') {
|
|
||||||
if (!workspaceId) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "workspaceId is required for operation 'edit'",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!params.toolId) {
|
|
||||||
return { success: false, error: "'toolId' is required for operation 'edit'" }
|
|
||||||
}
|
|
||||||
if (!params.schema && !params.code) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "At least one of 'schema' or 'code' is required for operation 'edit'",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const workspaceTool = await db
|
|
||||||
.select()
|
|
||||||
.from(customTools)
|
|
||||||
.where(and(eq(customTools.id, params.toolId), eq(customTools.workspaceId, workspaceId)))
|
|
||||||
.limit(1)
|
|
||||||
|
|
||||||
const legacyTool =
|
|
||||||
workspaceTool.length === 0
|
|
||||||
? await db
|
|
||||||
.select()
|
|
||||||
.from(customTools)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(customTools.id, params.toolId),
|
|
||||||
isNull(customTools.workspaceId),
|
|
||||||
eq(customTools.userId, context.userId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1)
|
|
||||||
: []
|
|
||||||
|
|
||||||
const existing = workspaceTool[0] || legacyTool[0]
|
|
||||||
if (!existing) {
|
|
||||||
return { success: false, error: `Custom tool not found: ${params.toolId}` }
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedSchema = params.schema || (existing.schema as ManageCustomToolSchema)
|
|
||||||
const mergedCode = params.code || existing.code
|
|
||||||
const title = params.title || mergedSchema.function?.name || existing.title
|
|
||||||
|
|
||||||
await upsertCustomTools({
|
|
||||||
tools: [
|
|
||||||
{
|
|
||||||
id: params.toolId,
|
|
||||||
title,
|
|
||||||
schema: mergedSchema,
|
|
||||||
code: mergedCode,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
userId: context.userId,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
success: true,
|
|
||||||
operation,
|
|
||||||
toolId: params.toolId,
|
|
||||||
title,
|
|
||||||
message: `Updated custom tool "${title}"`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation === 'delete') {
|
|
||||||
if (!params.toolId) {
|
|
||||||
return { success: false, error: "'toolId' is required for operation 'delete'" }
|
|
||||||
}
|
|
||||||
|
|
||||||
const workspaceDelete =
|
|
||||||
workspaceId != null
|
|
||||||
? await db
|
|
||||||
.delete(customTools)
|
|
||||||
.where(
|
|
||||||
and(eq(customTools.id, params.toolId), eq(customTools.workspaceId, workspaceId))
|
|
||||||
)
|
|
||||||
.returning({ id: customTools.id })
|
|
||||||
: []
|
|
||||||
|
|
||||||
const legacyDelete =
|
|
||||||
workspaceDelete.length === 0
|
|
||||||
? await db
|
|
||||||
.delete(customTools)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(customTools.id, params.toolId),
|
|
||||||
isNull(customTools.workspaceId),
|
|
||||||
eq(customTools.userId, context.userId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.returning({ id: customTools.id })
|
|
||||||
: []
|
|
||||||
|
|
||||||
const deleted = workspaceDelete[0] || legacyDelete[0]
|
|
||||||
if (!deleted) {
|
|
||||||
return { success: false, error: `Custom tool not found: ${params.toolId}` }
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
success: true,
|
|
||||||
operation,
|
|
||||||
toolId: params.toolId,
|
|
||||||
message: 'Deleted custom tool',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: `Unsupported operation for manage_custom_tool: ${operation}`,
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('manage_custom_tool execution failed', {
|
|
||||||
operation,
|
|
||||||
workspaceId,
|
|
||||||
userId: context.userId,
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Failed to manage custom tool',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SERVER_TOOLS = new Set<string>([
|
const SERVER_TOOLS = new Set<string>([
|
||||||
'get_blocks_and_tools',
|
'get_blocks_and_tools',
|
||||||
'get_blocks_metadata',
|
'get_blocks_metadata',
|
||||||
@@ -403,19 +161,6 @@ const SIM_WORKFLOW_TOOL_HANDLERS: Record<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
oauth_request_access: async (p, _c) => {
|
|
||||||
const providerName = (p.providerName || p.provider_name || 'the provider') as string
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
success: true,
|
|
||||||
status: 'requested',
|
|
||||||
providerName,
|
|
||||||
message: `Requested ${providerName} OAuth connection. The user should complete the OAuth modal in the UI, then retry credential-dependent actions.`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
manage_custom_tool: (p, c) => executeManageCustomTool(p, c),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -312,12 +312,6 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
|||||||
'read:attachment:confluence',
|
'read:attachment:confluence',
|
||||||
'write:attachment:confluence',
|
'write:attachment:confluence',
|
||||||
'search:confluence',
|
'search:confluence',
|
||||||
'read:blogpost:confluence',
|
|
||||||
'write:blogpost:confluence',
|
|
||||||
'read:content.property:confluence',
|
|
||||||
'write:content.property:confluence',
|
|
||||||
'read:hierarchical-content:confluence',
|
|
||||||
'read:content.metadata:confluence',
|
|
||||||
'read:me',
|
'read:me',
|
||||||
'offline_access',
|
'offline_access',
|
||||||
],
|
],
|
||||||
@@ -374,14 +368,6 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
|||||||
'read:comment.property:jira',
|
'read:comment.property:jira',
|
||||||
'read:jql:jira',
|
'read:jql:jira',
|
||||||
'read:field:jira',
|
'read:field:jira',
|
||||||
// Project management (components, versions)
|
|
||||||
'manage:jira-project',
|
|
||||||
// Jira Software / Agile scopes (no classic equivalent)
|
|
||||||
'read:board-scope:jira-software',
|
|
||||||
'write:board-scope:jira-software',
|
|
||||||
'read:sprint:jira-software',
|
|
||||||
'write:sprint:jira-software',
|
|
||||||
'delete:sprint:jira-software',
|
|
||||||
// Jira Service Management scopes
|
// Jira Service Management scopes
|
||||||
'read:servicedesk:jira-service-management',
|
'read:servicedesk:jira-service-management',
|
||||||
'read:requesttype:jira-service-management',
|
'read:requesttype:jira-service-management',
|
||||||
@@ -411,16 +397,6 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
|||||||
'write:request.participant:jira-service-management',
|
'write:request.participant:jira-service-management',
|
||||||
'read:request.approval:jira-service-management',
|
'read:request.approval:jira-service-management',
|
||||||
'write:request.approval:jira-service-management',
|
'write:request.approval:jira-service-management',
|
||||||
'read:request.feedback:jira-service-management',
|
|
||||||
'write:request.feedback:jira-service-management',
|
|
||||||
'delete:request.feedback:jira-service-management',
|
|
||||||
'read:request.notification:jira-service-management',
|
|
||||||
'write:request.notification:jira-service-management',
|
|
||||||
'delete:request.notification:jira-service-management',
|
|
||||||
'read:request.attachment:jira-service-management',
|
|
||||||
'read:knowledgebase:jira-service-management',
|
|
||||||
'delete:organization:jira-service-management',
|
|
||||||
'delete:servicedesk.customer:jira-service-management',
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,11 +27,9 @@ import {
|
|||||||
import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils'
|
import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils'
|
||||||
import { executeWebhookJob } from '@/background/webhook-execution'
|
import { executeWebhookJob } from '@/background/webhook-execution'
|
||||||
import { resolveEnvVarReferences } from '@/executor/utils/reference-validation'
|
import { resolveEnvVarReferences } from '@/executor/utils/reference-validation'
|
||||||
import { isConfluenceEventMatch } from '@/triggers/confluence/utils'
|
|
||||||
import { isGitHubEventMatch } from '@/triggers/github/utils'
|
import { isGitHubEventMatch } from '@/triggers/github/utils'
|
||||||
import { isHubSpotContactEventMatch } from '@/triggers/hubspot/utils'
|
import { isHubSpotContactEventMatch } from '@/triggers/hubspot/utils'
|
||||||
import { isJiraEventMatch } from '@/triggers/jira/utils'
|
import { isJiraEventMatch } from '@/triggers/jira/utils'
|
||||||
import { isJsmEventMatch } from '@/triggers/jsm/utils'
|
|
||||||
|
|
||||||
const logger = createLogger('WebhookProcessor')
|
const logger = createLogger('WebhookProcessor')
|
||||||
|
|
||||||
@@ -683,7 +681,7 @@ export async function verifyProviderAuth(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundWebhook.provider === 'jira' || foundWebhook.provider === 'jira_service_management') {
|
if (foundWebhook.provider === 'jira') {
|
||||||
const secret = providerConfig.secret as string | undefined
|
const secret = providerConfig.secret as string | undefined
|
||||||
|
|
||||||
if (secret) {
|
if (secret) {
|
||||||
@@ -708,31 +706,6 @@ export async function verifyProviderAuth(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundWebhook.provider === 'confluence') {
|
|
||||||
const secret = providerConfig.secret as string | undefined
|
|
||||||
|
|
||||||
if (secret) {
|
|
||||||
const signature = request.headers.get('X-Hub-Signature')
|
|
||||||
|
|
||||||
if (!signature) {
|
|
||||||
logger.warn(`[${requestId}] Confluence webhook missing signature header`)
|
|
||||||
return new NextResponse('Unauthorized - Missing Confluence signature', { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const isValidSignature = validateJiraSignature(secret, signature, rawBody)
|
|
||||||
|
|
||||||
if (!isValidSignature) {
|
|
||||||
logger.warn(`[${requestId}] Confluence signature verification failed`, {
|
|
||||||
signatureLength: signature.length,
|
|
||||||
secretLength: secret.length,
|
|
||||||
})
|
|
||||||
return new NextResponse('Unauthorized - Invalid Confluence signature', { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`[${requestId}] Confluence signature verified successfully`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundWebhook.provider === 'github') {
|
if (foundWebhook.provider === 'github') {
|
||||||
const secret = providerConfig.secret as string | undefined
|
const secret = providerConfig.secret as string | undefined
|
||||||
|
|
||||||
@@ -956,60 +929,6 @@ export async function queueWebhookExecution(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSM event filtering for event-specific triggers
|
|
||||||
if (foundWebhook.provider === 'jira_service_management') {
|
|
||||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
|
||||||
const triggerId = providerConfig.triggerId as string | undefined
|
|
||||||
|
|
||||||
if (triggerId && triggerId !== 'jsm_webhook') {
|
|
||||||
const webhookEvent = body.webhookEvent as string | undefined
|
|
||||||
|
|
||||||
if (!isJsmEventMatch(triggerId, webhookEvent || '', body)) {
|
|
||||||
logger.debug(
|
|
||||||
`[${options.requestId}] JSM event mismatch for trigger ${triggerId}. Event: ${webhookEvent}. Skipping execution.`,
|
|
||||||
{
|
|
||||||
webhookId: foundWebhook.id,
|
|
||||||
workflowId: foundWorkflow.id,
|
|
||||||
triggerId,
|
|
||||||
receivedEvent: webhookEvent,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Return 200 OK to prevent Jira from retrying
|
|
||||||
return NextResponse.json({
|
|
||||||
message: 'Event type does not match trigger configuration. Ignoring.',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confluence event filtering for event-specific triggers
|
|
||||||
if (foundWebhook.provider === 'confluence') {
|
|
||||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
|
||||||
const triggerId = providerConfig.triggerId as string | undefined
|
|
||||||
|
|
||||||
if (triggerId && triggerId !== 'confluence_webhook') {
|
|
||||||
const event = body.event as string | undefined
|
|
||||||
|
|
||||||
if (!isConfluenceEventMatch(triggerId, event || '')) {
|
|
||||||
logger.debug(
|
|
||||||
`[${options.requestId}] Confluence event mismatch for trigger ${triggerId}. Event: ${event}. Skipping execution.`,
|
|
||||||
{
|
|
||||||
webhookId: foundWebhook.id,
|
|
||||||
workflowId: foundWorkflow.id,
|
|
||||||
triggerId,
|
|
||||||
receivedEvent: event,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Return 200 OK to prevent Confluence from retrying
|
|
||||||
return NextResponse.json({
|
|
||||||
message: 'Event type does not match trigger configuration. Ignoring.',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundWebhook.provider === 'hubspot') {
|
if (foundWebhook.provider === 'hubspot') {
|
||||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||||
const triggerId = providerConfig.triggerId as string | undefined
|
const triggerId = providerConfig.triggerId as string | undefined
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ const PROVIDER_EXTRACTORS: Record<string, (body: any) => string | null> = {
|
|||||||
hubspot: extractHubSpotIdentifier,
|
hubspot: extractHubSpotIdentifier,
|
||||||
linear: extractLinearIdentifier,
|
linear: extractLinearIdentifier,
|
||||||
jira: extractJiraIdentifier,
|
jira: extractJiraIdentifier,
|
||||||
jira_service_management: extractJiraIdentifier,
|
|
||||||
'microsoft-teams': extractMicrosoftTeamsIdentifier,
|
'microsoft-teams': extractMicrosoftTeamsIdentifier,
|
||||||
airtable: extractAirtableIdentifier,
|
airtable: extractAirtableIdentifier,
|
||||||
grain: extractGrainIdentifier,
|
grain: extractGrainIdentifier,
|
||||||
|
|||||||
@@ -530,9 +530,6 @@ export async function validateTwilioSignature(
|
|||||||
const SLACK_MAX_FILE_SIZE = 50 * 1024 * 1024 // 50 MB
|
const SLACK_MAX_FILE_SIZE = 50 * 1024 * 1024 // 50 MB
|
||||||
const SLACK_MAX_FILES = 15
|
const SLACK_MAX_FILES = 15
|
||||||
|
|
||||||
const JIRA_MAX_FILE_SIZE = 50 * 1024 * 1024 // 50 MB
|
|
||||||
const CONFLUENCE_MAX_FILE_SIZE = 50 * 1024 * 1024 // 50 MB
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the full file object from the Slack API when the event payload
|
* Resolves the full file object from the Slack API when the event payload
|
||||||
* only contains a partial file (e.g. missing url_private due to file_access restrictions).
|
* only contains a partial file (e.g. missing url_private due to file_access restrictions).
|
||||||
@@ -682,169 +679,6 @@ async function downloadSlackFiles(
|
|||||||
return downloaded
|
return downloaded
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads a Jira attachment file using Basic auth (email + API token).
|
|
||||||
* Returns the file data in the format expected by WebhookAttachmentProcessor.
|
|
||||||
*/
|
|
||||||
async function downloadJiraAttachment(
|
|
||||||
attachment: { content?: string; filename?: string; mimeType?: string; size?: number },
|
|
||||||
apiEmail: string,
|
|
||||||
apiToken: string
|
|
||||||
): Promise<{ name: string; data: string; mimeType: string; size: number } | null> {
|
|
||||||
const contentUrl = attachment.content
|
|
||||||
if (!contentUrl) {
|
|
||||||
logger.warn('Jira attachment has no content URL, skipping download')
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const reportedSize = Number(attachment.size) || 0
|
|
||||||
if (reportedSize > JIRA_MAX_FILE_SIZE) {
|
|
||||||
logger.warn('Jira attachment exceeds size limit, skipping', {
|
|
||||||
filename: attachment.filename,
|
|
||||||
size: reportedSize,
|
|
||||||
limit: JIRA_MAX_FILE_SIZE,
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const urlValidation = await validateUrlWithDNS(contentUrl, 'attachment_content')
|
|
||||||
if (!urlValidation.isValid) {
|
|
||||||
logger.warn('Jira attachment URL failed DNS validation, skipping', {
|
|
||||||
filename: attachment.filename,
|
|
||||||
error: urlValidation.error,
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const authHeader = Buffer.from(`${apiEmail}:${apiToken}`).toString('base64')
|
|
||||||
|
|
||||||
const response = await secureFetchWithPinnedIP(contentUrl, urlValidation.resolvedIP!, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Basic ${authHeader}`,
|
|
||||||
Accept: '*/*',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
logger.warn('Failed to download Jira attachment', {
|
|
||||||
filename: attachment.filename,
|
|
||||||
status: response.status,
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const arrayBuffer = await response.arrayBuffer()
|
|
||||||
const buffer = Buffer.from(arrayBuffer)
|
|
||||||
|
|
||||||
if (buffer.length > JIRA_MAX_FILE_SIZE) {
|
|
||||||
logger.warn('Downloaded Jira attachment exceeds size limit, skipping', {
|
|
||||||
filename: attachment.filename,
|
|
||||||
actualSize: buffer.length,
|
|
||||||
limit: JIRA_MAX_FILE_SIZE,
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: attachment.filename || 'attachment',
|
|
||||||
data: buffer.toString('base64'),
|
|
||||||
mimeType: attachment.mimeType || 'application/octet-stream',
|
|
||||||
size: buffer.length,
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error downloading Jira attachment', {
|
|
||||||
filename: attachment.filename,
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads a Confluence attachment file using Atlassian Basic Auth.
|
|
||||||
* Constructs the download URL from the domain and attachment download path.
|
|
||||||
*/
|
|
||||||
async function downloadConfluenceAttachment(
|
|
||||||
attachment: Record<string, any>,
|
|
||||||
domain: string,
|
|
||||||
apiEmail: string,
|
|
||||||
apiToken: string
|
|
||||||
): Promise<{ name: string; data: string; mimeType: string; size: number } | null> {
|
|
||||||
// Confluence webhook payload includes _links.download for the attachment
|
|
||||||
const downloadPath = attachment?._links?.download || attachment?._expandable?.download || null
|
|
||||||
const attachmentId = attachment?.id
|
|
||||||
|
|
||||||
if (!downloadPath && !attachmentId) {
|
|
||||||
logger.warn('Confluence attachment has no download path or ID, skipping download')
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const reportedSize = Number(attachment?.extensions?.fileSize || attachment?.fileSize || 0)
|
|
||||||
if (reportedSize > CONFLUENCE_MAX_FILE_SIZE) {
|
|
||||||
logger.warn('Confluence attachment exceeds size limit, skipping', {
|
|
||||||
title: attachment?.title,
|
|
||||||
size: reportedSize,
|
|
||||||
limit: CONFLUENCE_MAX_FILE_SIZE,
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the download URL
|
|
||||||
const cleanDomain = domain.replace(/\/+$/, '')
|
|
||||||
const baseUrl = cleanDomain.startsWith('http') ? cleanDomain : `https://${cleanDomain}`
|
|
||||||
const downloadUrl = downloadPath
|
|
||||||
? `${baseUrl}/wiki${downloadPath}`
|
|
||||||
: `${baseUrl}/wiki/rest/api/content/${attachmentId}/download`
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader = Buffer.from(`${apiEmail}:${apiToken}`).toString('base64')
|
|
||||||
|
|
||||||
const response = await fetch(downloadUrl, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Basic ${authHeader}`,
|
|
||||||
Accept: '*/*',
|
|
||||||
'X-Atlassian-Token': 'no-check',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
logger.warn('Failed to download Confluence attachment', {
|
|
||||||
title: attachment?.title,
|
|
||||||
status: response.status,
|
|
||||||
url: sanitizeUrlForLog(downloadUrl),
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const arrayBuffer = await response.arrayBuffer()
|
|
||||||
const buffer = Buffer.from(arrayBuffer)
|
|
||||||
|
|
||||||
if (buffer.length > CONFLUENCE_MAX_FILE_SIZE) {
|
|
||||||
logger.warn('Downloaded Confluence attachment exceeds size limit, skipping', {
|
|
||||||
title: attachment?.title,
|
|
||||||
actualSize: buffer.length,
|
|
||||||
limit: CONFLUENCE_MAX_FILE_SIZE,
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: attachment?.title || 'attachment',
|
|
||||||
data: buffer.toString('base64'),
|
|
||||||
mimeType:
|
|
||||||
attachment?.extensions?.mediaType || attachment?.mediaType || 'application/octet-stream',
|
|
||||||
size: buffer.length,
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error downloading Confluence attachment', {
|
|
||||||
title: attachment?.title,
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format webhook input based on provider
|
* Format webhook input based on provider
|
||||||
*/
|
*/
|
||||||
@@ -1269,156 +1103,22 @@ export async function formatWebhookInput(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (foundWebhook.provider === 'jira') {
|
if (foundWebhook.provider === 'jira') {
|
||||||
const {
|
const { extractIssueData, extractCommentData, extractWorklogData } = await import(
|
||||||
extractIssueData,
|
'@/triggers/jira/utils'
|
||||||
extractCommentData,
|
)
|
||||||
extractWorklogData,
|
|
||||||
extractAttachmentData,
|
|
||||||
extractSprintData,
|
|
||||||
extractProjectData,
|
|
||||||
extractVersionData,
|
|
||||||
extractBoardData,
|
|
||||||
extractIssueLinkData,
|
|
||||||
} = await import('@/triggers/jira/utils')
|
|
||||||
|
|
||||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||||
const triggerId = providerConfig.triggerId as string | undefined
|
const triggerId = providerConfig.triggerId as string | undefined
|
||||||
|
|
||||||
if (
|
if (triggerId === 'jira_issue_commented') {
|
||||||
triggerId === 'jira_issue_commented' ||
|
|
||||||
triggerId === 'jira_comment_updated' ||
|
|
||||||
triggerId === 'jira_comment_deleted'
|
|
||||||
) {
|
|
||||||
return extractCommentData(body)
|
return extractCommentData(body)
|
||||||
}
|
}
|
||||||
if (
|
if (triggerId === 'jira_worklog_created') {
|
||||||
triggerId === 'jira_worklog_created' ||
|
|
||||||
triggerId === 'jira_worklog_updated' ||
|
|
||||||
triggerId === 'jira_worklog_deleted'
|
|
||||||
) {
|
|
||||||
return extractWorklogData(body)
|
return extractWorklogData(body)
|
||||||
}
|
}
|
||||||
if (triggerId === 'jira_attachment_created' || triggerId === 'jira_attachment_deleted') {
|
|
||||||
const result = extractAttachmentData(body)
|
|
||||||
|
|
||||||
// Download the attachment file if configured
|
|
||||||
if (triggerId === 'jira_attachment_created') {
|
|
||||||
const apiEmail = providerConfig.apiEmail as string | undefined
|
|
||||||
const apiToken = providerConfig.apiToken as string | undefined
|
|
||||||
const includeAttachments = Boolean(providerConfig.includeAttachments)
|
|
||||||
|
|
||||||
if (includeAttachments && apiEmail && apiToken && result.attachment?.content) {
|
|
||||||
const downloaded = await downloadJiraAttachment(result.attachment, apiEmail, apiToken)
|
|
||||||
if (downloaded) {
|
|
||||||
result.attachments = [downloaded]
|
|
||||||
}
|
|
||||||
} else if (includeAttachments && (!apiEmail || !apiToken)) {
|
|
||||||
logger.warn(
|
|
||||||
'Jira attachment trigger has includeAttachments enabled but missing API credentials'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
if (triggerId?.startsWith('jira_sprint_')) {
|
|
||||||
return extractSprintData(body)
|
|
||||||
}
|
|
||||||
if (triggerId?.startsWith('jira_project_')) {
|
|
||||||
return extractProjectData(body)
|
|
||||||
}
|
|
||||||
if (triggerId?.startsWith('jira_version_')) {
|
|
||||||
return extractVersionData(body)
|
|
||||||
}
|
|
||||||
if (triggerId?.startsWith('jira_board_')) {
|
|
||||||
return extractBoardData(body)
|
|
||||||
}
|
|
||||||
if (triggerId?.startsWith('jira_issuelink_')) {
|
|
||||||
return extractIssueLinkData(body)
|
|
||||||
}
|
|
||||||
return extractIssueData(body)
|
return extractIssueData(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundWebhook.provider === 'jira_service_management') {
|
|
||||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
|
||||||
const triggerId = providerConfig.triggerId as string | undefined
|
|
||||||
const includeFiles = Boolean(providerConfig.includeFiles)
|
|
||||||
const jiraEmail = providerConfig.jiraEmail as string | undefined
|
|
||||||
const jiraApiToken = providerConfig.jiraApiToken as string | undefined
|
|
||||||
|
|
||||||
const webhookEvent = body.webhookEvent || ''
|
|
||||||
|
|
||||||
// Base data common to all JSM events
|
|
||||||
const baseData: Record<string, any> = {
|
|
||||||
webhookEvent,
|
|
||||||
timestamp: body.timestamp,
|
|
||||||
issue: body.issue || {},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle attachment events
|
|
||||||
if (
|
|
||||||
triggerId === 'jsm_attachment_created' ||
|
|
||||||
triggerId === 'jsm_attachment_deleted' ||
|
|
||||||
webhookEvent.includes('attachment')
|
|
||||||
) {
|
|
||||||
const attachment = body.attachment || {}
|
|
||||||
baseData.attachment = attachment
|
|
||||||
|
|
||||||
let files: Array<{ name: string; data: string; mimeType: string; size: number }> = []
|
|
||||||
|
|
||||||
if (
|
|
||||||
webhookEvent.includes('attachment_created') &&
|
|
||||||
includeFiles &&
|
|
||||||
jiraEmail &&
|
|
||||||
jiraApiToken &&
|
|
||||||
attachment.content
|
|
||||||
) {
|
|
||||||
const downloaded = await downloadJiraAttachment(attachment, jiraEmail, jiraApiToken)
|
|
||||||
if (downloaded) {
|
|
||||||
files = [downloaded]
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
webhookEvent.includes('attachment_created') &&
|
|
||||||
includeFiles &&
|
|
||||||
(!jiraEmail || !jiraApiToken)
|
|
||||||
) {
|
|
||||||
logger.warn(
|
|
||||||
'JSM attachment trigger has includeFiles enabled but missing Jira API credentials'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
baseData.files = files
|
|
||||||
return baseData
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle comment events
|
|
||||||
if (
|
|
||||||
triggerId === 'jsm_request_commented' ||
|
|
||||||
triggerId === 'jsm_comment_updated' ||
|
|
||||||
triggerId === 'jsm_comment_deleted' ||
|
|
||||||
webhookEvent.includes('comment')
|
|
||||||
) {
|
|
||||||
baseData.comment = body.comment || {}
|
|
||||||
return baseData
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle worklog events
|
|
||||||
if (
|
|
||||||
triggerId === 'jsm_worklog_created' ||
|
|
||||||
triggerId === 'jsm_worklog_updated' ||
|
|
||||||
triggerId === 'jsm_worklog_deleted' ||
|
|
||||||
webhookEvent.includes('worklog')
|
|
||||||
) {
|
|
||||||
baseData.worklog = body.worklog || {}
|
|
||||||
return baseData
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default: request events (created/updated/deleted) and generic webhook
|
|
||||||
baseData.issue_event_type_name = body.issue_event_type_name
|
|
||||||
baseData.changelog = body.changelog
|
|
||||||
return baseData
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundWebhook.provider === 'stripe') {
|
if (foundWebhook.provider === 'stripe') {
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
@@ -1467,70 +1167,6 @@ export async function formatWebhookInput(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundWebhook.provider === 'confluence') {
|
|
||||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
|
||||||
const event = body.event as string | undefined
|
|
||||||
const result: Record<string, unknown> = {
|
|
||||||
event: event || '',
|
|
||||||
timestamp: body.timestamp,
|
|
||||||
userAccountId: body.userAccountId || '',
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body.page) {
|
|
||||||
result.page = body.page
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body.comment) {
|
|
||||||
result.comment = body.comment
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body.blog || body.blogpost) {
|
|
||||||
result.blog = body.blog || body.blogpost
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body.attachment) {
|
|
||||||
result.attachment = body.attachment
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body.space) {
|
|
||||||
result.space = body.space
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body.label) {
|
|
||||||
result.label = body.label
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body.content) {
|
|
||||||
result.content = body.content
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download attachment file content when configured
|
|
||||||
const includeFileContent = Boolean(providerConfig.includeFileContent)
|
|
||||||
const confluenceEmail = providerConfig.confluenceEmail as string | undefined
|
|
||||||
const confluenceApiToken = providerConfig.confluenceApiToken as string | undefined
|
|
||||||
const confluenceDomain = providerConfig.confluenceDomain as string | undefined
|
|
||||||
|
|
||||||
if (body.attachment && includeFileContent) {
|
|
||||||
if (confluenceEmail && confluenceApiToken && confluenceDomain) {
|
|
||||||
const downloaded = await downloadConfluenceAttachment(
|
|
||||||
body.attachment,
|
|
||||||
confluenceDomain,
|
|
||||||
confluenceEmail,
|
|
||||||
confluenceApiToken
|
|
||||||
)
|
|
||||||
if (downloaded) {
|
|
||||||
result.files = [downloaded]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.warn(
|
|
||||||
'Confluence attachment trigger has includeFileContent enabled but missing credentials (email, API token, or domain)'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2364,261 +2364,6 @@ describe('hasWorkflowChanged', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Trigger Config Normalization (False Positive Prevention)', () => {
|
|
||||||
it.concurrent(
|
|
||||||
'should not detect change when deployed has null fields but current has values from triggerConfig',
|
|
||||||
() => {
|
|
||||||
// Core scenario: deployed state has null individual fields, current state has
|
|
||||||
// values populated from triggerConfig at runtime by populateTriggerFieldsFromConfig
|
|
||||||
const deployedState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: null },
|
|
||||||
botToken: { id: 'botToken', type: 'short-input', value: null },
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'secret123', botToken: 'token456' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: 'secret123' },
|
|
||||||
botToken: { id: 'botToken', type: 'short-input', value: 'token456' },
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'secret123', botToken: 'token456' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(hasWorkflowChanged(currentState, deployedState)).toBe(false)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
it.concurrent(
|
|
||||||
'should detect change when user edits a trigger field to a different value',
|
|
||||||
() => {
|
|
||||||
const deployedState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: null },
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'old-secret' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: 'new-secret' },
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'old-secret' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(hasWorkflowChanged(currentState, deployedState)).toBe(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
it.concurrent('should not detect change when both sides have no triggerConfig', () => {
|
|
||||||
const deployedState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: null },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: null },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(hasWorkflowChanged(currentState, deployedState)).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent(
|
|
||||||
'should not detect change when deployed has empty fields and triggerConfig populates them',
|
|
||||||
() => {
|
|
||||||
// Empty string is also treated as "empty" by normalizeTriggerConfigValues
|
|
||||||
const deployedState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: '' },
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'secret123' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: 'secret123' },
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'secret123' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(hasWorkflowChanged(currentState, deployedState)).toBe(false)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
it.concurrent('should not detect change when triggerId differs', () => {
|
|
||||||
const deployedState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
model: { value: 'gpt-4' },
|
|
||||||
triggerId: { value: null },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
model: { value: 'gpt-4' },
|
|
||||||
triggerId: { value: 'slack_webhook' },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(hasWorkflowChanged(currentState, deployedState)).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent(
|
|
||||||
'should not detect change for namespaced system subBlock IDs like samplePayload_slack_webhook',
|
|
||||||
() => {
|
|
||||||
const deployedState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
model: { value: 'gpt-4' },
|
|
||||||
samplePayload_slack_webhook: { value: 'old payload' },
|
|
||||||
triggerInstructions_slack_webhook: { value: 'old instructions' },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
model: { value: 'gpt-4' },
|
|
||||||
samplePayload_slack_webhook: { value: 'new payload' },
|
|
||||||
triggerInstructions_slack_webhook: { value: 'new instructions' },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(hasWorkflowChanged(currentState, deployedState)).toBe(false)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
it.concurrent(
|
|
||||||
'should handle mixed scenario: some fields from triggerConfig, some user-edited',
|
|
||||||
() => {
|
|
||||||
const deployedState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: null },
|
|
||||||
botToken: { id: 'botToken', type: 'short-input', value: null },
|
|
||||||
includeFiles: { id: 'includeFiles', type: 'switch', value: false },
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'secret123', botToken: 'token456' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentState = createWorkflowState({
|
|
||||||
blocks: {
|
|
||||||
block1: createBlock('block1', {
|
|
||||||
type: 'starter',
|
|
||||||
subBlocks: {
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: 'secret123' },
|
|
||||||
botToken: { id: 'botToken', type: 'short-input', value: 'token456' },
|
|
||||||
includeFiles: { id: 'includeFiles', type: 'switch', value: true },
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'secret123', botToken: 'token456' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// includeFiles changed from false to true — this IS a real change
|
|
||||||
expect(hasWorkflowChanged(currentState, deployedState)).toBe(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Trigger Runtime Metadata (Should Not Trigger Change)', () => {
|
describe('Trigger Runtime Metadata (Should Not Trigger Change)', () => {
|
||||||
it.concurrent('should not detect change when webhookId differs', () => {
|
it.concurrent('should not detect change when webhookId differs', () => {
|
||||||
const deployedState = createWorkflowState({
|
const deployedState = createWorkflowState({
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
normalizeLoop,
|
normalizeLoop,
|
||||||
normalizeParallel,
|
normalizeParallel,
|
||||||
normalizeSubBlockValue,
|
normalizeSubBlockValue,
|
||||||
normalizeTriggerConfigValues,
|
|
||||||
normalizeValue,
|
normalizeValue,
|
||||||
normalizeVariables,
|
normalizeVariables,
|
||||||
sanitizeVariable,
|
sanitizeVariable,
|
||||||
@@ -173,18 +172,14 @@ export function generateWorkflowDiffSummary(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize trigger config values for both states before comparison
|
|
||||||
const normalizedCurrentSubs = normalizeTriggerConfigValues(currentSubBlocks)
|
|
||||||
const normalizedPreviousSubs = normalizeTriggerConfigValues(previousSubBlocks)
|
|
||||||
|
|
||||||
// Compare subBlocks using shared helper for filtering (single source of truth)
|
// Compare subBlocks using shared helper for filtering (single source of truth)
|
||||||
const allSubBlockIds = filterSubBlockIds([
|
const allSubBlockIds = filterSubBlockIds([
|
||||||
...new Set([...Object.keys(normalizedCurrentSubs), ...Object.keys(normalizedPreviousSubs)]),
|
...new Set([...Object.keys(currentSubBlocks), ...Object.keys(previousSubBlocks)]),
|
||||||
])
|
])
|
||||||
|
|
||||||
for (const subId of allSubBlockIds) {
|
for (const subId of allSubBlockIds) {
|
||||||
const currentSub = normalizedCurrentSubs[subId] as Record<string, unknown> | undefined
|
const currentSub = currentSubBlocks[subId] as Record<string, unknown> | undefined
|
||||||
const previousSub = normalizedPreviousSubs[subId] as Record<string, unknown> | undefined
|
const previousSub = previousSubBlocks[subId] as Record<string, unknown> | undefined
|
||||||
|
|
||||||
if (!currentSub || !previousSub) {
|
if (!currentSub || !previousSub) {
|
||||||
changes.push({
|
changes.push({
|
||||||
|
|||||||
@@ -4,12 +4,10 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import type { Loop, Parallel } from '@/stores/workflows/workflow/types'
|
import type { Loop, Parallel } from '@/stores/workflows/workflow/types'
|
||||||
import {
|
import {
|
||||||
filterSubBlockIds,
|
|
||||||
normalizedStringify,
|
normalizedStringify,
|
||||||
normalizeEdge,
|
normalizeEdge,
|
||||||
normalizeLoop,
|
normalizeLoop,
|
||||||
normalizeParallel,
|
normalizeParallel,
|
||||||
normalizeTriggerConfigValues,
|
|
||||||
normalizeValue,
|
normalizeValue,
|
||||||
sanitizeInputFormat,
|
sanitizeInputFormat,
|
||||||
sanitizeTools,
|
sanitizeTools,
|
||||||
@@ -586,214 +584,4 @@ describe('Workflow Normalization Utilities', () => {
|
|||||||
expect(result2).toBe(result3)
|
expect(result2).toBe(result3)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('filterSubBlockIds', () => {
|
|
||||||
it.concurrent('should exclude exact SYSTEM_SUBBLOCK_IDS', () => {
|
|
||||||
const ids = ['signingSecret', 'samplePayload', 'triggerInstructions', 'botToken']
|
|
||||||
const result = filterSubBlockIds(ids)
|
|
||||||
expect(result).toEqual(['botToken', 'signingSecret'])
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should exclude namespaced SYSTEM_SUBBLOCK_IDS (prefix matching)', () => {
|
|
||||||
const ids = [
|
|
||||||
'signingSecret',
|
|
||||||
'samplePayload_slack_webhook',
|
|
||||||
'triggerInstructions_slack_webhook',
|
|
||||||
'webhookUrlDisplay_slack_webhook',
|
|
||||||
'botToken',
|
|
||||||
]
|
|
||||||
const result = filterSubBlockIds(ids)
|
|
||||||
expect(result).toEqual(['botToken', 'signingSecret'])
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should exclude exact TRIGGER_RUNTIME_SUBBLOCK_IDS', () => {
|
|
||||||
const ids = ['webhookId', 'triggerPath', 'triggerConfig', 'triggerId', 'signingSecret']
|
|
||||||
const result = filterSubBlockIds(ids)
|
|
||||||
expect(result).toEqual(['signingSecret'])
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should not exclude IDs that merely contain a system ID substring', () => {
|
|
||||||
const ids = ['mySamplePayload', 'notSamplePayload']
|
|
||||||
const result = filterSubBlockIds(ids)
|
|
||||||
expect(result).toEqual(['mySamplePayload', 'notSamplePayload'])
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should return sorted results', () => {
|
|
||||||
const ids = ['zebra', 'alpha', 'middle']
|
|
||||||
const result = filterSubBlockIds(ids)
|
|
||||||
expect(result).toEqual(['alpha', 'middle', 'zebra'])
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should handle empty array', () => {
|
|
||||||
expect(filterSubBlockIds([])).toEqual([])
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should handle all IDs being excluded', () => {
|
|
||||||
const ids = ['webhookId', 'triggerPath', 'samplePayload', 'triggerConfig']
|
|
||||||
const result = filterSubBlockIds(ids)
|
|
||||||
expect(result).toEqual([])
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should exclude setupScript and scheduleInfo namespaced variants', () => {
|
|
||||||
const ids = ['setupScript_google_sheets_row', 'scheduleInfo_cron_trigger', 'realField']
|
|
||||||
const result = filterSubBlockIds(ids)
|
|
||||||
expect(result).toEqual(['realField'])
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should exclude triggerCredentials namespaced variants', () => {
|
|
||||||
const ids = ['triggerCredentials_slack_webhook', 'signingSecret']
|
|
||||||
const result = filterSubBlockIds(ids)
|
|
||||||
expect(result).toEqual(['signingSecret'])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('normalizeTriggerConfigValues', () => {
|
|
||||||
it.concurrent('should return subBlocks unchanged when no triggerConfig exists', () => {
|
|
||||||
const subBlocks = {
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: 'secret123' },
|
|
||||||
botToken: { id: 'botToken', type: 'short-input', value: 'token456' },
|
|
||||||
}
|
|
||||||
const result = normalizeTriggerConfigValues(subBlocks)
|
|
||||||
expect(result).toEqual(subBlocks)
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should return subBlocks unchanged when triggerConfig value is null', () => {
|
|
||||||
const subBlocks = {
|
|
||||||
triggerConfig: { id: 'triggerConfig', type: 'short-input', value: null },
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: null },
|
|
||||||
}
|
|
||||||
const result = normalizeTriggerConfigValues(subBlocks)
|
|
||||||
expect(result).toEqual(subBlocks)
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent(
|
|
||||||
'should return subBlocks unchanged when triggerConfig value is not an object',
|
|
||||||
() => {
|
|
||||||
const subBlocks = {
|
|
||||||
triggerConfig: { id: 'triggerConfig', type: 'short-input', value: 'string-value' },
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: null },
|
|
||||||
}
|
|
||||||
const result = normalizeTriggerConfigValues(subBlocks)
|
|
||||||
expect(result).toEqual(subBlocks)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
it.concurrent('should populate null individual fields from triggerConfig', () => {
|
|
||||||
const subBlocks = {
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'secret123', botToken: 'token456' },
|
|
||||||
},
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: null },
|
|
||||||
botToken: { id: 'botToken', type: 'short-input', value: null },
|
|
||||||
}
|
|
||||||
const result = normalizeTriggerConfigValues(subBlocks)
|
|
||||||
expect((result.signingSecret as Record<string, unknown>).value).toBe('secret123')
|
|
||||||
expect((result.botToken as Record<string, unknown>).value).toBe('token456')
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should populate undefined individual fields from triggerConfig', () => {
|
|
||||||
const subBlocks = {
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'secret123' },
|
|
||||||
},
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: undefined },
|
|
||||||
}
|
|
||||||
const result = normalizeTriggerConfigValues(subBlocks)
|
|
||||||
expect((result.signingSecret as Record<string, unknown>).value).toBe('secret123')
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should populate empty string individual fields from triggerConfig', () => {
|
|
||||||
const subBlocks = {
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'secret123' },
|
|
||||||
},
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: '' },
|
|
||||||
}
|
|
||||||
const result = normalizeTriggerConfigValues(subBlocks)
|
|
||||||
expect((result.signingSecret as Record<string, unknown>).value).toBe('secret123')
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should NOT overwrite existing non-empty individual field values', () => {
|
|
||||||
const subBlocks = {
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'old-secret' },
|
|
||||||
},
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: 'user-edited-secret' },
|
|
||||||
}
|
|
||||||
const result = normalizeTriggerConfigValues(subBlocks)
|
|
||||||
expect((result.signingSecret as Record<string, unknown>).value).toBe('user-edited-secret')
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should skip triggerConfig fields that are null/undefined', () => {
|
|
||||||
const subBlocks = {
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: null, botToken: undefined },
|
|
||||||
},
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: null },
|
|
||||||
botToken: { id: 'botToken', type: 'short-input', value: null },
|
|
||||||
}
|
|
||||||
const result = normalizeTriggerConfigValues(subBlocks)
|
|
||||||
expect((result.signingSecret as Record<string, unknown>).value).toBe(null)
|
|
||||||
expect((result.botToken as Record<string, unknown>).value).toBe(null)
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should skip fields from triggerConfig that have no matching subBlock', () => {
|
|
||||||
const subBlocks = {
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { nonExistentField: 'value123' },
|
|
||||||
},
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: null },
|
|
||||||
}
|
|
||||||
const result = normalizeTriggerConfigValues(subBlocks)
|
|
||||||
expect(result.nonExistentField).toBeUndefined()
|
|
||||||
expect((result.signingSecret as Record<string, unknown>).value).toBe(null)
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should not mutate the original subBlocks object', () => {
|
|
||||||
const original = {
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'secret123' },
|
|
||||||
},
|
|
||||||
signingSecret: { id: 'signingSecret', type: 'short-input', value: null },
|
|
||||||
}
|
|
||||||
normalizeTriggerConfigValues(original)
|
|
||||||
expect((original.signingSecret as Record<string, unknown>).value).toBe(null)
|
|
||||||
})
|
|
||||||
|
|
||||||
it.concurrent('should preserve other subBlock properties when populating value', () => {
|
|
||||||
const subBlocks = {
|
|
||||||
triggerConfig: {
|
|
||||||
id: 'triggerConfig',
|
|
||||||
type: 'short-input',
|
|
||||||
value: { signingSecret: 'secret123' },
|
|
||||||
},
|
|
||||||
signingSecret: {
|
|
||||||
id: 'signingSecret',
|
|
||||||
type: 'short-input',
|
|
||||||
value: null,
|
|
||||||
placeholder: 'Enter signing secret',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
const result = normalizeTriggerConfigValues(subBlocks)
|
|
||||||
const normalized = result.signingSecret as Record<string, unknown>
|
|
||||||
expect(normalized.value).toBe('secret123')
|
|
||||||
expect(normalized.id).toBe('signingSecret')
|
|
||||||
expect(normalized.type).toBe('short-input')
|
|
||||||
expect(normalized.placeholder).toBe('Enter signing secret')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -418,48 +418,10 @@ export function extractBlockFieldsForComparison(block: BlockState): ExtractedBlo
|
|||||||
*/
|
*/
|
||||||
export function filterSubBlockIds(subBlockIds: string[]): string[] {
|
export function filterSubBlockIds(subBlockIds: string[]): string[] {
|
||||||
return subBlockIds
|
return subBlockIds
|
||||||
.filter((id) => {
|
.filter((id) => !SYSTEM_SUBBLOCK_IDS.includes(id) && !TRIGGER_RUNTIME_SUBBLOCK_IDS.includes(id))
|
||||||
if (TRIGGER_RUNTIME_SUBBLOCK_IDS.includes(id)) return false
|
|
||||||
if (SYSTEM_SUBBLOCK_IDS.some((sysId) => id === sysId || id.startsWith(`${sysId}_`)))
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
.sort()
|
.sort()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalizes trigger block subBlocks by populating null/empty individual fields
|
|
||||||
* from the triggerConfig aggregate subBlock. This compensates for the runtime
|
|
||||||
* population done by populateTriggerFieldsFromConfig, ensuring consistent
|
|
||||||
* comparison between client state (with populated values) and deployed state
|
|
||||||
* (with null values from DB).
|
|
||||||
*/
|
|
||||||
export function normalizeTriggerConfigValues(
|
|
||||||
subBlocks: Record<string, unknown>
|
|
||||||
): Record<string, unknown> {
|
|
||||||
const triggerConfigSub = subBlocks.triggerConfig as Record<string, unknown> | undefined
|
|
||||||
const triggerConfigValue = triggerConfigSub?.value
|
|
||||||
if (!triggerConfigValue || typeof triggerConfigValue !== 'object') {
|
|
||||||
return subBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = { ...subBlocks }
|
|
||||||
for (const [fieldId, configValue] of Object.entries(
|
|
||||||
triggerConfigValue as Record<string, unknown>
|
|
||||||
)) {
|
|
||||||
if (configValue === null || configValue === undefined) continue
|
|
||||||
const existingSub = result[fieldId] as Record<string, unknown> | undefined
|
|
||||||
if (
|
|
||||||
existingSub &&
|
|
||||||
(existingSub.value === null || existingSub.value === undefined || existingSub.value === '')
|
|
||||||
) {
|
|
||||||
result[fieldId] = { ...existingSub, value: configValue }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes a subBlock value with sanitization for specific subBlock types.
|
* Normalizes a subBlock value with sanitization for specific subBlock types.
|
||||||
* Sanitizes: tools (removes isExpanded), inputFormat (removes collapsed)
|
* Sanitizes: tools (removes isExpanded), inputFormat (removes collapsed)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
type GenerateContentConfig,
|
type GenerateContentConfig,
|
||||||
type GenerateContentResponse,
|
type GenerateContentResponse,
|
||||||
type GoogleGenAI,
|
type GoogleGenAI,
|
||||||
type Interactions,
|
|
||||||
type Part,
|
type Part,
|
||||||
type Schema,
|
type Schema,
|
||||||
type ThinkingConfig,
|
type ThinkingConfig,
|
||||||
@@ -28,7 +27,6 @@ import {
|
|||||||
import type { FunctionCallResponse, ProviderRequest, ProviderResponse } from '@/providers/types'
|
import type { FunctionCallResponse, ProviderRequest, ProviderResponse } from '@/providers/types'
|
||||||
import {
|
import {
|
||||||
calculateCost,
|
calculateCost,
|
||||||
isDeepResearchModel,
|
|
||||||
prepareToolExecution,
|
prepareToolExecution,
|
||||||
prepareToolsWithUsageControl,
|
prepareToolsWithUsageControl,
|
||||||
} from '@/providers/utils'
|
} from '@/providers/utils'
|
||||||
@@ -383,468 +381,6 @@ export interface GeminiExecutionConfig {
|
|||||||
providerType: GeminiProviderType
|
providerType: GeminiProviderType
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEEP_RESEARCH_POLL_INTERVAL_MS = 10_000
|
|
||||||
const DEEP_RESEARCH_MAX_DURATION_MS = 60 * 60 * 1000
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sleeps for the specified number of milliseconds
|
|
||||||
*/
|
|
||||||
function sleep(ms: number): Promise<void> {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collapses a ProviderRequest into a single input string and optional system instruction
|
|
||||||
* for the Interactions API, which takes a flat input rather than a messages array.
|
|
||||||
*
|
|
||||||
* Deep research is single-turn only — it takes one research query and returns a report.
|
|
||||||
* Memory/conversation history is hidden in the UI for deep research models, so only
|
|
||||||
* the last user message is used as input. System messages are passed via system_instruction.
|
|
||||||
*/
|
|
||||||
function collapseMessagesToInput(request: ProviderRequest): {
|
|
||||||
input: string
|
|
||||||
systemInstruction: string | undefined
|
|
||||||
} {
|
|
||||||
const systemParts: string[] = []
|
|
||||||
const userParts: string[] = []
|
|
||||||
|
|
||||||
if (request.systemPrompt) {
|
|
||||||
systemParts.push(request.systemPrompt)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.messages) {
|
|
||||||
for (const msg of request.messages) {
|
|
||||||
if (msg.role === 'system' && msg.content) {
|
|
||||||
systemParts.push(msg.content)
|
|
||||||
} else if (msg.role === 'user' && msg.content) {
|
|
||||||
userParts.push(msg.content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
input:
|
|
||||||
userParts.length > 0
|
|
||||||
? userParts[userParts.length - 1]
|
|
||||||
: 'Please conduct research on the provided topic.',
|
|
||||||
systemInstruction: systemParts.length > 0 ? systemParts.join('\n\n') : undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts text content from a completed interaction's outputs array.
|
|
||||||
* The outputs array can contain text, thought, google_search_result, and other types.
|
|
||||||
* We concatenate all text outputs to get the full research report.
|
|
||||||
*/
|
|
||||||
function extractTextFromInteractionOutputs(outputs: Interactions.Interaction['outputs']): string {
|
|
||||||
if (!outputs || outputs.length === 0) return ''
|
|
||||||
|
|
||||||
const textParts: string[] = []
|
|
||||||
for (const output of outputs) {
|
|
||||||
if (output.type === 'text') {
|
|
||||||
const text = (output as Interactions.TextContent).text
|
|
||||||
if (text) textParts.push(text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return textParts.join('\n\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts token usage from an Interaction's Usage object.
|
|
||||||
* The Interactions API provides total_input_tokens, total_output_tokens, total_tokens,
|
|
||||||
* and total_reasoning_tokens (for thinking models).
|
|
||||||
*
|
|
||||||
* Also handles the raw API field name total_thought_tokens which the SDK may
|
|
||||||
* map to total_reasoning_tokens.
|
|
||||||
*/
|
|
||||||
function extractInteractionUsage(usage: Interactions.Usage | undefined): {
|
|
||||||
inputTokens: number
|
|
||||||
outputTokens: number
|
|
||||||
reasoningTokens: number
|
|
||||||
totalTokens: number
|
|
||||||
} {
|
|
||||||
if (!usage) {
|
|
||||||
return { inputTokens: 0, outputTokens: 0, reasoningTokens: 0, totalTokens: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
const usageLogger = createLogger('DeepResearchUsage')
|
|
||||||
usageLogger.info('Raw interaction usage', { usage: JSON.stringify(usage) })
|
|
||||||
|
|
||||||
const inputTokens = usage.total_input_tokens ?? 0
|
|
||||||
const outputTokens = usage.total_output_tokens ?? 0
|
|
||||||
const reasoningTokens =
|
|
||||||
usage.total_reasoning_tokens ??
|
|
||||||
((usage as Record<string, unknown>).total_thought_tokens as number) ??
|
|
||||||
0
|
|
||||||
const totalTokens = usage.total_tokens ?? inputTokens + outputTokens
|
|
||||||
|
|
||||||
return { inputTokens, outputTokens, reasoningTokens, totalTokens }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a standard ProviderResponse from a completed deep research interaction.
|
|
||||||
*/
|
|
||||||
function buildDeepResearchResponse(
|
|
||||||
content: string,
|
|
||||||
model: string,
|
|
||||||
usage: {
|
|
||||||
inputTokens: number
|
|
||||||
outputTokens: number
|
|
||||||
reasoningTokens: number
|
|
||||||
totalTokens: number
|
|
||||||
},
|
|
||||||
providerStartTime: number,
|
|
||||||
providerStartTimeISO: string,
|
|
||||||
interactionId?: string
|
|
||||||
): ProviderResponse {
|
|
||||||
const providerEndTime = Date.now()
|
|
||||||
const duration = providerEndTime - providerStartTime
|
|
||||||
|
|
||||||
return {
|
|
||||||
content,
|
|
||||||
model,
|
|
||||||
tokens: {
|
|
||||||
input: usage.inputTokens,
|
|
||||||
output: usage.outputTokens,
|
|
||||||
total: usage.totalTokens,
|
|
||||||
},
|
|
||||||
timing: {
|
|
||||||
startTime: providerStartTimeISO,
|
|
||||||
endTime: new Date(providerEndTime).toISOString(),
|
|
||||||
duration,
|
|
||||||
modelTime: duration,
|
|
||||||
toolsTime: 0,
|
|
||||||
firstResponseTime: duration,
|
|
||||||
iterations: 1,
|
|
||||||
timeSegments: [
|
|
||||||
{
|
|
||||||
type: 'model',
|
|
||||||
name: 'Deep research',
|
|
||||||
startTime: providerStartTime,
|
|
||||||
endTime: providerEndTime,
|
|
||||||
duration,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
cost: calculateCost(model, usage.inputTokens, usage.outputTokens),
|
|
||||||
interactionId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a ReadableStream from a deep research streaming interaction.
|
|
||||||
*
|
|
||||||
* Deep research streaming returns InteractionSSEEvent chunks including:
|
|
||||||
* - interaction.start: initial interaction with ID
|
|
||||||
* - content.delta: incremental text and thought_summary updates
|
|
||||||
* - content.start / content.stop: output boundaries
|
|
||||||
* - interaction.complete: final event (outputs is undefined in streaming; must reconstruct)
|
|
||||||
* - error: error events
|
|
||||||
*
|
|
||||||
* We stream text deltas to the client and track usage from the interaction.complete event.
|
|
||||||
*/
|
|
||||||
function createDeepResearchStream(
|
|
||||||
stream: AsyncIterable<Interactions.InteractionSSEEvent>,
|
|
||||||
onComplete?: (
|
|
||||||
content: string,
|
|
||||||
usage: {
|
|
||||||
inputTokens: number
|
|
||||||
outputTokens: number
|
|
||||||
reasoningTokens: number
|
|
||||||
totalTokens: number
|
|
||||||
},
|
|
||||||
interactionId?: string
|
|
||||||
) => void
|
|
||||||
): ReadableStream<Uint8Array> {
|
|
||||||
const streamLogger = createLogger('DeepResearchStream')
|
|
||||||
let fullContent = ''
|
|
||||||
let completionUsage = { inputTokens: 0, outputTokens: 0, reasoningTokens: 0, totalTokens: 0 }
|
|
||||||
let completedInteractionId: string | undefined
|
|
||||||
|
|
||||||
return new ReadableStream({
|
|
||||||
async start(controller) {
|
|
||||||
try {
|
|
||||||
for await (const event of stream) {
|
|
||||||
if (event.event_type === 'content.delta') {
|
|
||||||
const delta = (event as Interactions.ContentDelta).delta
|
|
||||||
if (delta?.type === 'text' && 'text' in delta && delta.text) {
|
|
||||||
fullContent += delta.text
|
|
||||||
controller.enqueue(new TextEncoder().encode(delta.text))
|
|
||||||
}
|
|
||||||
} else if (event.event_type === 'interaction.complete') {
|
|
||||||
const interaction = (event as Interactions.InteractionEvent).interaction
|
|
||||||
if (interaction?.usage) {
|
|
||||||
completionUsage = extractInteractionUsage(interaction.usage)
|
|
||||||
}
|
|
||||||
completedInteractionId = interaction?.id
|
|
||||||
} else if (event.event_type === 'interaction.start') {
|
|
||||||
const interaction = (event as Interactions.InteractionEvent).interaction
|
|
||||||
if (interaction?.id) {
|
|
||||||
completedInteractionId = interaction.id
|
|
||||||
}
|
|
||||||
} else if (event.event_type === 'error') {
|
|
||||||
const errorEvent = event as { error?: { code?: string; message?: string } }
|
|
||||||
const message = errorEvent.error?.message ?? 'Unknown deep research stream error'
|
|
||||||
streamLogger.error('Deep research stream error', {
|
|
||||||
code: errorEvent.error?.code,
|
|
||||||
message,
|
|
||||||
})
|
|
||||||
controller.error(new Error(message))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onComplete?.(fullContent, completionUsage, completedInteractionId)
|
|
||||||
controller.close()
|
|
||||||
} catch (error) {
|
|
||||||
streamLogger.error('Error reading deep research stream', {
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
})
|
|
||||||
controller.error(error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes a deep research request using the Interactions API.
|
|
||||||
*
|
|
||||||
* Deep research uses the Interactions API ({@link https://ai.google.dev/api/interactions-api}),
|
|
||||||
* a completely different surface from generateContent. It creates a background interaction
|
|
||||||
* that performs comprehensive research (up to 60 minutes).
|
|
||||||
*
|
|
||||||
* Supports both streaming and non-streaming modes:
|
|
||||||
* - Streaming: returns a StreamingExecution with a ReadableStream of text deltas
|
|
||||||
* - Non-streaming: polls until completion and returns a ProviderResponse
|
|
||||||
*
|
|
||||||
* Deep research does NOT support custom function calling tools, MCP servers,
|
|
||||||
* or structured output (response_format). These are gracefully ignored.
|
|
||||||
*/
|
|
||||||
export async function executeDeepResearchRequest(
|
|
||||||
config: GeminiExecutionConfig
|
|
||||||
): Promise<ProviderResponse | StreamingExecution> {
|
|
||||||
const { ai, model, request, providerType } = config
|
|
||||||
const logger = createLogger(providerType === 'google' ? 'GoogleProvider' : 'VertexProvider')
|
|
||||||
|
|
||||||
logger.info('Preparing deep research request', {
|
|
||||||
model,
|
|
||||||
hasSystemPrompt: !!request.systemPrompt,
|
|
||||||
hasMessages: !!request.messages?.length,
|
|
||||||
streaming: !!request.stream,
|
|
||||||
hasPreviousInteractionId: !!request.previousInteractionId,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (request.tools?.length) {
|
|
||||||
logger.warn('Deep research does not support custom tools — ignoring tools parameter')
|
|
||||||
}
|
|
||||||
if (request.responseFormat) {
|
|
||||||
logger.warn(
|
|
||||||
'Deep research does not support structured output — ignoring responseFormat parameter'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const providerStartTime = Date.now()
|
|
||||||
const providerStartTimeISO = new Date(providerStartTime).toISOString()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { input, systemInstruction } = collapseMessagesToInput(request)
|
|
||||||
|
|
||||||
// Deep research requires background=true and store=true (store defaults to true,
|
|
||||||
// but we set it explicitly per API requirements)
|
|
||||||
const baseParams = {
|
|
||||||
agent: model as Interactions.CreateAgentInteractionParamsNonStreaming['agent'],
|
|
||||||
input,
|
|
||||||
background: true,
|
|
||||||
store: true,
|
|
||||||
...(systemInstruction && { system_instruction: systemInstruction }),
|
|
||||||
...(request.previousInteractionId && {
|
|
||||||
previous_interaction_id: request.previousInteractionId,
|
|
||||||
}),
|
|
||||||
agent_config: {
|
|
||||||
type: 'deep-research' as const,
|
|
||||||
thinking_summaries: 'auto' as const,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('Creating deep research interaction', {
|
|
||||||
inputLength: input.length,
|
|
||||||
hasSystemInstruction: !!systemInstruction,
|
|
||||||
streaming: !!request.stream,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Streaming mode: create a streaming interaction and return a StreamingExecution
|
|
||||||
if (request.stream) {
|
|
||||||
const streamParams: Interactions.CreateAgentInteractionParamsStreaming = {
|
|
||||||
...baseParams,
|
|
||||||
stream: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
const streamResponse = await ai.interactions.create(streamParams)
|
|
||||||
const firstResponseTime = Date.now() - providerStartTime
|
|
||||||
|
|
||||||
const streamingResult: StreamingExecution = {
|
|
||||||
stream: undefined as unknown as ReadableStream<Uint8Array>,
|
|
||||||
execution: {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
content: '',
|
|
||||||
model,
|
|
||||||
tokens: { input: 0, output: 0, total: 0 },
|
|
||||||
providerTiming: {
|
|
||||||
startTime: providerStartTimeISO,
|
|
||||||
endTime: new Date().toISOString(),
|
|
||||||
duration: Date.now() - providerStartTime,
|
|
||||||
modelTime: firstResponseTime,
|
|
||||||
toolsTime: 0,
|
|
||||||
firstResponseTime,
|
|
||||||
iterations: 1,
|
|
||||||
timeSegments: [
|
|
||||||
{
|
|
||||||
type: 'model',
|
|
||||||
name: 'Deep research (streaming)',
|
|
||||||
startTime: providerStartTime,
|
|
||||||
endTime: providerStartTime + firstResponseTime,
|
|
||||||
duration: firstResponseTime,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
cost: {
|
|
||||||
input: 0,
|
|
||||||
output: 0,
|
|
||||||
total: 0,
|
|
||||||
pricing: { input: 0, output: 0, updatedAt: new Date().toISOString() },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
logs: [],
|
|
||||||
metadata: {
|
|
||||||
startTime: providerStartTimeISO,
|
|
||||||
endTime: new Date().toISOString(),
|
|
||||||
duration: Date.now() - providerStartTime,
|
|
||||||
},
|
|
||||||
isStreaming: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
streamingResult.stream = createDeepResearchStream(
|
|
||||||
streamResponse,
|
|
||||||
(content, usage, streamInteractionId) => {
|
|
||||||
streamingResult.execution.output.content = content
|
|
||||||
streamingResult.execution.output.tokens = {
|
|
||||||
input: usage.inputTokens,
|
|
||||||
output: usage.outputTokens,
|
|
||||||
total: usage.totalTokens,
|
|
||||||
}
|
|
||||||
streamingResult.execution.output.interactionId = streamInteractionId
|
|
||||||
|
|
||||||
const cost = calculateCost(model, usage.inputTokens, usage.outputTokens)
|
|
||||||
streamingResult.execution.output.cost = cost
|
|
||||||
|
|
||||||
const streamEndTime = Date.now()
|
|
||||||
if (streamingResult.execution.output.providerTiming) {
|
|
||||||
streamingResult.execution.output.providerTiming.endTime = new Date(
|
|
||||||
streamEndTime
|
|
||||||
).toISOString()
|
|
||||||
streamingResult.execution.output.providerTiming.duration =
|
|
||||||
streamEndTime - providerStartTime
|
|
||||||
const segments = streamingResult.execution.output.providerTiming.timeSegments
|
|
||||||
if (segments?.[0]) {
|
|
||||||
segments[0].endTime = streamEndTime
|
|
||||||
segments[0].duration = streamEndTime - providerStartTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return streamingResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// Non-streaming mode: create and poll
|
|
||||||
const createParams: Interactions.CreateAgentInteractionParamsNonStreaming = {
|
|
||||||
...baseParams,
|
|
||||||
stream: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
const interaction = await ai.interactions.create(createParams)
|
|
||||||
const interactionId = interaction.id
|
|
||||||
|
|
||||||
logger.info('Deep research interaction created', { interactionId, status: interaction.status })
|
|
||||||
|
|
||||||
// Poll until a terminal status
|
|
||||||
const pollStartTime = Date.now()
|
|
||||||
let result: Interactions.Interaction = interaction
|
|
||||||
|
|
||||||
while (Date.now() - pollStartTime < DEEP_RESEARCH_MAX_DURATION_MS) {
|
|
||||||
if (result.status === 'completed') {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.status === 'failed') {
|
|
||||||
throw new Error(`Deep research interaction failed: ${interactionId}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.status === 'cancelled') {
|
|
||||||
throw new Error(`Deep research interaction was cancelled: ${interactionId}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('Deep research in progress, polling...', {
|
|
||||||
interactionId,
|
|
||||||
status: result.status,
|
|
||||||
elapsedMs: Date.now() - pollStartTime,
|
|
||||||
})
|
|
||||||
|
|
||||||
await sleep(DEEP_RESEARCH_POLL_INTERVAL_MS)
|
|
||||||
result = await ai.interactions.get(interactionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.status !== 'completed') {
|
|
||||||
throw new Error(
|
|
||||||
`Deep research timed out after ${DEEP_RESEARCH_MAX_DURATION_MS / 1000}s (status: ${result.status})`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = extractTextFromInteractionOutputs(result.outputs)
|
|
||||||
const usage = extractInteractionUsage(result.usage)
|
|
||||||
|
|
||||||
logger.info('Deep research completed', {
|
|
||||||
interactionId,
|
|
||||||
contentLength: content.length,
|
|
||||||
inputTokens: usage.inputTokens,
|
|
||||||
outputTokens: usage.outputTokens,
|
|
||||||
reasoningTokens: usage.reasoningTokens,
|
|
||||||
totalTokens: usage.totalTokens,
|
|
||||||
durationMs: Date.now() - providerStartTime,
|
|
||||||
})
|
|
||||||
|
|
||||||
return buildDeepResearchResponse(
|
|
||||||
content,
|
|
||||||
model,
|
|
||||||
usage,
|
|
||||||
providerStartTime,
|
|
||||||
providerStartTimeISO,
|
|
||||||
interactionId
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
const providerEndTime = Date.now()
|
|
||||||
const duration = providerEndTime - providerStartTime
|
|
||||||
|
|
||||||
logger.error('Error in deep research request:', {
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
stack: error instanceof Error ? error.stack : undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
const enhancedError = error instanceof Error ? error : new Error(String(error))
|
|
||||||
Object.assign(enhancedError, {
|
|
||||||
timing: {
|
|
||||||
startTime: providerStartTimeISO,
|
|
||||||
endTime: new Date(providerEndTime).toISOString(),
|
|
||||||
duration,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
throw enhancedError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a request using the Gemini API
|
* Executes a request using the Gemini API
|
||||||
*
|
*
|
||||||
@@ -855,12 +391,6 @@ export async function executeGeminiRequest(
|
|||||||
config: GeminiExecutionConfig
|
config: GeminiExecutionConfig
|
||||||
): Promise<ProviderResponse | StreamingExecution> {
|
): Promise<ProviderResponse | StreamingExecution> {
|
||||||
const { ai, model, request, providerType } = config
|
const { ai, model, request, providerType } = config
|
||||||
|
|
||||||
// Route deep research models to the interactions API
|
|
||||||
if (isDeepResearchModel(model)) {
|
|
||||||
return executeDeepResearchRequest(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
const logger = createLogger(providerType === 'google' ? 'GoogleProvider' : 'VertexProvider')
|
const logger = createLogger(providerType === 'google' ? 'GoogleProvider' : 'VertexProvider')
|
||||||
|
|
||||||
logger.info(`Preparing ${providerType} Gemini request`, {
|
logger.info(`Preparing ${providerType} Gemini request`, {
|
||||||
|
|||||||
@@ -46,9 +46,6 @@ export interface ModelCapabilities {
|
|||||||
levels: string[]
|
levels: string[]
|
||||||
default?: string
|
default?: string
|
||||||
}
|
}
|
||||||
deepResearch?: boolean
|
|
||||||
/** Whether this model supports conversation memory. Defaults to true if omitted. */
|
|
||||||
memory?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelDefinition {
|
export interface ModelDefinition {
|
||||||
@@ -828,7 +825,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
|||||||
name: 'Google',
|
name: 'Google',
|
||||||
description: "Google's Gemini models",
|
description: "Google's Gemini models",
|
||||||
defaultModel: 'gemini-2.5-pro',
|
defaultModel: 'gemini-2.5-pro',
|
||||||
modelPatterns: [/^gemini/, /^deep-research/],
|
modelPatterns: [/^gemini/],
|
||||||
capabilities: {
|
capabilities: {
|
||||||
toolUsageControl: true,
|
toolUsageControl: true,
|
||||||
},
|
},
|
||||||
@@ -931,19 +928,6 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
|||||||
},
|
},
|
||||||
contextWindow: 1000000,
|
contextWindow: 1000000,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'deep-research-pro-preview-12-2025',
|
|
||||||
pricing: {
|
|
||||||
input: 2.0,
|
|
||||||
output: 2.0,
|
|
||||||
updatedAt: '2026-02-10',
|
|
||||||
},
|
|
||||||
capabilities: {
|
|
||||||
deepResearch: true,
|
|
||||||
memory: false,
|
|
||||||
},
|
|
||||||
contextWindow: 1000000,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
vertex: {
|
vertex: {
|
||||||
@@ -1054,19 +1038,6 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
|||||||
},
|
},
|
||||||
contextWindow: 1000000,
|
contextWindow: 1000000,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'vertex/deep-research-pro-preview-12-2025',
|
|
||||||
pricing: {
|
|
||||||
input: 2.0,
|
|
||||||
output: 2.0,
|
|
||||||
updatedAt: '2026-02-10',
|
|
||||||
},
|
|
||||||
capabilities: {
|
|
||||||
deepResearch: true,
|
|
||||||
memory: false,
|
|
||||||
},
|
|
||||||
contextWindow: 1000000,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
deepseek: {
|
deepseek: {
|
||||||
@@ -2509,37 +2480,6 @@ export function getThinkingLevelsForModel(modelId: string): string[] | null {
|
|||||||
return capability?.levels ?? null
|
return capability?.levels ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all models that support deep research capability
|
|
||||||
*/
|
|
||||||
export function getModelsWithDeepResearch(): string[] {
|
|
||||||
const models: string[] = []
|
|
||||||
for (const provider of Object.values(PROVIDER_DEFINITIONS)) {
|
|
||||||
for (const model of provider.models) {
|
|
||||||
if (model.capabilities.deepResearch) {
|
|
||||||
models.push(model.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return models
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all models that explicitly disable memory support (memory: false).
|
|
||||||
* Models without this capability default to supporting memory.
|
|
||||||
*/
|
|
||||||
export function getModelsWithoutMemory(): string[] {
|
|
||||||
const models: string[] = []
|
|
||||||
for (const provider of Object.values(PROVIDER_DEFINITIONS)) {
|
|
||||||
for (const model of provider.models) {
|
|
||||||
if (model.capabilities.memory === false) {
|
|
||||||
models.push(model.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return models
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the max output tokens for a specific model.
|
* Get the max output tokens for a specific model.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -95,8 +95,6 @@ export interface ProviderResponse {
|
|||||||
total: number
|
total: number
|
||||||
pricing: ModelPricing
|
pricing: ModelPricing
|
||||||
}
|
}
|
||||||
/** Interaction ID returned by the Interactions API (used for multi-turn deep research) */
|
|
||||||
interactionId?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToolUsageControl = 'auto' | 'force' | 'none'
|
export type ToolUsageControl = 'auto' | 'force' | 'none'
|
||||||
@@ -171,8 +169,6 @@ export interface ProviderRequest {
|
|||||||
verbosity?: string
|
verbosity?: string
|
||||||
thinkingLevel?: string
|
thinkingLevel?: string
|
||||||
isDeployedContext?: boolean
|
isDeployedContext?: boolean
|
||||||
/** Previous interaction ID for multi-turn Interactions API requests (deep research follow-ups) */
|
|
||||||
previousInteractionId?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const providers: Record<string, ProviderConfig> = {}
|
export const providers: Record<string, ProviderConfig> = {}
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import {
|
|||||||
getMaxOutputTokensForModel as getMaxOutputTokensForModelFromDefinitions,
|
getMaxOutputTokensForModel as getMaxOutputTokensForModelFromDefinitions,
|
||||||
getMaxTemperature as getMaxTempFromDefinitions,
|
getMaxTemperature as getMaxTempFromDefinitions,
|
||||||
getModelPricing as getModelPricingFromDefinitions,
|
getModelPricing as getModelPricingFromDefinitions,
|
||||||
getModelsWithDeepResearch,
|
|
||||||
getModelsWithoutMemory,
|
|
||||||
getModelsWithReasoningEffort,
|
getModelsWithReasoningEffort,
|
||||||
getModelsWithTemperatureSupport,
|
getModelsWithTemperatureSupport,
|
||||||
getModelsWithTempRange01,
|
getModelsWithTempRange01,
|
||||||
@@ -955,8 +953,6 @@ export const MODELS_WITH_TEMPERATURE_SUPPORT = getModelsWithTemperatureSupport()
|
|||||||
export const MODELS_WITH_REASONING_EFFORT = getModelsWithReasoningEffort()
|
export const MODELS_WITH_REASONING_EFFORT = getModelsWithReasoningEffort()
|
||||||
export const MODELS_WITH_VERBOSITY = getModelsWithVerbosity()
|
export const MODELS_WITH_VERBOSITY = getModelsWithVerbosity()
|
||||||
export const MODELS_WITH_THINKING = getModelsWithThinking()
|
export const MODELS_WITH_THINKING = getModelsWithThinking()
|
||||||
export const MODELS_WITH_DEEP_RESEARCH = getModelsWithDeepResearch()
|
|
||||||
export const MODELS_WITHOUT_MEMORY = getModelsWithoutMemory()
|
|
||||||
export const PROVIDERS_WITH_TOOL_USAGE_CONTROL = getProvidersWithToolUsageControl()
|
export const PROVIDERS_WITH_TOOL_USAGE_CONTROL = getProvidersWithToolUsageControl()
|
||||||
|
|
||||||
export function supportsTemperature(model: string): boolean {
|
export function supportsTemperature(model: string): boolean {
|
||||||
@@ -975,10 +971,6 @@ export function supportsThinking(model: string): boolean {
|
|||||||
return MODELS_WITH_THINKING.includes(model.toLowerCase())
|
return MODELS_WITH_THINKING.includes(model.toLowerCase())
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isDeepResearchModel(model: string): boolean {
|
|
||||||
return MODELS_WITH_DEEP_RESEARCH.includes(model.toLowerCase())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the maximum temperature value for a model
|
* Get the maximum temperature value for a model
|
||||||
* @returns Maximum temperature value (1 or 2) or undefined if temperature not supported
|
* @returns Maximum temperature value (1 or 2) or undefined if temperature not supported
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
@@ -310,50 +310,6 @@ function parseModelKey(compositeKey: string): { provider: string; modelId: strin
|
|||||||
return { provider: compositeKey.slice(0, slashIdx), modelId: compositeKey.slice(slashIdx + 1) }
|
return { provider: compositeKey.slice(0, slashIdx), modelId: compositeKey.slice(slashIdx + 1) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert legacy/variant Claude IDs into the canonical ID shape used by the model catalog.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
* - claude-4.5-opus -> claude-opus-4-5
|
|
||||||
* - claude-opus-4.6 -> claude-opus-4-6
|
|
||||||
* - anthropic.claude-opus-4-5-20251101-v1:0 -> claude-opus-4-5 (match key only)
|
|
||||||
*/
|
|
||||||
function canonicalizeModelMatchKey(modelId: string): string {
|
|
||||||
if (!modelId) return modelId
|
|
||||||
const normalized = modelId.trim().toLowerCase()
|
|
||||||
|
|
||||||
const toCanonicalClaude = (tier: string, version: string): string => {
|
|
||||||
const normalizedVersion = version.replace(/\./g, '-')
|
|
||||||
return `claude-${tier}-${normalizedVersion}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const tierFirstExact = normalized.match(/^claude-(opus|sonnet|haiku)-(\d+(?:[.-]\d+)?)$/)
|
|
||||||
if (tierFirstExact) {
|
|
||||||
const [, tier, version] = tierFirstExact
|
|
||||||
return toCanonicalClaude(tier, version)
|
|
||||||
}
|
|
||||||
|
|
||||||
const versionFirstExact = normalized.match(/^claude-(\d+(?:[.-]\d+)?)-(opus|sonnet|haiku)$/)
|
|
||||||
if (versionFirstExact) {
|
|
||||||
const [, version, tier] = versionFirstExact
|
|
||||||
return toCanonicalClaude(tier, version)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tierFirstEmbedded = normalized.match(/claude-(opus|sonnet|haiku)-(\d+(?:[.-]\d+)?)/)
|
|
||||||
if (tierFirstEmbedded) {
|
|
||||||
const [, tier, version] = tierFirstEmbedded
|
|
||||||
return toCanonicalClaude(tier, version)
|
|
||||||
}
|
|
||||||
|
|
||||||
const versionFirstEmbedded = normalized.match(/claude-(\d+(?:[.-]\d+)?)-(opus|sonnet|haiku)/)
|
|
||||||
if (versionFirstEmbedded) {
|
|
||||||
const [, version, tier] = versionFirstEmbedded
|
|
||||||
return toCanonicalClaude(tier, version)
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalized
|
|
||||||
}
|
|
||||||
|
|
||||||
const MODEL_PROVIDER_PRIORITY = [
|
const MODEL_PROVIDER_PRIORITY = [
|
||||||
'anthropic',
|
'anthropic',
|
||||||
'bedrock',
|
'bedrock',
|
||||||
@@ -394,23 +350,12 @@ function normalizeSelectedModelKey(selectedModel: string, models: AvailableModel
|
|||||||
|
|
||||||
const { provider, modelId } = parseModelKey(selectedModel)
|
const { provider, modelId } = parseModelKey(selectedModel)
|
||||||
const targetModelId = modelId || selectedModel
|
const targetModelId = modelId || selectedModel
|
||||||
const targetMatchKey = canonicalizeModelMatchKey(targetModelId)
|
|
||||||
|
|
||||||
const matches = models.filter((m) => {
|
const matches = models.filter((m) => m.id.endsWith(`/${targetModelId}`))
|
||||||
const candidateModelId = parseModelKey(m.id).modelId || m.id
|
|
||||||
const candidateMatchKey = canonicalizeModelMatchKey(candidateModelId)
|
|
||||||
return (
|
|
||||||
candidateModelId === targetModelId ||
|
|
||||||
m.id.endsWith(`/${targetModelId}`) ||
|
|
||||||
candidateMatchKey === targetMatchKey
|
|
||||||
)
|
|
||||||
})
|
|
||||||
if (matches.length === 0) return selectedModel
|
if (matches.length === 0) return selectedModel
|
||||||
|
|
||||||
if (provider) {
|
if (provider) {
|
||||||
const sameProvider = matches.find(
|
const sameProvider = matches.find((m) => m.provider === provider)
|
||||||
(m) => m.provider === provider || m.id.startsWith(`${provider}/`)
|
|
||||||
)
|
|
||||||
if (sameProvider) return sameProvider.id
|
if (sameProvider) return sameProvider.id
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1148,12 +1093,11 @@ export const useCopilotStore = create<CopilotStore>()(
|
|||||||
const chatConfig = chat.config ?? {}
|
const chatConfig = chat.config ?? {}
|
||||||
const chatMode = chatConfig.mode || get().mode
|
const chatMode = chatConfig.mode || get().mode
|
||||||
const chatModel = chatConfig.model || get().selectedModel
|
const chatModel = chatConfig.model || get().selectedModel
|
||||||
const normalizedChatModel = normalizeSelectedModelKey(chatModel, get().availableModels)
|
|
||||||
|
|
||||||
logger.debug('[Chat] Restoring chat config', {
|
logger.debug('[Chat] Restoring chat config', {
|
||||||
chatId: chat.id,
|
chatId: chat.id,
|
||||||
mode: chatMode,
|
mode: chatMode,
|
||||||
model: normalizedChatModel,
|
model: chatModel,
|
||||||
hasPlanArtifact: !!planArtifact,
|
hasPlanArtifact: !!planArtifact,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1175,7 +1119,7 @@ export const useCopilotStore = create<CopilotStore>()(
|
|||||||
showPlanTodos: false,
|
showPlanTodos: false,
|
||||||
streamingPlanContent: planArtifact,
|
streamingPlanContent: planArtifact,
|
||||||
mode: chatMode,
|
mode: chatMode,
|
||||||
selectedModel: normalizedChatModel as CopilotStore['selectedModel'],
|
selectedModel: chatModel as CopilotStore['selectedModel'],
|
||||||
suppressAutoSelect: false,
|
suppressAutoSelect: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1348,10 +1292,6 @@ export const useCopilotStore = create<CopilotStore>()(
|
|||||||
const refreshedConfig = updatedCurrentChat.config ?? {}
|
const refreshedConfig = updatedCurrentChat.config ?? {}
|
||||||
const refreshedMode = refreshedConfig.mode || get().mode
|
const refreshedMode = refreshedConfig.mode || get().mode
|
||||||
const refreshedModel = refreshedConfig.model || get().selectedModel
|
const refreshedModel = refreshedConfig.model || get().selectedModel
|
||||||
const normalizedRefreshedModel = normalizeSelectedModelKey(
|
|
||||||
refreshedModel,
|
|
||||||
get().availableModels
|
|
||||||
)
|
|
||||||
const toolCallsById = buildToolCallsById(normalizedMessages)
|
const toolCallsById = buildToolCallsById(normalizedMessages)
|
||||||
|
|
||||||
set({
|
set({
|
||||||
@@ -1360,7 +1300,7 @@ export const useCopilotStore = create<CopilotStore>()(
|
|||||||
toolCallsById,
|
toolCallsById,
|
||||||
streamingPlanContent: refreshedPlanArtifact,
|
streamingPlanContent: refreshedPlanArtifact,
|
||||||
mode: refreshedMode,
|
mode: refreshedMode,
|
||||||
selectedModel: normalizedRefreshedModel as CopilotStore['selectedModel'],
|
selectedModel: refreshedModel as CopilotStore['selectedModel'],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -1380,15 +1320,11 @@ export const useCopilotStore = create<CopilotStore>()(
|
|||||||
const chatConfig = mostRecentChat.config ?? {}
|
const chatConfig = mostRecentChat.config ?? {}
|
||||||
const chatMode = chatConfig.mode || get().mode
|
const chatMode = chatConfig.mode || get().mode
|
||||||
const chatModel = chatConfig.model || get().selectedModel
|
const chatModel = chatConfig.model || get().selectedModel
|
||||||
const normalizedChatModel = normalizeSelectedModelKey(
|
|
||||||
chatModel,
|
|
||||||
get().availableModels
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info('[Chat] Auto-selecting most recent chat with config', {
|
logger.info('[Chat] Auto-selecting most recent chat with config', {
|
||||||
chatId: mostRecentChat.id,
|
chatId: mostRecentChat.id,
|
||||||
mode: chatMode,
|
mode: chatMode,
|
||||||
model: normalizedChatModel,
|
model: chatModel,
|
||||||
hasPlanArtifact: !!planArtifact,
|
hasPlanArtifact: !!planArtifact,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1400,7 +1336,7 @@ export const useCopilotStore = create<CopilotStore>()(
|
|||||||
toolCallsById,
|
toolCallsById,
|
||||||
streamingPlanContent: planArtifact,
|
streamingPlanContent: planArtifact,
|
||||||
mode: chatMode,
|
mode: chatMode,
|
||||||
selectedModel: normalizedChatModel as CopilotStore['selectedModel'],
|
selectedModel: chatModel as CopilotStore['selectedModel'],
|
||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
await get().loadMessageCheckpoints(mostRecentChat.id)
|
await get().loadMessageCheckpoints(mostRecentChat.id)
|
||||||
@@ -2332,8 +2268,7 @@ export const useCopilotStore = create<CopilotStore>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
setSelectedModel: async (model) => {
|
setSelectedModel: async (model) => {
|
||||||
const normalizedModel = normalizeSelectedModelKey(model, get().availableModels)
|
set({ selectedModel: model })
|
||||||
set({ selectedModel: normalizedModel as CopilotStore['selectedModel'] })
|
|
||||||
},
|
},
|
||||||
setAgentPrefetch: (prefetch) => set({ agentPrefetch: prefetch }),
|
setAgentPrefetch: (prefetch) => set({ agentPrefetch: prefetch }),
|
||||||
loadAvailableModels: async () => {
|
loadAvailableModels: async () => {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { BLOGPOST_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
import {
|
||||||
|
CONTENT_BODY_OUTPUT_PROPERTIES,
|
||||||
|
TIMESTAMP_OUTPUT,
|
||||||
|
VERSION_OUTPUT_PROPERTIES,
|
||||||
|
} from '@/tools/confluence/types'
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceCreateBlogPostParams {
|
export interface ConfluenceCreateBlogPostParams {
|
||||||
@@ -125,6 +129,23 @@ export const confluenceCreateBlogPostTool: ToolConfig<
|
|||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
ts: TIMESTAMP_OUTPUT,
|
ts: TIMESTAMP_OUTPUT,
|
||||||
...BLOGPOST_ITEM_PROPERTIES,
|
id: { type: 'string', description: 'Created blog post ID' },
|
||||||
|
title: { type: 'string', description: 'Blog post title' },
|
||||||
|
status: { type: 'string', description: 'Blog post status', optional: true },
|
||||||
|
spaceId: { type: 'string', description: 'Space ID' },
|
||||||
|
authorId: { type: 'string', description: 'Author account ID', optional: true },
|
||||||
|
body: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Blog post body content',
|
||||||
|
properties: CONTENT_BODY_OUTPUT_PROPERTIES,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Blog post version information',
|
||||||
|
properties: VERSION_OUTPUT_PROPERTIES,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
webUrl: { type: 'string', description: 'URL to view the blog post', optional: true },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceCreateCommentParams {
|
export interface ConfluenceCreateCommentParams {
|
||||||
@@ -100,7 +99,7 @@ export const confluenceCreateCommentTool: ToolConfig<
|
|||||||
},
|
},
|
||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
ts: TIMESTAMP_OUTPUT,
|
ts: { type: 'string', description: 'Timestamp of creation' },
|
||||||
commentId: { type: 'string', description: 'Created comment ID' },
|
commentId: { type: 'string', description: 'Created comment ID' },
|
||||||
pageId: { type: 'string', description: 'Page ID' },
|
pageId: { type: 'string', description: 'Page ID' },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import { CONTENT_BODY_OUTPUT_PROPERTIES, VERSION_OUTPUT_PROPERTIES } from '@/tools/confluence/types'
|
||||||
CONTENT_BODY_OUTPUT_PROPERTIES,
|
|
||||||
TIMESTAMP_OUTPUT,
|
|
||||||
VERSION_OUTPUT_PROPERTIES,
|
|
||||||
} from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceCreatePageParams {
|
export interface ConfluenceCreatePageParams {
|
||||||
@@ -132,7 +128,7 @@ export const confluenceCreatePageTool: ToolConfig<
|
|||||||
},
|
},
|
||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
ts: TIMESTAMP_OUTPUT,
|
ts: { type: 'string', description: 'Timestamp of creation' },
|
||||||
pageId: { type: 'string', description: 'Created page ID' },
|
pageId: { type: 'string', description: 'Created page ID' },
|
||||||
title: { type: 'string', description: 'Page title' },
|
title: { type: 'string', description: 'Page title' },
|
||||||
status: { type: 'string', description: 'Page status', optional: true },
|
status: { type: 'string', description: 'Page status', optional: true },
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PAGE_PROPERTY_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
import { TIMESTAMP_OUTPUT, VERSION_OUTPUT_PROPERTIES } from '@/tools/confluence/types'
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceCreatePagePropertyParams {
|
export interface ConfluenceCreatePagePropertyParams {
|
||||||
@@ -115,6 +115,13 @@ export const confluenceCreatePagePropertyTool: ToolConfig<
|
|||||||
ts: TIMESTAMP_OUTPUT,
|
ts: TIMESTAMP_OUTPUT,
|
||||||
pageId: { type: 'string', description: 'ID of the page' },
|
pageId: { type: 'string', description: 'ID of the page' },
|
||||||
propertyId: { type: 'string', description: 'ID of the created property' },
|
propertyId: { type: 'string', description: 'ID of the created property' },
|
||||||
...PAGE_PROPERTY_ITEM_PROPERTIES,
|
key: { type: 'string', description: 'Property key' },
|
||||||
|
value: { type: 'json', description: 'Property value' },
|
||||||
|
version: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Version information',
|
||||||
|
properties: VERSION_OUTPUT_PROPERTIES,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
import { TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceCreateSpaceParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
name: string
|
|
||||||
key: string
|
|
||||||
description?: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceCreateSpaceResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
id: string
|
|
||||||
key: string
|
|
||||||
name: string
|
|
||||||
type: string
|
|
||||||
status: string
|
|
||||||
homepageId: string | null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceCreateSpaceTool: ToolConfig<
|
|
||||||
ConfluenceCreateSpaceParams,
|
|
||||||
ConfluenceCreateSpaceResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_create_space',
|
|
||||||
name: 'Confluence Create Space',
|
|
||||||
description: 'Create a new Confluence space.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Name for the new space',
|
|
||||||
},
|
|
||||||
key: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Unique key for the space (short identifier used in URLs)',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Description for the space',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: () => '/api/tools/confluence/spaces',
|
|
||||||
method: 'POST',
|
|
||||||
headers: (params: ConfluenceCreateSpaceParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: ConfluenceCreateSpaceParams) => ({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
name: params.name,
|
|
||||||
key: params.key,
|
|
||||||
description: params.description,
|
|
||||||
cloudId: params.cloudId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
id: data.id ?? '',
|
|
||||||
key: data.key ?? '',
|
|
||||||
name: data.name ?? '',
|
|
||||||
type: data.type ?? '',
|
|
||||||
status: data.status ?? '',
|
|
||||||
homepageId: data.homepageId ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
id: { type: 'string', description: 'ID of the created space' },
|
|
||||||
key: { type: 'string', description: 'Key of the created space' },
|
|
||||||
name: { type: 'string', description: 'Name of the created space' },
|
|
||||||
type: { type: 'string', description: 'Type of the space' },
|
|
||||||
status: { type: 'string', description: 'Status of the space' },
|
|
||||||
homepageId: { type: 'string', description: 'ID of the space homepage', optional: true },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
import { SPACE_PROPERTY_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceCreateSpacePropertyParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
spaceId: string
|
|
||||||
key: string
|
|
||||||
value: unknown
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceCreateSpacePropertyResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
spaceId: string
|
|
||||||
propertyId: string
|
|
||||||
key: string
|
|
||||||
value: unknown
|
|
||||||
version: { number: number } | null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceCreateSpacePropertyTool: ToolConfig<
|
|
||||||
ConfluenceCreateSpacePropertyParams,
|
|
||||||
ConfluenceCreateSpacePropertyResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_create_space_property',
|
|
||||||
name: 'Confluence Create Space Property',
|
|
||||||
description: 'Create a new content property on a Confluence space.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
spaceId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the space to add the property to',
|
|
||||||
},
|
|
||||||
key: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The key/name for the property',
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: 'json',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The value for the property (can be any JSON value)',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: () => '/api/tools/confluence/space-properties',
|
|
||||||
method: 'POST',
|
|
||||||
headers: (params: ConfluenceCreateSpacePropertyParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: ConfluenceCreateSpacePropertyParams) => ({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
spaceId: params.spaceId?.trim(),
|
|
||||||
key: params.key,
|
|
||||||
value: params.value,
|
|
||||||
cloudId: params.cloudId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
spaceId: data.spaceId ?? '',
|
|
||||||
propertyId: data.id ?? '',
|
|
||||||
key: data.key ?? '',
|
|
||||||
value: data.value ?? null,
|
|
||||||
version: data.version ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
spaceId: { type: 'string', description: 'ID of the space' },
|
|
||||||
propertyId: { type: 'string', description: 'ID of the created property' },
|
|
||||||
...SPACE_PROPERTY_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
import { TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceCreateWhiteboardParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
spaceId: string
|
|
||||||
title: string
|
|
||||||
parentId?: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceCreateWhiteboardResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
spaceId: string
|
|
||||||
parentId: string | null
|
|
||||||
parentType: string | null
|
|
||||||
authorId: string | null
|
|
||||||
createdAt: string | null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceCreateWhiteboardTool: ToolConfig<
|
|
||||||
ConfluenceCreateWhiteboardParams,
|
|
||||||
ConfluenceCreateWhiteboardResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_create_whiteboard',
|
|
||||||
name: 'Confluence Create Whiteboard',
|
|
||||||
description: 'Create a new whiteboard in a Confluence space.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
spaceId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the space to create the whiteboard in',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Title for the whiteboard',
|
|
||||||
},
|
|
||||||
parentId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'ID of the parent content (optional)',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: () => '/api/tools/confluence/whiteboards',
|
|
||||||
method: 'POST',
|
|
||||||
headers: (params: ConfluenceCreateWhiteboardParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: ConfluenceCreateWhiteboardParams) => ({
|
|
||||||
action: 'create',
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
spaceId: params.spaceId?.trim(),
|
|
||||||
title: params.title,
|
|
||||||
parentId: params.parentId?.trim(),
|
|
||||||
cloudId: params.cloudId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
id: data.id ?? '',
|
|
||||||
title: data.title ?? '',
|
|
||||||
spaceId: data.spaceId ?? '',
|
|
||||||
parentId: data.parentId ?? null,
|
|
||||||
parentType: data.parentType ?? null,
|
|
||||||
authorId: data.authorId ?? null,
|
|
||||||
createdAt: data.createdAt ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
id: { type: 'string', description: 'ID of the created whiteboard' },
|
|
||||||
title: { type: 'string', description: 'Title of the whiteboard' },
|
|
||||||
spaceId: { type: 'string', description: 'ID of the space' },
|
|
||||||
parentId: { type: 'string', description: 'ID of the parent content', optional: true },
|
|
||||||
parentType: { type: 'string', description: 'Type of the parent content', optional: true },
|
|
||||||
authorId: { type: 'string', description: 'Author account ID', optional: true },
|
|
||||||
createdAt: { type: 'string', description: 'Creation timestamp', optional: true },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { DELETED_OUTPUT, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceDeleteAttachmentParams {
|
export interface ConfluenceDeleteAttachmentParams {
|
||||||
@@ -91,8 +90,8 @@ export const confluenceDeleteAttachmentTool: ToolConfig<
|
|||||||
},
|
},
|
||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
ts: TIMESTAMP_OUTPUT,
|
ts: { type: 'string', description: 'Timestamp of deletion' },
|
||||||
attachmentId: { type: 'string', description: 'Deleted attachment ID' },
|
attachmentId: { type: 'string', description: 'Deleted attachment ID' },
|
||||||
deleted: DELETED_OUTPUT,
|
deleted: { type: 'boolean', description: 'Deletion status' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
import type {
|
|
||||||
ConfluenceDeleteBlogPostParams,
|
|
||||||
ConfluenceDeleteBlogPostResponse,
|
|
||||||
} from '@/tools/confluence/types'
|
|
||||||
import { DELETED_OUTPUT, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export const confluenceDeleteBlogPostTool: ToolConfig<
|
|
||||||
ConfluenceDeleteBlogPostParams,
|
|
||||||
ConfluenceDeleteBlogPostResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_delete_blogpost',
|
|
||||||
name: 'Confluence Delete Blog Post',
|
|
||||||
description: 'Delete a Confluence blog post.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
blogPostId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the blog post to delete',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: () => '/api/tools/confluence/blogposts',
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: (params: ConfluenceDeleteBlogPostParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: ConfluenceDeleteBlogPostParams) => ({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
blogPostId: params.blogPostId?.trim(),
|
|
||||||
cloudId: params.cloudId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
blogPostId: data.blogPostId ?? '',
|
|
||||||
deleted: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
blogPostId: { type: 'string', description: 'Deleted blog post ID' },
|
|
||||||
deleted: DELETED_OUTPUT,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { DELETED_OUTPUT, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceDeleteCommentParams {
|
export interface ConfluenceDeleteCommentParams {
|
||||||
@@ -91,8 +90,8 @@ export const confluenceDeleteCommentTool: ToolConfig<
|
|||||||
},
|
},
|
||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
ts: TIMESTAMP_OUTPUT,
|
ts: { type: 'string', description: 'Timestamp of deletion' },
|
||||||
commentId: { type: 'string', description: 'Deleted comment ID' },
|
commentId: { type: 'string', description: 'Deleted comment ID' },
|
||||||
deleted: DELETED_OUTPUT,
|
deleted: { type: 'boolean', description: 'Deletion status' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
import { TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceDeleteLabelParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
pageId: string
|
|
||||||
labelName: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceDeleteLabelResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
pageId: string
|
|
||||||
labelName: string
|
|
||||||
deleted: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceDeleteLabelTool: ToolConfig<
|
|
||||||
ConfluenceDeleteLabelParams,
|
|
||||||
ConfluenceDeleteLabelResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_delete_label',
|
|
||||||
name: 'Confluence Delete Label',
|
|
||||||
description: 'Remove a label from a Confluence page.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
pageId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Confluence page ID to remove the label from',
|
|
||||||
},
|
|
||||||
labelName: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Name of the label to remove',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: () => '/api/tools/confluence/labels',
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: (params: ConfluenceDeleteLabelParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: ConfluenceDeleteLabelParams) => ({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
pageId: params.pageId?.trim(),
|
|
||||||
labelName: params.labelName?.trim(),
|
|
||||||
cloudId: params.cloudId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
pageId: data.pageId ?? '',
|
|
||||||
labelName: data.labelName ?? '',
|
|
||||||
deleted: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
pageId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Page ID the label was removed from',
|
|
||||||
},
|
|
||||||
labelName: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Name of the removed label',
|
|
||||||
},
|
|
||||||
deleted: {
|
|
||||||
type: 'boolean',
|
|
||||||
description: 'Deletion status',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { DELETED_OUTPUT, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceDeletePageParams {
|
export interface ConfluenceDeletePageParams {
|
||||||
@@ -101,8 +100,8 @@ export const confluenceDeletePageTool: ToolConfig<
|
|||||||
},
|
},
|
||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
ts: TIMESTAMP_OUTPUT,
|
ts: { type: 'string', description: 'Timestamp of deletion' },
|
||||||
pageId: { type: 'string', description: 'Deleted page ID' },
|
pageId: { type: 'string', description: 'Deleted page ID' },
|
||||||
deleted: DELETED_OUTPUT,
|
deleted: { type: 'boolean', description: 'Deletion status' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
import { TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceDeletePagePropertyParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
pageId: string
|
|
||||||
propertyId: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceDeletePagePropertyResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
pageId: string
|
|
||||||
propertyId: string
|
|
||||||
deleted: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceDeletePagePropertyTool: ToolConfig<
|
|
||||||
ConfluenceDeletePagePropertyParams,
|
|
||||||
ConfluenceDeletePagePropertyResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_delete_page_property',
|
|
||||||
name: 'Confluence Delete Page Property',
|
|
||||||
description: 'Delete a content property from a Confluence page by its property ID.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
pageId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the page containing the property',
|
|
||||||
},
|
|
||||||
propertyId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the property to delete',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: () => '/api/tools/confluence/page-properties',
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: (params: ConfluenceDeletePagePropertyParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: ConfluenceDeletePagePropertyParams) => ({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
pageId: params.pageId?.trim(),
|
|
||||||
propertyId: params.propertyId?.trim(),
|
|
||||||
cloudId: params.cloudId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
pageId: data.pageId ?? '',
|
|
||||||
propertyId: data.propertyId ?? '',
|
|
||||||
deleted: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
pageId: { type: 'string', description: 'ID of the page' },
|
|
||||||
propertyId: { type: 'string', description: 'ID of the deleted property' },
|
|
||||||
deleted: { type: 'boolean', description: 'Deletion status' },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import { TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceDeleteSpaceParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
spaceId: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceDeleteSpaceResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
spaceId: string
|
|
||||||
deleted: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceDeleteSpaceTool: ToolConfig<
|
|
||||||
ConfluenceDeleteSpaceParams,
|
|
||||||
ConfluenceDeleteSpaceResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_delete_space',
|
|
||||||
name: 'Confluence Delete Space',
|
|
||||||
description: 'Delete a Confluence space by its ID.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
spaceId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the space to delete',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: () => '/api/tools/confluence/spaces',
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: (params: ConfluenceDeleteSpaceParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: ConfluenceDeleteSpaceParams) => ({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
spaceId: params.spaceId?.trim(),
|
|
||||||
cloudId: params.cloudId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
spaceId: data.spaceId ?? '',
|
|
||||||
deleted: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
spaceId: { type: 'string', description: 'ID of the deleted space' },
|
|
||||||
deleted: { type: 'boolean', description: 'Deletion status' },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
import { BLOGPOST_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
import {
|
||||||
|
CONTENT_BODY_OUTPUT_PROPERTIES,
|
||||||
|
TIMESTAMP_OUTPUT,
|
||||||
|
VERSION_OUTPUT_PROPERTIES,
|
||||||
|
} from '@/tools/confluence/types'
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceGetBlogPostParams {
|
export interface ConfluenceGetBlogPostParams {
|
||||||
@@ -117,6 +121,24 @@ export const confluenceGetBlogPostTool: ToolConfig<
|
|||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
ts: TIMESTAMP_OUTPUT,
|
ts: TIMESTAMP_OUTPUT,
|
||||||
...BLOGPOST_ITEM_PROPERTIES,
|
id: { type: 'string', description: 'Blog post ID' },
|
||||||
|
title: { type: 'string', description: 'Blog post title' },
|
||||||
|
status: { type: 'string', description: 'Blog post status', optional: true },
|
||||||
|
spaceId: { type: 'string', description: 'Space ID', optional: true },
|
||||||
|
authorId: { type: 'string', description: 'Author account ID', optional: true },
|
||||||
|
createdAt: { type: 'string', description: 'Creation timestamp', optional: true },
|
||||||
|
version: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Version information',
|
||||||
|
properties: VERSION_OUTPUT_PROPERTIES,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Blog post body content in requested format(s)',
|
||||||
|
properties: CONTENT_BODY_OUTPUT_PROPERTIES,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
webUrl: { type: 'string', description: 'URL to view the blog post', optional: true },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
import { PAGE_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceGetPagesByLabelParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
labelId: string
|
|
||||||
limit?: number
|
|
||||||
cursor?: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceGetPagesByLabelResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
labelId: string
|
|
||||||
pages: Array<{
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
status: string | null
|
|
||||||
spaceId: string | null
|
|
||||||
parentId: string | null
|
|
||||||
authorId: string | null
|
|
||||||
createdAt: string | null
|
|
||||||
version: {
|
|
||||||
number: number
|
|
||||||
message?: string
|
|
||||||
createdAt?: string
|
|
||||||
} | null
|
|
||||||
}>
|
|
||||||
nextCursor: string | null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceGetPagesByLabelTool: ToolConfig<
|
|
||||||
ConfluenceGetPagesByLabelParams,
|
|
||||||
ConfluenceGetPagesByLabelResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_get_pages_by_label',
|
|
||||||
name: 'Confluence Get Pages by Label',
|
|
||||||
description: 'Retrieve all pages that have a specific label applied.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
labelId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the label to get pages for',
|
|
||||||
},
|
|
||||||
limit: {
|
|
||||||
type: 'number',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Maximum number of pages to return (default: 50, max: 250)',
|
|
||||||
},
|
|
||||||
cursor: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Pagination cursor from previous response',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: (params: ConfluenceGetPagesByLabelParams) => {
|
|
||||||
const query = new URLSearchParams({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
labelId: params.labelId,
|
|
||||||
limit: String(params.limit || 50),
|
|
||||||
})
|
|
||||||
if (params.cursor) {
|
|
||||||
query.set('cursor', params.cursor)
|
|
||||||
}
|
|
||||||
if (params.cloudId) {
|
|
||||||
query.set('cloudId', params.cloudId)
|
|
||||||
}
|
|
||||||
return `/api/tools/confluence/pages-by-label?${query.toString()}`
|
|
||||||
},
|
|
||||||
method: 'GET',
|
|
||||||
headers: (params: ConfluenceGetPagesByLabelParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
labelId: data.labelId ?? '',
|
|
||||||
pages: data.pages ?? [],
|
|
||||||
nextCursor: data.nextCursor ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
labelId: { type: 'string', description: 'ID of the label' },
|
|
||||||
pages: {
|
|
||||||
type: 'array',
|
|
||||||
description: 'Array of pages with this label',
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
properties: PAGE_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nextCursor: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Cursor for fetching the next page of results',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
import { SPACE_PROPERTY_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceGetSpacePropertyParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
spaceId: string
|
|
||||||
propertyId: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceGetSpacePropertyResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
spaceId: string
|
|
||||||
id: string
|
|
||||||
key: string
|
|
||||||
value: unknown
|
|
||||||
version: { number: number } | null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceGetSpacePropertyTool: ToolConfig<
|
|
||||||
ConfluenceGetSpacePropertyParams,
|
|
||||||
ConfluenceGetSpacePropertyResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_get_space_property',
|
|
||||||
name: 'Confluence Get Space Property',
|
|
||||||
description: 'Get a specific content property from a Confluence space by its ID.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
spaceId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the space containing the property',
|
|
||||||
},
|
|
||||||
propertyId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the property to retrieve',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: (params: ConfluenceGetSpacePropertyParams) => {
|
|
||||||
const query = new URLSearchParams({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
spaceId: params.spaceId,
|
|
||||||
propertyId: params.propertyId,
|
|
||||||
action: 'get',
|
|
||||||
})
|
|
||||||
if (params.cloudId) query.set('cloudId', params.cloudId)
|
|
||||||
return `/api/tools/confluence/space-properties?${query.toString()}`
|
|
||||||
},
|
|
||||||
method: 'GET',
|
|
||||||
headers: (params: ConfluenceGetSpacePropertyParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
spaceId: data.spaceId ?? '',
|
|
||||||
id: data.id ?? '',
|
|
||||||
key: data.key ?? '',
|
|
||||||
value: data.value ?? null,
|
|
||||||
version: data.version ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
spaceId: { type: 'string', description: 'ID of the space' },
|
|
||||||
...SPACE_PROPERTY_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
import { TASK_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceGetTaskParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
taskId: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceGetTaskResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
id: string
|
|
||||||
localId: string
|
|
||||||
spaceId: string | null
|
|
||||||
pageId: string | null
|
|
||||||
blogPostId: string | null
|
|
||||||
status: string
|
|
||||||
body: Record<string, unknown> | null
|
|
||||||
createdBy: string | null
|
|
||||||
assignedTo: string | null
|
|
||||||
completedBy: string | null
|
|
||||||
createdAt: string | null
|
|
||||||
updatedAt: string | null
|
|
||||||
dueAt: string | null
|
|
||||||
completedAt: string | null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceGetTaskTool: ToolConfig<ConfluenceGetTaskParams, ConfluenceGetTaskResponse> =
|
|
||||||
{
|
|
||||||
id: 'confluence_get_task',
|
|
||||||
name: 'Confluence Get Task',
|
|
||||||
description: 'Get a specific task by its ID from Confluence.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
taskId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the task to retrieve',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: () => '/api/tools/confluence/tasks',
|
|
||||||
method: 'POST',
|
|
||||||
headers: (params: ConfluenceGetTaskParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: ConfluenceGetTaskParams) => ({
|
|
||||||
action: 'get',
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
taskId: params.taskId?.trim(),
|
|
||||||
cloudId: params.cloudId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
id: data.id ?? '',
|
|
||||||
localId: data.localId ?? '',
|
|
||||||
spaceId: data.spaceId ?? null,
|
|
||||||
pageId: data.pageId ?? null,
|
|
||||||
blogPostId: data.blogPostId ?? null,
|
|
||||||
status: data.status ?? '',
|
|
||||||
body: data.body ?? null,
|
|
||||||
createdBy: data.createdBy ?? null,
|
|
||||||
assignedTo: data.assignedTo ?? null,
|
|
||||||
completedBy: data.completedBy ?? null,
|
|
||||||
createdAt: data.createdAt ?? null,
|
|
||||||
updatedAt: data.updatedAt ?? null,
|
|
||||||
dueAt: data.dueAt ?? null,
|
|
||||||
completedAt: data.completedAt ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
...TASK_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -4,16 +4,12 @@ import { confluenceCreateCommentTool } from '@/tools/confluence/create_comment'
|
|||||||
import { confluenceCreatePageTool } from '@/tools/confluence/create_page'
|
import { confluenceCreatePageTool } from '@/tools/confluence/create_page'
|
||||||
import { confluenceCreatePagePropertyTool } from '@/tools/confluence/create_page_property'
|
import { confluenceCreatePagePropertyTool } from '@/tools/confluence/create_page_property'
|
||||||
import { confluenceDeleteAttachmentTool } from '@/tools/confluence/delete_attachment'
|
import { confluenceDeleteAttachmentTool } from '@/tools/confluence/delete_attachment'
|
||||||
import { confluenceDeleteBlogPostTool } from '@/tools/confluence/delete_blogpost'
|
|
||||||
import { confluenceDeleteCommentTool } from '@/tools/confluence/delete_comment'
|
import { confluenceDeleteCommentTool } from '@/tools/confluence/delete_comment'
|
||||||
import { confluenceDeleteLabelTool } from '@/tools/confluence/delete_label'
|
|
||||||
import { confluenceDeletePageTool } from '@/tools/confluence/delete_page'
|
import { confluenceDeletePageTool } from '@/tools/confluence/delete_page'
|
||||||
import { confluenceDeletePagePropertyTool } from '@/tools/confluence/delete_page_property'
|
|
||||||
import { confluenceGetBlogPostTool } from '@/tools/confluence/get_blogpost'
|
import { confluenceGetBlogPostTool } from '@/tools/confluence/get_blogpost'
|
||||||
import { confluenceGetPageAncestorsTool } from '@/tools/confluence/get_page_ancestors'
|
import { confluenceGetPageAncestorsTool } from '@/tools/confluence/get_page_ancestors'
|
||||||
import { confluenceGetPageChildrenTool } from '@/tools/confluence/get_page_children'
|
import { confluenceGetPageChildrenTool } from '@/tools/confluence/get_page_children'
|
||||||
import { confluenceGetPageVersionTool } from '@/tools/confluence/get_page_version'
|
import { confluenceGetPageVersionTool } from '@/tools/confluence/get_page_version'
|
||||||
import { confluenceGetPagesByLabelTool } from '@/tools/confluence/get_pages_by_label'
|
|
||||||
import { confluenceGetSpaceTool } from '@/tools/confluence/get_space'
|
import { confluenceGetSpaceTool } from '@/tools/confluence/get_space'
|
||||||
import { confluenceListAttachmentsTool } from '@/tools/confluence/list_attachments'
|
import { confluenceListAttachmentsTool } from '@/tools/confluence/list_attachments'
|
||||||
import { confluenceListBlogPostsTool } from '@/tools/confluence/list_blogposts'
|
import { confluenceListBlogPostsTool } from '@/tools/confluence/list_blogposts'
|
||||||
@@ -23,7 +19,6 @@ import { confluenceListLabelsTool } from '@/tools/confluence/list_labels'
|
|||||||
import { confluenceListPagePropertiesTool } from '@/tools/confluence/list_page_properties'
|
import { confluenceListPagePropertiesTool } from '@/tools/confluence/list_page_properties'
|
||||||
import { confluenceListPageVersionsTool } from '@/tools/confluence/list_page_versions'
|
import { confluenceListPageVersionsTool } from '@/tools/confluence/list_page_versions'
|
||||||
import { confluenceListPagesInSpaceTool } from '@/tools/confluence/list_pages_in_space'
|
import { confluenceListPagesInSpaceTool } from '@/tools/confluence/list_pages_in_space'
|
||||||
import { confluenceListSpaceLabelsTool } from '@/tools/confluence/list_space_labels'
|
|
||||||
import { confluenceListSpacesTool } from '@/tools/confluence/list_spaces'
|
import { confluenceListSpacesTool } from '@/tools/confluence/list_spaces'
|
||||||
import { confluenceRetrieveTool } from '@/tools/confluence/retrieve'
|
import { confluenceRetrieveTool } from '@/tools/confluence/retrieve'
|
||||||
import { confluenceSearchTool } from '@/tools/confluence/search'
|
import { confluenceSearchTool } from '@/tools/confluence/search'
|
||||||
@@ -32,9 +27,6 @@ import {
|
|||||||
ATTACHMENT_ITEM_PROPERTIES,
|
ATTACHMENT_ITEM_PROPERTIES,
|
||||||
ATTACHMENT_OUTPUT,
|
ATTACHMENT_OUTPUT,
|
||||||
ATTACHMENTS_OUTPUT,
|
ATTACHMENTS_OUTPUT,
|
||||||
BLOGPOST_ITEM_PROPERTIES,
|
|
||||||
BLOGPOST_OUTPUT,
|
|
||||||
BLOGPOSTS_OUTPUT,
|
|
||||||
BODY_FORMAT_PROPERTIES,
|
BODY_FORMAT_PROPERTIES,
|
||||||
COMMENT_BODY_OUTPUT_PROPERTIES,
|
COMMENT_BODY_OUTPUT_PROPERTIES,
|
||||||
COMMENT_ITEM_PROPERTIES,
|
COMMENT_ITEM_PROPERTIES,
|
||||||
@@ -51,7 +43,6 @@ import {
|
|||||||
PAGE_ID_OUTPUT,
|
PAGE_ID_OUTPUT,
|
||||||
PAGE_ITEM_PROPERTIES,
|
PAGE_ITEM_PROPERTIES,
|
||||||
PAGE_OUTPUT,
|
PAGE_OUTPUT,
|
||||||
PAGE_PROPERTY_ITEM_PROPERTIES,
|
|
||||||
PAGES_OUTPUT,
|
PAGES_OUTPUT,
|
||||||
PAGINATION_LINKS_PROPERTIES,
|
PAGINATION_LINKS_PROPERTIES,
|
||||||
SEARCH_RESULT_ITEM_PROPERTIES,
|
SEARCH_RESULT_ITEM_PROPERTIES,
|
||||||
@@ -64,15 +55,12 @@ import {
|
|||||||
SPACES_OUTPUT,
|
SPACES_OUTPUT,
|
||||||
SUCCESS_OUTPUT,
|
SUCCESS_OUTPUT,
|
||||||
TIMESTAMP_OUTPUT,
|
TIMESTAMP_OUTPUT,
|
||||||
UPDATED_OUTPUT,
|
|
||||||
URL_OUTPUT,
|
URL_OUTPUT,
|
||||||
VERSION_OUTPUT,
|
VERSION_OUTPUT,
|
||||||
VERSION_OUTPUT_PROPERTIES,
|
VERSION_OUTPUT_PROPERTIES,
|
||||||
} from '@/tools/confluence/types'
|
} from '@/tools/confluence/types'
|
||||||
import { confluenceUpdateTool } from '@/tools/confluence/update'
|
import { confluenceUpdateTool } from '@/tools/confluence/update'
|
||||||
import { confluenceUpdateBlogPostTool } from '@/tools/confluence/update_blogpost'
|
|
||||||
import { confluenceUpdateCommentTool } from '@/tools/confluence/update_comment'
|
import { confluenceUpdateCommentTool } from '@/tools/confluence/update_comment'
|
||||||
import { confluenceUpdatePagePropertyTool } from '@/tools/confluence/update_page_property'
|
|
||||||
import { confluenceUploadAttachmentTool } from '@/tools/confluence/upload_attachment'
|
import { confluenceUploadAttachmentTool } from '@/tools/confluence/upload_attachment'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -90,14 +78,10 @@ export {
|
|||||||
// Page Properties Tools
|
// Page Properties Tools
|
||||||
confluenceListPagePropertiesTool,
|
confluenceListPagePropertiesTool,
|
||||||
confluenceCreatePagePropertyTool,
|
confluenceCreatePagePropertyTool,
|
||||||
confluenceUpdatePagePropertyTool,
|
|
||||||
confluenceDeletePagePropertyTool,
|
|
||||||
// Blog Post Tools
|
// Blog Post Tools
|
||||||
confluenceListBlogPostsTool,
|
confluenceListBlogPostsTool,
|
||||||
confluenceGetBlogPostTool,
|
confluenceGetBlogPostTool,
|
||||||
confluenceCreateBlogPostTool,
|
confluenceCreateBlogPostTool,
|
||||||
confluenceUpdateBlogPostTool,
|
|
||||||
confluenceDeleteBlogPostTool,
|
|
||||||
confluenceListBlogPostsInSpaceTool,
|
confluenceListBlogPostsInSpaceTool,
|
||||||
// Search Tools
|
// Search Tools
|
||||||
confluenceSearchTool,
|
confluenceSearchTool,
|
||||||
@@ -114,19 +98,14 @@ export {
|
|||||||
// Label Tools
|
// Label Tools
|
||||||
confluenceListLabelsTool,
|
confluenceListLabelsTool,
|
||||||
confluenceAddLabelTool,
|
confluenceAddLabelTool,
|
||||||
confluenceDeleteLabelTool,
|
|
||||||
confluenceGetPagesByLabelTool,
|
|
||||||
confluenceListSpaceLabelsTool,
|
|
||||||
// Space Tools
|
// Space Tools
|
||||||
confluenceGetSpaceTool,
|
confluenceGetSpaceTool,
|
||||||
confluenceListSpacesTool,
|
confluenceListSpacesTool,
|
||||||
// Item property constants (for use in outputs)
|
// Item property constants (for use in outputs)
|
||||||
ATTACHMENT_ITEM_PROPERTIES,
|
ATTACHMENT_ITEM_PROPERTIES,
|
||||||
BLOGPOST_ITEM_PROPERTIES,
|
|
||||||
COMMENT_ITEM_PROPERTIES,
|
COMMENT_ITEM_PROPERTIES,
|
||||||
LABEL_ITEM_PROPERTIES,
|
LABEL_ITEM_PROPERTIES,
|
||||||
PAGE_ITEM_PROPERTIES,
|
PAGE_ITEM_PROPERTIES,
|
||||||
PAGE_PROPERTY_ITEM_PROPERTIES,
|
|
||||||
SEARCH_RESULT_ITEM_PROPERTIES,
|
SEARCH_RESULT_ITEM_PROPERTIES,
|
||||||
SPACE_ITEM_PROPERTIES,
|
SPACE_ITEM_PROPERTIES,
|
||||||
VERSION_OUTPUT_PROPERTIES,
|
VERSION_OUTPUT_PROPERTIES,
|
||||||
@@ -140,8 +119,6 @@ export {
|
|||||||
// Complete output definitions (for use in outputs)
|
// Complete output definitions (for use in outputs)
|
||||||
ATTACHMENT_OUTPUT,
|
ATTACHMENT_OUTPUT,
|
||||||
ATTACHMENTS_OUTPUT,
|
ATTACHMENTS_OUTPUT,
|
||||||
BLOGPOST_OUTPUT,
|
|
||||||
BLOGPOSTS_OUTPUT,
|
|
||||||
COMMENT_OUTPUT,
|
COMMENT_OUTPUT,
|
||||||
COMMENTS_OUTPUT,
|
COMMENTS_OUTPUT,
|
||||||
CONTENT_BODY_OUTPUT,
|
CONTENT_BODY_OUTPUT,
|
||||||
@@ -160,6 +137,5 @@ export {
|
|||||||
PAGE_ID_OUTPUT,
|
PAGE_ID_OUTPUT,
|
||||||
SUCCESS_OUTPUT,
|
SUCCESS_OUTPUT,
|
||||||
DELETED_OUTPUT,
|
DELETED_OUTPUT,
|
||||||
UPDATED_OUTPUT,
|
|
||||||
URL_OUTPUT,
|
URL_OUTPUT,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BLOGPOST_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
import { TIMESTAMP_OUTPUT, VERSION_OUTPUT_PROPERTIES } from '@/tools/confluence/types'
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceListBlogPostsParams {
|
export interface ConfluenceListBlogPostsParams {
|
||||||
@@ -141,7 +141,21 @@ export const confluenceListBlogPostsTool: ToolConfig<
|
|||||||
description: 'Array of blog posts',
|
description: 'Array of blog posts',
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: BLOGPOST_ITEM_PROPERTIES,
|
properties: {
|
||||||
|
id: { type: 'string', description: 'Blog post ID' },
|
||||||
|
title: { type: 'string', description: 'Blog post title' },
|
||||||
|
status: { type: 'string', description: 'Blog post status', optional: true },
|
||||||
|
spaceId: { type: 'string', description: 'Space ID', optional: true },
|
||||||
|
authorId: { type: 'string', description: 'Author account ID', optional: true },
|
||||||
|
createdAt: { type: 'string', description: 'Creation timestamp', optional: true },
|
||||||
|
version: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Version information',
|
||||||
|
properties: VERSION_OUTPUT_PROPERTIES,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
webUrl: { type: 'string', description: 'URL to view the blog post', optional: true },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nextCursor: {
|
nextCursor: {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { BLOGPOST_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
import {
|
||||||
|
CONTENT_BODY_OUTPUT_PROPERTIES,
|
||||||
|
TIMESTAMP_OUTPUT,
|
||||||
|
VERSION_OUTPUT_PROPERTIES,
|
||||||
|
} from '@/tools/confluence/types'
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceListBlogPostsInSpaceParams {
|
export interface ConfluenceListBlogPostsInSpaceParams {
|
||||||
@@ -142,7 +146,27 @@ export const confluenceListBlogPostsInSpaceTool: ToolConfig<
|
|||||||
description: 'Array of blog posts in the space',
|
description: 'Array of blog posts in the space',
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: BLOGPOST_ITEM_PROPERTIES,
|
properties: {
|
||||||
|
id: { type: 'string', description: 'Blog post ID' },
|
||||||
|
title: { type: 'string', description: 'Blog post title' },
|
||||||
|
status: { type: 'string', description: 'Blog post status', optional: true },
|
||||||
|
spaceId: { type: 'string', description: 'Space ID', optional: true },
|
||||||
|
authorId: { type: 'string', description: 'Author account ID', optional: true },
|
||||||
|
createdAt: { type: 'string', description: 'Creation timestamp', optional: true },
|
||||||
|
version: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Version information',
|
||||||
|
properties: VERSION_OUTPUT_PROPERTIES,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Blog post body content',
|
||||||
|
properties: CONTENT_BODY_OUTPUT_PROPERTIES,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
webUrl: { type: 'string', description: 'URL to view the blog post', optional: true },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nextCursor: {
|
nextCursor: {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { LABEL_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
import { LABEL_ITEM_PROPERTIES } from '@/tools/confluence/types'
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceListLabelsParams {
|
export interface ConfluenceListLabelsParams {
|
||||||
@@ -115,7 +115,7 @@ export const confluenceListLabelsTool: ToolConfig<
|
|||||||
},
|
},
|
||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
ts: TIMESTAMP_OUTPUT,
|
ts: { type: 'string', description: 'Timestamp of retrieval' },
|
||||||
labels: {
|
labels: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
description: 'Array of labels on the page',
|
description: 'Array of labels on the page',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PAGE_PROPERTY_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
import { TIMESTAMP_OUTPUT, VERSION_OUTPUT_PROPERTIES } from '@/tools/confluence/types'
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceListPagePropertiesParams {
|
export interface ConfluenceListPagePropertiesParams {
|
||||||
@@ -127,7 +127,17 @@ export const confluenceListPagePropertiesTool: ToolConfig<
|
|||||||
description: 'Array of content properties',
|
description: 'Array of content properties',
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: PAGE_PROPERTY_ITEM_PROPERTIES,
|
properties: {
|
||||||
|
id: { type: 'string', description: 'Property ID' },
|
||||||
|
key: { type: 'string', description: 'Property key' },
|
||||||
|
value: { type: 'json', description: 'Property value (can be any JSON)' },
|
||||||
|
version: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Version information',
|
||||||
|
properties: VERSION_OUTPUT_PROPERTIES,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nextCursor: {
|
nextCursor: {
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
import { LABEL_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceListSpaceLabelsParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
spaceId: string
|
|
||||||
limit?: number
|
|
||||||
cursor?: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceListSpaceLabelsResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
spaceId: string
|
|
||||||
labels: Array<{
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
prefix: string
|
|
||||||
}>
|
|
||||||
nextCursor: string | null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceListSpaceLabelsTool: ToolConfig<
|
|
||||||
ConfluenceListSpaceLabelsParams,
|
|
||||||
ConfluenceListSpaceLabelsResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_list_space_labels',
|
|
||||||
name: 'Confluence List Space Labels',
|
|
||||||
description: 'List all labels associated with a Confluence space.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
spaceId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the Confluence space to list labels from',
|
|
||||||
},
|
|
||||||
limit: {
|
|
||||||
type: 'number',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Maximum number of labels to return (default: 25, max: 250)',
|
|
||||||
},
|
|
||||||
cursor: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Pagination cursor from previous response',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: (params: ConfluenceListSpaceLabelsParams) => {
|
|
||||||
const query = new URLSearchParams({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
spaceId: params.spaceId,
|
|
||||||
limit: String(params.limit || 25),
|
|
||||||
})
|
|
||||||
if (params.cursor) {
|
|
||||||
query.set('cursor', params.cursor)
|
|
||||||
}
|
|
||||||
if (params.cloudId) {
|
|
||||||
query.set('cloudId', params.cloudId)
|
|
||||||
}
|
|
||||||
return `/api/tools/confluence/space-labels?${query.toString()}`
|
|
||||||
},
|
|
||||||
method: 'GET',
|
|
||||||
headers: (params: ConfluenceListSpaceLabelsParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
spaceId: data.spaceId ?? '',
|
|
||||||
labels: data.labels ?? [],
|
|
||||||
nextCursor: data.nextCursor ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
spaceId: { type: 'string', description: 'ID of the space' },
|
|
||||||
labels: {
|
|
||||||
type: 'array',
|
|
||||||
description: 'Array of labels on the space',
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
properties: LABEL_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nextCursor: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Cursor for fetching the next page of results',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import { SPACE_PROPERTY_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceListSpacePropertiesParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
spaceId: string
|
|
||||||
limit?: number
|
|
||||||
cursor?: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceListSpacePropertiesResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
spaceId: string
|
|
||||||
properties: Array<Record<string, unknown>>
|
|
||||||
nextCursor: string | null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceListSpacePropertiesTool: ToolConfig<
|
|
||||||
ConfluenceListSpacePropertiesParams,
|
|
||||||
ConfluenceListSpacePropertiesResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_list_space_properties',
|
|
||||||
name: 'Confluence List Space Properties',
|
|
||||||
description: 'List all content properties on a Confluence space.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
spaceId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the space to list properties from',
|
|
||||||
},
|
|
||||||
limit: {
|
|
||||||
type: 'number',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Maximum number of properties to return (default: 50, max: 250)',
|
|
||||||
},
|
|
||||||
cursor: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Pagination cursor from previous response',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: (params: ConfluenceListSpacePropertiesParams) => {
|
|
||||||
const query = new URLSearchParams({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
spaceId: params.spaceId,
|
|
||||||
limit: String(params.limit || 50),
|
|
||||||
})
|
|
||||||
if (params.cursor) query.set('cursor', params.cursor)
|
|
||||||
if (params.cloudId) query.set('cloudId', params.cloudId)
|
|
||||||
return `/api/tools/confluence/space-properties?${query.toString()}`
|
|
||||||
},
|
|
||||||
method: 'GET',
|
|
||||||
headers: (params: ConfluenceListSpacePropertiesParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
spaceId: data.spaceId ?? '',
|
|
||||||
properties: data.properties ?? [],
|
|
||||||
nextCursor: data.nextCursor ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
spaceId: { type: 'string', description: 'ID of the space' },
|
|
||||||
properties: {
|
|
||||||
type: 'array',
|
|
||||||
description: 'Array of space properties',
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
properties: SPACE_PROPERTY_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nextCursor: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Cursor for fetching the next page of results',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
import { TASK_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceListTasksParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
spaceId?: string
|
|
||||||
pageId?: string
|
|
||||||
status?: string
|
|
||||||
limit?: number
|
|
||||||
cursor?: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceListTasksResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
tasks: Array<Record<string, unknown>>
|
|
||||||
nextCursor: string | null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceListTasksTool: ToolConfig<
|
|
||||||
ConfluenceListTasksParams,
|
|
||||||
ConfluenceListTasksResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_list_tasks',
|
|
||||||
name: 'Confluence List Tasks',
|
|
||||||
description: 'List tasks from Confluence, optionally filtered by space, page, or status.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
spaceId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Filter tasks by space ID',
|
|
||||||
},
|
|
||||||
pageId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Filter tasks by page ID',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Filter tasks by status (complete or incomplete)',
|
|
||||||
},
|
|
||||||
limit: {
|
|
||||||
type: 'number',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Maximum number of tasks to return (default: 50, max: 250)',
|
|
||||||
},
|
|
||||||
cursor: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Pagination cursor from previous response',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: (params: ConfluenceListTasksParams) => {
|
|
||||||
const query = new URLSearchParams({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
limit: String(params.limit || 50),
|
|
||||||
})
|
|
||||||
if (params.spaceId) query.set('spaceId', params.spaceId)
|
|
||||||
if (params.pageId) query.set('pageId', params.pageId)
|
|
||||||
if (params.status) query.set('status', params.status)
|
|
||||||
if (params.cursor) query.set('cursor', params.cursor)
|
|
||||||
if (params.cloudId) query.set('cloudId', params.cloudId)
|
|
||||||
return `/api/tools/confluence/tasks?${query.toString()}`
|
|
||||||
},
|
|
||||||
method: 'GET',
|
|
||||||
headers: (params: ConfluenceListTasksParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
tasks: data.tasks ?? [],
|
|
||||||
nextCursor: data.nextCursor ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
tasks: {
|
|
||||||
type: 'array',
|
|
||||||
description: 'Array of Confluence tasks',
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
properties: TASK_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nextCursor: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Cursor for fetching the next page of results',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { SEARCH_RESULT_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
import { SEARCH_RESULT_ITEM_PROPERTIES } from '@/tools/confluence/types'
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceSearchParams {
|
export interface ConfluenceSearchParams {
|
||||||
@@ -101,7 +101,7 @@ export const confluenceSearchTool: ToolConfig<ConfluenceSearchParams, Confluence
|
|||||||
},
|
},
|
||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
ts: TIMESTAMP_OUTPUT,
|
ts: { type: 'string', description: 'Timestamp of search' },
|
||||||
results: {
|
results: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
description: 'Array of search results',
|
description: 'Array of search results',
|
||||||
|
|||||||
@@ -382,393 +382,6 @@ export const LABELS_OUTPUT: OutputProperty = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Blog post item properties from Confluence API v2.
|
|
||||||
* Based on GET /wiki/api/v2/blogposts response structure.
|
|
||||||
*/
|
|
||||||
export const BLOGPOST_ITEM_PROPERTIES = {
|
|
||||||
id: { type: 'string', description: 'Unique blog post identifier' },
|
|
||||||
title: { type: 'string', description: 'Blog post title' },
|
|
||||||
status: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Blog post status (e.g., current, draft)',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
spaceId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ID of the space containing the blog post',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
authorId: { type: 'string', description: 'Account ID of the blog post author', optional: true },
|
|
||||||
createdAt: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ISO 8601 timestamp when the blog post was created',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
version: {
|
|
||||||
type: 'object',
|
|
||||||
description: 'Blog post version information',
|
|
||||||
properties: VERSION_OUTPUT_PROPERTIES,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
type: 'object',
|
|
||||||
description: 'Blog post body content',
|
|
||||||
properties: CONTENT_BODY_OUTPUT_PROPERTIES,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
webUrl: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'URL to view the blog post in Confluence',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
} as const satisfies Record<string, OutputProperty>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Complete blog post object output definition.
|
|
||||||
*/
|
|
||||||
export const BLOGPOST_OUTPUT: OutputProperty = {
|
|
||||||
type: 'object',
|
|
||||||
description: 'Confluence blog post object',
|
|
||||||
properties: BLOGPOST_ITEM_PROPERTIES,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blog posts array output definition for list endpoints.
|
|
||||||
*/
|
|
||||||
export const BLOGPOSTS_OUTPUT: OutputProperty = {
|
|
||||||
type: 'array',
|
|
||||||
description: 'Array of Confluence blog posts',
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
properties: BLOGPOST_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Page property item properties from Confluence API v2.
|
|
||||||
* Based on GET /wiki/api/v2/pages/{id}/properties response structure.
|
|
||||||
*/
|
|
||||||
export const PAGE_PROPERTY_ITEM_PROPERTIES = {
|
|
||||||
id: { type: 'string', description: 'Unique property identifier' },
|
|
||||||
key: { type: 'string', description: 'Property key/name' },
|
|
||||||
value: { type: 'json', description: 'Property value (can be any JSON)' },
|
|
||||||
version: {
|
|
||||||
type: 'object',
|
|
||||||
description: 'Property version information',
|
|
||||||
properties: VERSION_OUTPUT_PROPERTIES,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
} as const satisfies Record<string, OutputProperty>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Space property item properties from Confluence API v2.
|
|
||||||
* Same shape as page properties (id, key, value, version).
|
|
||||||
*/
|
|
||||||
export const SPACE_PROPERTY_ITEM_PROPERTIES = {
|
|
||||||
...PAGE_PROPERTY_ITEM_PROPERTIES,
|
|
||||||
} as const satisfies Record<string, OutputProperty>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Task item properties from Confluence API v2.
|
|
||||||
* Based on GET /wiki/api/v2/tasks response structure.
|
|
||||||
*/
|
|
||||||
export const TASK_ITEM_PROPERTIES = {
|
|
||||||
id: { type: 'string', description: 'Unique task identifier' },
|
|
||||||
localId: { type: 'string', description: 'Local task identifier within the content' },
|
|
||||||
spaceId: { type: 'string', description: 'ID of the space containing the task', optional: true },
|
|
||||||
pageId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ID of the page containing the task',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
blogPostId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ID of the blog post containing the task',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
status: { type: 'string', description: 'Task status (e.g., complete, incomplete)' },
|
|
||||||
body: {
|
|
||||||
type: 'object',
|
|
||||||
description: 'Task body content',
|
|
||||||
properties: {
|
|
||||||
value: { type: 'string', description: 'Task body text' },
|
|
||||||
representation: { type: 'string', description: 'Content representation format' },
|
|
||||||
},
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
createdBy: { type: 'string', description: 'Account ID of the task creator', optional: true },
|
|
||||||
assignedTo: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Account ID of the assigned user',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
completedBy: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Account ID of the user who completed the task',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ISO 8601 timestamp when the task was created',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ISO 8601 timestamp when the task was last updated',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
dueAt: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ISO 8601 timestamp for the task due date',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
completedAt: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ISO 8601 timestamp when the task was completed',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
} as const satisfies Record<string, OutputProperty>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Complete task object output definition.
|
|
||||||
*/
|
|
||||||
export const TASK_OUTPUT: OutputProperty = {
|
|
||||||
type: 'object',
|
|
||||||
description: 'Confluence task object',
|
|
||||||
properties: TASK_ITEM_PROPERTIES,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tasks array output definition for list endpoints.
|
|
||||||
*/
|
|
||||||
export const TASKS_OUTPUT: OutputProperty = {
|
|
||||||
type: 'array',
|
|
||||||
description: 'Array of Confluence tasks',
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
properties: TASK_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whiteboard item properties from Confluence API v2.
|
|
||||||
* Based on POST /wiki/api/v2/whiteboards response structure.
|
|
||||||
*/
|
|
||||||
export const WHITEBOARD_ITEM_PROPERTIES = {
|
|
||||||
id: { type: 'string', description: 'Unique whiteboard identifier' },
|
|
||||||
title: { type: 'string', description: 'Whiteboard title' },
|
|
||||||
spaceId: { type: 'string', description: 'ID of the space containing the whiteboard' },
|
|
||||||
parentId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ID of the parent content',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
parentType: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Type of the parent content (e.g., page, space, whiteboard)',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
position: {
|
|
||||||
type: 'number',
|
|
||||||
description: 'Position of the whiteboard among siblings',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
authorId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Account ID of the whiteboard creator',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ISO 8601 timestamp when the whiteboard was created',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
version: {
|
|
||||||
type: 'object',
|
|
||||||
description: 'Whiteboard version information',
|
|
||||||
properties: VERSION_OUTPUT_PROPERTIES,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
} as const satisfies Record<string, OutputProperty>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Database item properties from Confluence API v2.
|
|
||||||
* Based on POST /wiki/api/v2/databases response structure.
|
|
||||||
*/
|
|
||||||
export const DATABASE_ITEM_PROPERTIES = {
|
|
||||||
id: { type: 'string', description: 'Unique database identifier' },
|
|
||||||
title: { type: 'string', description: 'Database title' },
|
|
||||||
spaceId: { type: 'string', description: 'ID of the space containing the database' },
|
|
||||||
parentId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ID of the parent content',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
parentType: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Type of the parent content',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
position: {
|
|
||||||
type: 'number',
|
|
||||||
description: 'Position of the database among siblings',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Database status (e.g., current, trashed)',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
authorId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Account ID of the database creator',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ISO 8601 timestamp when the database was created',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
version: {
|
|
||||||
type: 'object',
|
|
||||||
description: 'Database version information',
|
|
||||||
properties: VERSION_OUTPUT_PROPERTIES,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
} as const satisfies Record<string, OutputProperty>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Folder item properties from Confluence API v2.
|
|
||||||
* Based on POST /wiki/api/v2/folders response structure.
|
|
||||||
*/
|
|
||||||
export const FOLDER_ITEM_PROPERTIES = {
|
|
||||||
id: { type: 'string', description: 'Unique folder identifier' },
|
|
||||||
title: { type: 'string', description: 'Folder title' },
|
|
||||||
spaceId: { type: 'string', description: 'ID of the space containing the folder' },
|
|
||||||
parentId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ID of the parent content',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
parentType: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Type of the parent content',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
position: {
|
|
||||||
type: 'number',
|
|
||||||
description: 'Position of the folder among siblings',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Folder status (e.g., current, trashed)',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
authorId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Account ID of the folder creator',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ISO 8601 timestamp when the folder was created',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
version: {
|
|
||||||
type: 'object',
|
|
||||||
description: 'Folder version information',
|
|
||||||
properties: VERSION_OUTPUT_PROPERTIES,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
} as const satisfies Record<string, OutputProperty>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inline comment item properties from Confluence API v2.
|
|
||||||
* Extends COMMENT_ITEM_PROPERTIES with resolution fields.
|
|
||||||
*/
|
|
||||||
export const INLINE_COMMENT_ITEM_PROPERTIES = {
|
|
||||||
...COMMENT_ITEM_PROPERTIES,
|
|
||||||
resolutionStatus: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Resolution status of the inline comment (e.g., open, resolved)',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
resolutionLastModifiedBy: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Account ID of the user who last modified the resolution status',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
resolutionLastModifiedAt: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ISO 8601 timestamp when the resolution was last modified',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
} as const satisfies Record<string, OutputProperty>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Space permission item properties from Confluence API v2.
|
|
||||||
* Based on GET /wiki/api/v2/spaces/{id}/permissions response structure.
|
|
||||||
*/
|
|
||||||
export const SPACE_PERMISSION_ITEM_PROPERTIES = {
|
|
||||||
id: { type: 'string', description: 'Unique permission identifier' },
|
|
||||||
principalType: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Type of the principal (e.g., user, group, role)',
|
|
||||||
},
|
|
||||||
principalId: { type: 'string', description: 'ID of the principal' },
|
|
||||||
operationKey: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Permission operation key (e.g., read, delete, administer)',
|
|
||||||
},
|
|
||||||
operationTargetType: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Target type for the operation (e.g., space, page, blogpost)',
|
|
||||||
},
|
|
||||||
isAnonymous: {
|
|
||||||
type: 'boolean',
|
|
||||||
description: 'Whether this permission applies to anonymous users',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
} as const satisfies Record<string, OutputProperty>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User item properties from Confluence API v2.
|
|
||||||
* Based on GET /wiki/api/v2/users-bulk response structure.
|
|
||||||
*/
|
|
||||||
export const USER_ITEM_PROPERTIES = {
|
|
||||||
accountId: { type: 'string', description: 'Unique Atlassian account identifier' },
|
|
||||||
accountType: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Account type (e.g., atlassian, app, customer)',
|
|
||||||
},
|
|
||||||
accountStatus: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'Account status (e.g., active, inactive, closed)',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
displayName: { type: 'string', description: 'User display name' },
|
|
||||||
publicName: { type: 'string', description: 'User public name', optional: true },
|
|
||||||
email: { type: 'string', description: 'User email address', optional: true },
|
|
||||||
timeZone: { type: 'string', description: 'User time zone', optional: true },
|
|
||||||
personalSpaceId: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'ID of the user personal space',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
isExternalCollaborator: {
|
|
||||||
type: 'boolean',
|
|
||||||
description: 'Whether the user is an external collaborator',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
profilePicture: {
|
|
||||||
type: 'string',
|
|
||||||
description: 'URL to the user profile picture',
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
} as const satisfies Record<string, OutputProperty>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search result space info properties.
|
* Search result space info properties.
|
||||||
* Based on Confluence search API space object in results.
|
* Based on Confluence search API space object in results.
|
||||||
@@ -882,14 +495,6 @@ export const URL_OUTPUT: OutputProperty = {
|
|||||||
description: 'URL to view in Confluence',
|
description: 'URL to view in Confluence',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Common updated status output property.
|
|
||||||
*/
|
|
||||||
export const UPDATED_OUTPUT: OutputProperty = {
|
|
||||||
type: 'boolean',
|
|
||||||
description: 'Update status',
|
|
||||||
}
|
|
||||||
|
|
||||||
// Page operations
|
// Page operations
|
||||||
export interface ConfluenceRetrieveParams {
|
export interface ConfluenceRetrieveParams {
|
||||||
accessToken: string
|
accessToken: string
|
||||||
@@ -1105,149 +710,6 @@ export interface ConfluenceSpaceResponse extends ToolResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blog post update/delete operations
|
|
||||||
export interface ConfluenceUpdateBlogPostParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
blogPostId: string
|
|
||||||
title?: string
|
|
||||||
content?: string
|
|
||||||
status?: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceUpdateBlogPostResponse extends ToolResponse {
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
status: string | null
|
|
||||||
spaceId: string | null
|
|
||||||
authorId: string | null
|
|
||||||
body: Record<string, unknown> | null
|
|
||||||
version: Record<string, unknown> | null
|
|
||||||
webUrl: string | null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceDeleteBlogPostParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
blogPostId: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceDeleteBlogPostResponse extends ToolResponse {
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
blogPostId: string
|
|
||||||
deleted: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Page property update
|
|
||||||
export interface ConfluenceUpdatePagePropertyParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
pageId: string
|
|
||||||
propertyId: string
|
|
||||||
key: string
|
|
||||||
value: unknown
|
|
||||||
versionNumber: number
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceUpdatePagePropertyResponse extends ToolResponse {
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
pageId: string
|
|
||||||
propertyId: string
|
|
||||||
key: string
|
|
||||||
value: unknown
|
|
||||||
version: Record<string, unknown> | null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Task operations
|
|
||||||
export interface ConfluenceTaskResponse extends ToolResponse {
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whiteboard operations
|
|
||||||
export interface ConfluenceWhiteboardResponse extends ToolResponse {
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Database operations
|
|
||||||
export interface ConfluenceDatabaseResponse extends ToolResponse {
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Folder operations
|
|
||||||
export interface ConfluenceFolderResponse extends ToolResponse {
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inline comment operations
|
|
||||||
export interface ConfluenceInlineCommentResponse extends ToolResponse {
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Space permission operations
|
|
||||||
export interface ConfluenceSpacePermissionResponse extends ToolResponse {
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Space property operations
|
|
||||||
export interface ConfluenceSpacePropertyResponse extends ToolResponse {
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// User bulk operations
|
|
||||||
export interface ConfluenceUserBulkResponse extends ToolResponse {
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blog post label operations
|
|
||||||
export interface ConfluenceBlogPostLabelResponse extends ToolResponse {
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blog post version operations
|
|
||||||
export interface ConfluenceBlogPostVersionResponse extends ToolResponse {
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
[key: string]: unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ConfluenceResponse =
|
export type ConfluenceResponse =
|
||||||
| ConfluenceRetrieveResponse
|
| ConfluenceRetrieveResponse
|
||||||
| ConfluenceUpdateResponse
|
| ConfluenceUpdateResponse
|
||||||
@@ -1259,16 +721,3 @@ export type ConfluenceResponse =
|
|||||||
| ConfluenceUploadAttachmentResponse
|
| ConfluenceUploadAttachmentResponse
|
||||||
| ConfluenceLabelResponse
|
| ConfluenceLabelResponse
|
||||||
| ConfluenceSpaceResponse
|
| ConfluenceSpaceResponse
|
||||||
| ConfluenceUpdateBlogPostResponse
|
|
||||||
| ConfluenceDeleteBlogPostResponse
|
|
||||||
| ConfluenceUpdatePagePropertyResponse
|
|
||||||
| ConfluenceTaskResponse
|
|
||||||
| ConfluenceWhiteboardResponse
|
|
||||||
| ConfluenceDatabaseResponse
|
|
||||||
| ConfluenceFolderResponse
|
|
||||||
| ConfluenceInlineCommentResponse
|
|
||||||
| ConfluenceSpacePermissionResponse
|
|
||||||
| ConfluenceSpacePropertyResponse
|
|
||||||
| ConfluenceUserBulkResponse
|
|
||||||
| ConfluenceBlogPostLabelResponse
|
|
||||||
| ConfluenceBlogPostVersionResponse
|
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import type { ConfluenceUpdateParams, ConfluenceUpdateResponse } from '@/tools/confluence/types'
|
import type { ConfluenceUpdateParams, ConfluenceUpdateResponse } from '@/tools/confluence/types'
|
||||||
import {
|
import { CONTENT_BODY_OUTPUT_PROPERTIES, VERSION_OUTPUT_PROPERTIES } from '@/tools/confluence/types'
|
||||||
CONTENT_BODY_OUTPUT_PROPERTIES,
|
|
||||||
SUCCESS_OUTPUT,
|
|
||||||
TIMESTAMP_OUTPUT,
|
|
||||||
VERSION_OUTPUT_PROPERTIES,
|
|
||||||
} from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export const confluenceUpdateTool: ToolConfig<ConfluenceUpdateParams, ConfluenceUpdateResponse> = {
|
export const confluenceUpdateTool: ToolConfig<ConfluenceUpdateParams, ConfluenceUpdateResponse> = {
|
||||||
@@ -117,7 +112,7 @@ export const confluenceUpdateTool: ToolConfig<ConfluenceUpdateParams, Confluence
|
|||||||
},
|
},
|
||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
ts: TIMESTAMP_OUTPUT,
|
ts: { type: 'string', description: 'Timestamp of update' },
|
||||||
pageId: { type: 'string', description: 'Confluence page ID' },
|
pageId: { type: 'string', description: 'Confluence page ID' },
|
||||||
title: { type: 'string', description: 'Updated page title' },
|
title: { type: 'string', description: 'Updated page title' },
|
||||||
status: { type: 'string', description: 'Page status', optional: true },
|
status: { type: 'string', description: 'Page status', optional: true },
|
||||||
@@ -135,6 +130,6 @@ export const confluenceUpdateTool: ToolConfig<ConfluenceUpdateParams, Confluence
|
|||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
url: { type: 'string', description: 'URL to view the page in Confluence', optional: true },
|
url: { type: 'string', description: 'URL to view the page in Confluence', optional: true },
|
||||||
success: SUCCESS_OUTPUT,
|
success: { type: 'boolean', description: 'Update operation success status' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
import type {
|
|
||||||
ConfluenceUpdateBlogPostParams,
|
|
||||||
ConfluenceUpdateBlogPostResponse,
|
|
||||||
} from '@/tools/confluence/types'
|
|
||||||
import { BLOGPOST_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export const confluenceUpdateBlogPostTool: ToolConfig<
|
|
||||||
ConfluenceUpdateBlogPostParams,
|
|
||||||
ConfluenceUpdateBlogPostResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_update_blogpost',
|
|
||||||
name: 'Confluence Update Blog Post',
|
|
||||||
description: 'Update an existing Confluence blog post title, content, or status.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
blogPostId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the blog post to update',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'New title for the blog post',
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'New content for the blog post in Confluence storage format',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Blog post status: current or draft',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: () => '/api/tools/confluence/blogposts',
|
|
||||||
method: 'PUT',
|
|
||||||
headers: (params: ConfluenceUpdateBlogPostParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: ConfluenceUpdateBlogPostParams) => ({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
blogPostId: params.blogPostId?.trim(),
|
|
||||||
title: params.title,
|
|
||||||
content: params.content,
|
|
||||||
status: params.status,
|
|
||||||
cloudId: params.cloudId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
id: data.id ?? '',
|
|
||||||
title: data.title ?? '',
|
|
||||||
status: data.status ?? null,
|
|
||||||
spaceId: data.spaceId ?? null,
|
|
||||||
authorId: data.authorId ?? null,
|
|
||||||
body: data.body ?? null,
|
|
||||||
version: data.version ?? null,
|
|
||||||
webUrl: data.webUrl ?? data._links?.webui ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
...BLOGPOST_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { TIMESTAMP_OUTPUT, UPDATED_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceUpdateCommentParams {
|
export interface ConfluenceUpdateCommentParams {
|
||||||
@@ -100,8 +99,8 @@ export const confluenceUpdateCommentTool: ToolConfig<
|
|||||||
},
|
},
|
||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
ts: TIMESTAMP_OUTPUT,
|
ts: { type: 'string', description: 'Timestamp of update' },
|
||||||
commentId: { type: 'string', description: 'Updated comment ID' },
|
commentId: { type: 'string', description: 'Updated comment ID' },
|
||||||
updated: UPDATED_OUTPUT,
|
updated: { type: 'boolean', description: 'Update status' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
import type {
|
|
||||||
ConfluenceUpdatePagePropertyParams,
|
|
||||||
ConfluenceUpdatePagePropertyResponse,
|
|
||||||
} from '@/tools/confluence/types'
|
|
||||||
import { PAGE_PROPERTY_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export const confluenceUpdatePagePropertyTool: ToolConfig<
|
|
||||||
ConfluenceUpdatePagePropertyParams,
|
|
||||||
ConfluenceUpdatePagePropertyResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_update_page_property',
|
|
||||||
name: 'Confluence Update Page Property',
|
|
||||||
description: 'Update an existing content property on a Confluence page.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
pageId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the page containing the property',
|
|
||||||
},
|
|
||||||
propertyId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the property to update',
|
|
||||||
},
|
|
||||||
key: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The key/name of the property',
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: 'json',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The new value for the property (can be any JSON value)',
|
|
||||||
},
|
|
||||||
versionNumber: {
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The current version number of the property (for conflict prevention)',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: () => '/api/tools/confluence/page-properties',
|
|
||||||
method: 'PUT',
|
|
||||||
headers: (params: ConfluenceUpdatePagePropertyParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: ConfluenceUpdatePagePropertyParams) => ({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
pageId: params.pageId?.trim(),
|
|
||||||
propertyId: params.propertyId?.trim(),
|
|
||||||
key: params.key,
|
|
||||||
value: params.value,
|
|
||||||
versionNumber: Number(params.versionNumber),
|
|
||||||
cloudId: params.cloudId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
pageId: data.pageId ?? '',
|
|
||||||
propertyId: data.id ?? '',
|
|
||||||
key: data.key ?? '',
|
|
||||||
value: data.value ?? null,
|
|
||||||
version: data.version ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
pageId: { type: 'string', description: 'ID of the page' },
|
|
||||||
propertyId: { type: 'string', description: 'ID of the updated property' },
|
|
||||||
...PAGE_PROPERTY_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
import { TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceUpdateSpaceParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
spaceId: string
|
|
||||||
name?: string
|
|
||||||
description?: string
|
|
||||||
status?: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceUpdateSpaceResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
id: string
|
|
||||||
key: string
|
|
||||||
name: string
|
|
||||||
type: string
|
|
||||||
status: string
|
|
||||||
updated: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceUpdateSpaceTool: ToolConfig<
|
|
||||||
ConfluenceUpdateSpaceParams,
|
|
||||||
ConfluenceUpdateSpaceResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_update_space',
|
|
||||||
name: 'Confluence Update Space',
|
|
||||||
description: 'Update an existing Confluence space (name, description, or status).',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
spaceId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the space to update',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'New name for the space',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'New description for the space',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'New status for the space (current or archived)',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: () => '/api/tools/confluence/spaces',
|
|
||||||
method: 'PUT',
|
|
||||||
headers: (params: ConfluenceUpdateSpaceParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: ConfluenceUpdateSpaceParams) => ({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
spaceId: params.spaceId?.trim(),
|
|
||||||
name: params.name,
|
|
||||||
description: params.description,
|
|
||||||
status: params.status,
|
|
||||||
cloudId: params.cloudId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
id: data.id ?? '',
|
|
||||||
key: data.key ?? '',
|
|
||||||
name: data.name ?? '',
|
|
||||||
type: data.type ?? '',
|
|
||||||
status: data.status ?? '',
|
|
||||||
updated: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
id: { type: 'string', description: 'ID of the updated space' },
|
|
||||||
key: { type: 'string', description: 'Key of the updated space' },
|
|
||||||
name: { type: 'string', description: 'Name of the updated space' },
|
|
||||||
type: { type: 'string', description: 'Type of the space' },
|
|
||||||
status: { type: 'string', description: 'Status of the space' },
|
|
||||||
updated: { type: 'boolean', description: 'Update status' },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
import { SPACE_PROPERTY_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceUpdateSpacePropertyParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
spaceId: string
|
|
||||||
propertyId: string
|
|
||||||
key: string
|
|
||||||
value: unknown
|
|
||||||
versionNumber: number
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceUpdateSpacePropertyResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
spaceId: string
|
|
||||||
propertyId: string
|
|
||||||
key: string
|
|
||||||
value: unknown
|
|
||||||
version: Record<string, unknown> | null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceUpdateSpacePropertyTool: ToolConfig<
|
|
||||||
ConfluenceUpdateSpacePropertyParams,
|
|
||||||
ConfluenceUpdateSpacePropertyResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_update_space_property',
|
|
||||||
name: 'Confluence Update Space Property',
|
|
||||||
description: 'Update a content property on a Confluence space.',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
spaceId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the space containing the property',
|
|
||||||
},
|
|
||||||
propertyId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the property to update',
|
|
||||||
},
|
|
||||||
key: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The key/name for the property',
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: 'json',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The new value for the property (can be any JSON value)',
|
|
||||||
},
|
|
||||||
versionNumber: {
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Current version number of the property (for optimistic locking)',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: () => '/api/tools/confluence/space-properties',
|
|
||||||
method: 'PUT',
|
|
||||||
headers: (params: ConfluenceUpdateSpacePropertyParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: ConfluenceUpdateSpacePropertyParams) => ({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
spaceId: params.spaceId?.trim(),
|
|
||||||
propertyId: params.propertyId?.trim(),
|
|
||||||
key: params.key,
|
|
||||||
value: params.value,
|
|
||||||
versionNumber: params.versionNumber,
|
|
||||||
cloudId: params.cloudId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
spaceId: data.spaceId ?? '',
|
|
||||||
propertyId: data.id ?? '',
|
|
||||||
key: data.key ?? '',
|
|
||||||
value: data.value ?? null,
|
|
||||||
version: data.version ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
spaceId: { type: 'string', description: 'ID of the space' },
|
|
||||||
propertyId: { type: 'string', description: 'ID of the updated property' },
|
|
||||||
...SPACE_PROPERTY_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
import { TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export interface ConfluenceUpdateTaskParams {
|
|
||||||
accessToken: string
|
|
||||||
domain: string
|
|
||||||
taskId: string
|
|
||||||
status: string
|
|
||||||
cloudId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfluenceUpdateTaskResponse {
|
|
||||||
success: boolean
|
|
||||||
output: {
|
|
||||||
ts: string
|
|
||||||
id: string
|
|
||||||
status: string
|
|
||||||
updated: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const confluenceUpdateTaskTool: ToolConfig<
|
|
||||||
ConfluenceUpdateTaskParams,
|
|
||||||
ConfluenceUpdateTaskResponse
|
|
||||||
> = {
|
|
||||||
id: 'confluence_update_task',
|
|
||||||
name: 'Confluence Update Task',
|
|
||||||
description: 'Update the status of a Confluence task (mark as complete or incomplete).',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'confluence',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Confluence',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Confluence domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
taskId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'The ID of the task to update',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'New status for the task (complete or incomplete)',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description:
|
|
||||||
'Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: () => '/api/tools/confluence/tasks',
|
|
||||||
method: 'PUT',
|
|
||||||
headers: (params: ConfluenceUpdateTaskParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: ConfluenceUpdateTaskParams) => ({
|
|
||||||
domain: params.domain,
|
|
||||||
accessToken: params.accessToken,
|
|
||||||
taskId: params.taskId?.trim(),
|
|
||||||
status: params.status,
|
|
||||||
cloudId: params.cloudId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response) => {
|
|
||||||
const data = await response.json()
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
id: data.id ?? '',
|
|
||||||
status: data.status ?? '',
|
|
||||||
updated: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
id: { type: 'string', description: 'ID of the updated task' },
|
|
||||||
status: { type: 'string', description: 'Updated task status' },
|
|
||||||
updated: { type: 'boolean', description: 'Update status' },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
import type { ToolConfig } from '@/tools/types'
|
||||||
|
|
||||||
export interface ConfluenceUploadAttachmentParams {
|
export interface ConfluenceUploadAttachmentParams {
|
||||||
@@ -124,7 +123,7 @@ export const confluenceUploadAttachmentTool: ToolConfig<
|
|||||||
},
|
},
|
||||||
|
|
||||||
outputs: {
|
outputs: {
|
||||||
ts: TIMESTAMP_OUTPUT,
|
ts: { type: 'string', description: 'Timestamp of upload' },
|
||||||
attachmentId: { type: 'string', description: 'Uploaded attachment ID' },
|
attachmentId: { type: 'string', description: 'Uploaded attachment ID' },
|
||||||
title: { type: 'string', description: 'Attachment file name' },
|
title: { type: 'string', description: 'Attachment file name' },
|
||||||
fileSize: { type: 'number', description: 'File size in bytes' },
|
fileSize: { type: 'number', description: 'File size in bytes' },
|
||||||
|
|||||||
@@ -1,171 +0,0 @@
|
|||||||
import type { JiraCreateComponentParams, JiraCreateComponentResponse } from '@/tools/jira/types'
|
|
||||||
import {
|
|
||||||
COMPONENT_DETAIL_ITEM_PROPERTIES,
|
|
||||||
SUCCESS_OUTPUT,
|
|
||||||
TIMESTAMP_OUTPUT,
|
|
||||||
} from '@/tools/jira/types'
|
|
||||||
import { getJiraCloudId, transformUser } from '@/tools/jira/utils'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export const jiraCreateComponentTool: ToolConfig<
|
|
||||||
JiraCreateComponentParams,
|
|
||||||
JiraCreateComponentResponse
|
|
||||||
> = {
|
|
||||||
id: 'jira_create_component',
|
|
||||||
name: 'Jira Create Component',
|
|
||||||
description: 'Create a new component in a Jira project',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'jira',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Jira',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Component name',
|
|
||||||
},
|
|
||||||
project: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Project key (e.g., PROJ)',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Component description',
|
|
||||||
},
|
|
||||||
leadAccountId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Account ID of the component lead',
|
|
||||||
},
|
|
||||||
assigneeType: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description:
|
|
||||||
'Default assignee type: PROJECT_DEFAULT, COMPONENT_LEAD, PROJECT_LEAD, or UNASSIGNED',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description:
|
|
||||||
'Jira Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: (params: JiraCreateComponentParams) => {
|
|
||||||
if (params.cloudId) {
|
|
||||||
return `https://api.atlassian.com/ex/jira/${params.cloudId}/rest/api/3/component`
|
|
||||||
}
|
|
||||||
return 'https://api.atlassian.com/oauth/token/accessible-resources'
|
|
||||||
},
|
|
||||||
method: (params: JiraCreateComponentParams) => (params.cloudId ? 'POST' : 'GET'),
|
|
||||||
headers: (params: JiraCreateComponentParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: JiraCreateComponentParams) => {
|
|
||||||
if (!params.cloudId) return undefined as any
|
|
||||||
const body: Record<string, unknown> = {
|
|
||||||
name: params.name,
|
|
||||||
project: params.project.trim(),
|
|
||||||
}
|
|
||||||
if (params.description) body.description = params.description
|
|
||||||
if (params.leadAccountId) body.leadAccountId = params.leadAccountId.trim()
|
|
||||||
if (params.assigneeType) body.assigneeType = params.assigneeType
|
|
||||||
return body
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response, params?: JiraCreateComponentParams) => {
|
|
||||||
const createComponent = async (cloudId: string) => {
|
|
||||||
const body: Record<string, unknown> = {
|
|
||||||
name: params!.name,
|
|
||||||
project: params!.project.trim(),
|
|
||||||
}
|
|
||||||
if (params?.description) body.description = params.description
|
|
||||||
if (params?.leadAccountId) body.leadAccountId = params.leadAccountId.trim()
|
|
||||||
if (params?.assigneeType) body.assigneeType = params.assigneeType
|
|
||||||
|
|
||||||
const res = await fetch(`https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/component`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params!.accessToken}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
})
|
|
||||||
if (!res.ok) {
|
|
||||||
let message = `Failed to create component (${res.status})`
|
|
||||||
try {
|
|
||||||
const err = await res.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
return res.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
let data: any
|
|
||||||
if (!params?.cloudId) {
|
|
||||||
const cloudId = await getJiraCloudId(params!.domain, params!.accessToken)
|
|
||||||
data = await createComponent(cloudId)
|
|
||||||
} else {
|
|
||||||
if (!response.ok) {
|
|
||||||
let message = `Failed to create component (${response.status})`
|
|
||||||
try {
|
|
||||||
const err = await response.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
data = await response.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
id: data.id ?? '',
|
|
||||||
name: data.name ?? '',
|
|
||||||
description: data.description ?? null,
|
|
||||||
lead: transformUser(data.lead),
|
|
||||||
assigneeType: data.assigneeType ?? null,
|
|
||||||
project: data.project ?? null,
|
|
||||||
projectId: data.projectId ?? null,
|
|
||||||
self: data.self ?? '',
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
success: SUCCESS_OUTPUT,
|
|
||||||
...COMPONENT_DETAIL_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
import type { JiraCreateSprintParams, JiraCreateSprintResponse } from '@/tools/jira/types'
|
|
||||||
import { SPRINT_ITEM_PROPERTIES, SUCCESS_OUTPUT, TIMESTAMP_OUTPUT } from '@/tools/jira/types'
|
|
||||||
import { getJiraCloudId } from '@/tools/jira/utils'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export const jiraCreateSprintTool: ToolConfig<JiraCreateSprintParams, JiraCreateSprintResponse> = {
|
|
||||||
id: 'jira_create_sprint',
|
|
||||||
name: 'Jira Create Sprint',
|
|
||||||
description: 'Create a new sprint in a Jira board',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'jira',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Jira',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Sprint name',
|
|
||||||
},
|
|
||||||
boardId: {
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Board ID to create the sprint in',
|
|
||||||
},
|
|
||||||
goal: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Sprint goal',
|
|
||||||
},
|
|
||||||
startDate: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Sprint start date (ISO 8601 format)',
|
|
||||||
},
|
|
||||||
endDate: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Sprint end date (ISO 8601 format)',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description:
|
|
||||||
'Jira Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: (params: JiraCreateSprintParams) => {
|
|
||||||
if (params.cloudId) {
|
|
||||||
return `https://api.atlassian.com/ex/jira/${params.cloudId}/rest/agile/1.0/sprint`
|
|
||||||
}
|
|
||||||
return 'https://api.atlassian.com/oauth/token/accessible-resources'
|
|
||||||
},
|
|
||||||
method: (params: JiraCreateSprintParams) => (params.cloudId ? 'POST' : 'GET'),
|
|
||||||
headers: (params: JiraCreateSprintParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: JiraCreateSprintParams) => {
|
|
||||||
if (!params.cloudId) return undefined as any
|
|
||||||
const body: Record<string, unknown> = {
|
|
||||||
name: params.name,
|
|
||||||
originBoardId: params.boardId,
|
|
||||||
}
|
|
||||||
if (params.goal) body.goal = params.goal
|
|
||||||
if (params.startDate) body.startDate = params.startDate
|
|
||||||
if (params.endDate) body.endDate = params.endDate
|
|
||||||
return body
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response, params?: JiraCreateSprintParams) => {
|
|
||||||
const createSprint = async (cloudId: string) => {
|
|
||||||
const body: Record<string, unknown> = {
|
|
||||||
name: params!.name,
|
|
||||||
originBoardId: params!.boardId,
|
|
||||||
}
|
|
||||||
if (params?.goal) body.goal = params.goal
|
|
||||||
if (params?.startDate) body.startDate = params.startDate
|
|
||||||
if (params?.endDate) body.endDate = params.endDate
|
|
||||||
|
|
||||||
const res = await fetch(
|
|
||||||
`https://api.atlassian.com/ex/jira/${cloudId}/rest/agile/1.0/sprint`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params!.accessToken}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (!res.ok) {
|
|
||||||
let message = `Failed to create sprint (${res.status})`
|
|
||||||
try {
|
|
||||||
const err = await res.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
return res.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
let data: any
|
|
||||||
if (!params?.cloudId) {
|
|
||||||
const cloudId = await getJiraCloudId(params!.domain, params!.accessToken)
|
|
||||||
data = await createSprint(cloudId)
|
|
||||||
} else {
|
|
||||||
if (!response.ok) {
|
|
||||||
let message = `Failed to create sprint (${response.status})`
|
|
||||||
try {
|
|
||||||
const err = await response.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
data = await response.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
id: data.id ?? 0,
|
|
||||||
name: data.name ?? '',
|
|
||||||
state: data.state ?? '',
|
|
||||||
startDate: data.startDate ?? null,
|
|
||||||
endDate: data.endDate ?? null,
|
|
||||||
completeDate: data.completeDate ?? null,
|
|
||||||
goal: data.goal ?? null,
|
|
||||||
boardId: data.originBoardId ?? null,
|
|
||||||
self: data.self ?? '',
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
success: SUCCESS_OUTPUT,
|
|
||||||
...SPRINT_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
import type { JiraCreateVersionParams, JiraCreateVersionResponse } from '@/tools/jira/types'
|
|
||||||
import {
|
|
||||||
SUCCESS_OUTPUT,
|
|
||||||
TIMESTAMP_OUTPUT,
|
|
||||||
VERSION_DETAIL_ITEM_PROPERTIES,
|
|
||||||
} from '@/tools/jira/types'
|
|
||||||
import { getJiraCloudId } from '@/tools/jira/utils'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export const jiraCreateVersionTool: ToolConfig<JiraCreateVersionParams, JiraCreateVersionResponse> =
|
|
||||||
{
|
|
||||||
id: 'jira_create_version',
|
|
||||||
name: 'Jira Create Version',
|
|
||||||
description: 'Create a new version/release in a Jira project',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'jira',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Jira',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Version name (e.g., 1.0.0, Sprint 5)',
|
|
||||||
},
|
|
||||||
projectId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Project ID to create the version in',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Version description',
|
|
||||||
},
|
|
||||||
startDate: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Start date (YYYY-MM-DD)',
|
|
||||||
},
|
|
||||||
releaseDate: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Release date (YYYY-MM-DD)',
|
|
||||||
},
|
|
||||||
released: {
|
|
||||||
type: 'boolean',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Whether the version is released (default: false)',
|
|
||||||
},
|
|
||||||
archived: {
|
|
||||||
type: 'boolean',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Whether the version is archived (default: false)',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description:
|
|
||||||
'Jira Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: (params: JiraCreateVersionParams) => {
|
|
||||||
if (params.cloudId) {
|
|
||||||
return `https://api.atlassian.com/ex/jira/${params.cloudId}/rest/api/3/version`
|
|
||||||
}
|
|
||||||
return 'https://api.atlassian.com/oauth/token/accessible-resources'
|
|
||||||
},
|
|
||||||
method: (params: JiraCreateVersionParams) => (params.cloudId ? 'POST' : 'GET'),
|
|
||||||
headers: (params: JiraCreateVersionParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: JiraCreateVersionParams) => {
|
|
||||||
if (!params.cloudId) return undefined as any
|
|
||||||
const body: Record<string, unknown> = {
|
|
||||||
name: params.name,
|
|
||||||
projectId: Number(params.projectId.trim()),
|
|
||||||
}
|
|
||||||
if (params.description) body.description = params.description
|
|
||||||
if (params.startDate) body.startDate = params.startDate
|
|
||||||
if (params.releaseDate) body.releaseDate = params.releaseDate
|
|
||||||
if (params.released !== undefined) body.released = params.released
|
|
||||||
if (params.archived !== undefined) body.archived = params.archived
|
|
||||||
return body
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response, params?: JiraCreateVersionParams) => {
|
|
||||||
const createVersion = async (cloudId: string) => {
|
|
||||||
const body: Record<string, unknown> = {
|
|
||||||
name: params!.name,
|
|
||||||
projectId: Number(params!.projectId.trim()),
|
|
||||||
}
|
|
||||||
if (params?.description) body.description = params.description
|
|
||||||
if (params?.startDate) body.startDate = params.startDate
|
|
||||||
if (params?.releaseDate) body.releaseDate = params.releaseDate
|
|
||||||
if (params?.released !== undefined) body.released = params.released
|
|
||||||
if (params?.archived !== undefined) body.archived = params.archived
|
|
||||||
|
|
||||||
const res = await fetch(`https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/version`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params!.accessToken}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
})
|
|
||||||
if (!res.ok) {
|
|
||||||
let message = `Failed to create version (${res.status})`
|
|
||||||
try {
|
|
||||||
const err = await res.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
return res.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
let data: any
|
|
||||||
if (!params?.cloudId) {
|
|
||||||
const cloudId = await getJiraCloudId(params!.domain, params!.accessToken)
|
|
||||||
data = await createVersion(cloudId)
|
|
||||||
} else {
|
|
||||||
if (!response.ok) {
|
|
||||||
let message = `Failed to create version (${response.status})`
|
|
||||||
try {
|
|
||||||
const err = await response.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
data = await response.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
id: data.id ?? '',
|
|
||||||
name: data.name ?? '',
|
|
||||||
description: data.description ?? null,
|
|
||||||
released: data.released ?? false,
|
|
||||||
archived: data.archived ?? false,
|
|
||||||
startDate: data.startDate ?? null,
|
|
||||||
releaseDate: data.releaseDate ?? null,
|
|
||||||
overdue: data.overdue ?? null,
|
|
||||||
projectId: data.projectId ?? null,
|
|
||||||
self: data.self ?? '',
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
success: SUCCESS_OUTPUT,
|
|
||||||
...VERSION_DETAIL_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
import type { JiraDeleteComponentParams, JiraDeleteComponentResponse } from '@/tools/jira/types'
|
|
||||||
import { SUCCESS_OUTPUT, TIMESTAMP_OUTPUT } from '@/tools/jira/types'
|
|
||||||
import { getJiraCloudId } from '@/tools/jira/utils'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export const jiraDeleteComponentTool: ToolConfig<
|
|
||||||
JiraDeleteComponentParams,
|
|
||||||
JiraDeleteComponentResponse
|
|
||||||
> = {
|
|
||||||
id: 'jira_delete_component',
|
|
||||||
name: 'Jira Delete Component',
|
|
||||||
description: 'Delete a component from a Jira project',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'jira',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Jira',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
componentId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Component ID to delete',
|
|
||||||
},
|
|
||||||
moveIssuesTo: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Component ID to reassign issues to (optional)',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description:
|
|
||||||
'Jira Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: (params: JiraDeleteComponentParams) => {
|
|
||||||
if (params.cloudId) {
|
|
||||||
let url = `https://api.atlassian.com/ex/jira/${params.cloudId}/rest/api/3/component/${params.componentId.trim()}`
|
|
||||||
if (params.moveIssuesTo)
|
|
||||||
url += `?moveIssuesTo=${encodeURIComponent(params.moveIssuesTo.trim())}`
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
return 'https://api.atlassian.com/oauth/token/accessible-resources'
|
|
||||||
},
|
|
||||||
method: (params: JiraDeleteComponentParams) => (params.cloudId ? 'DELETE' : 'GET'),
|
|
||||||
headers: (params: JiraDeleteComponentParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response, params?: JiraDeleteComponentParams) => {
|
|
||||||
if (!params?.cloudId) {
|
|
||||||
const cloudId = await getJiraCloudId(params!.domain, params!.accessToken)
|
|
||||||
let url = `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/component/${params!.componentId.trim()}`
|
|
||||||
if (params?.moveIssuesTo)
|
|
||||||
url += `?moveIssuesTo=${encodeURIComponent(params.moveIssuesTo.trim())}`
|
|
||||||
const deleteResponse = await fetch(url, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params!.accessToken}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (deleteResponse.status !== 204 && !deleteResponse.ok) {
|
|
||||||
let message = `Failed to delete component (${deleteResponse.status})`
|
|
||||||
try {
|
|
||||||
const err = await deleteResponse.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
componentId: params!.componentId,
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status !== 204 && !response.ok) {
|
|
||||||
let message = `Failed to delete component (${response.status})`
|
|
||||||
try {
|
|
||||||
const err = await response.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
componentId: params?.componentId ?? 'unknown',
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
success: SUCCESS_OUTPUT,
|
|
||||||
componentId: { type: 'string', description: 'Deleted component ID' },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import type { JiraDeleteSprintParams, JiraDeleteSprintResponse } from '@/tools/jira/types'
|
|
||||||
import { SUCCESS_OUTPUT, TIMESTAMP_OUTPUT } from '@/tools/jira/types'
|
|
||||||
import { getJiraCloudId } from '@/tools/jira/utils'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export const jiraDeleteSprintTool: ToolConfig<JiraDeleteSprintParams, JiraDeleteSprintResponse> = {
|
|
||||||
id: 'jira_delete_sprint',
|
|
||||||
name: 'Jira Delete Sprint',
|
|
||||||
description: 'Delete a sprint from a Jira board',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'jira',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Jira',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
sprintId: {
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Sprint ID to delete',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description:
|
|
||||||
'Jira Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: (params: JiraDeleteSprintParams) => {
|
|
||||||
if (params.cloudId) {
|
|
||||||
return `https://api.atlassian.com/ex/jira/${params.cloudId}/rest/agile/1.0/sprint/${params.sprintId}`
|
|
||||||
}
|
|
||||||
return 'https://api.atlassian.com/oauth/token/accessible-resources'
|
|
||||||
},
|
|
||||||
method: (params: JiraDeleteSprintParams) => (params.cloudId ? 'DELETE' : 'GET'),
|
|
||||||
headers: (params: JiraDeleteSprintParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response, params?: JiraDeleteSprintParams) => {
|
|
||||||
if (!params?.cloudId) {
|
|
||||||
const cloudId = await getJiraCloudId(params!.domain, params!.accessToken)
|
|
||||||
const deleteResponse = await fetch(
|
|
||||||
`https://api.atlassian.com/ex/jira/${cloudId}/rest/agile/1.0/sprint/${params!.sprintId}`,
|
|
||||||
{
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params!.accessToken}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (deleteResponse.status !== 204 && !deleteResponse.ok) {
|
|
||||||
let message = `Failed to delete sprint (${deleteResponse.status})`
|
|
||||||
try {
|
|
||||||
const err = await deleteResponse.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
sprintId: params!.sprintId,
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status !== 204 && !response.ok) {
|
|
||||||
let message = `Failed to delete sprint (${response.status})`
|
|
||||||
try {
|
|
||||||
const err = await response.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
sprintId: params?.sprintId ?? 0,
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
success: SUCCESS_OUTPUT,
|
|
||||||
sprintId: { type: 'number', description: 'Deleted sprint ID' },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
import type { JiraDeleteVersionParams, JiraDeleteVersionResponse } from '@/tools/jira/types'
|
|
||||||
import { SUCCESS_OUTPUT, TIMESTAMP_OUTPUT } from '@/tools/jira/types'
|
|
||||||
import { getJiraCloudId } from '@/tools/jira/utils'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export const jiraDeleteVersionTool: ToolConfig<JiraDeleteVersionParams, JiraDeleteVersionResponse> =
|
|
||||||
{
|
|
||||||
id: 'jira_delete_version',
|
|
||||||
name: 'Jira Delete Version',
|
|
||||||
description: 'Delete a version/release from a Jira project',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'jira',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Jira',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
versionId: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Version ID to delete',
|
|
||||||
},
|
|
||||||
moveFixIssuesTo: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Version ID to move fix version issues to (optional)',
|
|
||||||
},
|
|
||||||
moveAffectedIssuesTo: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Version ID to move affected version issues to (optional)',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description:
|
|
||||||
'Jira Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: (params: JiraDeleteVersionParams) => {
|
|
||||||
if (params.cloudId) {
|
|
||||||
return `https://api.atlassian.com/ex/jira/${params.cloudId}/rest/api/3/version/${params.versionId.trim()}/removeAndSwap`
|
|
||||||
}
|
|
||||||
return 'https://api.atlassian.com/oauth/token/accessible-resources'
|
|
||||||
},
|
|
||||||
method: (params: JiraDeleteVersionParams) => (params.cloudId ? 'POST' : 'GET'),
|
|
||||||
headers: (params: JiraDeleteVersionParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
body: (params: JiraDeleteVersionParams) => {
|
|
||||||
if (!params.cloudId) return undefined as any
|
|
||||||
const body: Record<string, unknown> = {}
|
|
||||||
if (params.moveFixIssuesTo) body.moveFixIssuesTo = Number(params.moveFixIssuesTo.trim())
|
|
||||||
if (params.moveAffectedIssuesTo)
|
|
||||||
body.moveAffectedIssuesTo = Number(params.moveAffectedIssuesTo.trim())
|
|
||||||
return body
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response, params?: JiraDeleteVersionParams) => {
|
|
||||||
if (!params?.cloudId) {
|
|
||||||
const cloudId = await getJiraCloudId(params!.domain, params!.accessToken)
|
|
||||||
const body: Record<string, unknown> = {}
|
|
||||||
if (params?.moveFixIssuesTo) body.moveFixIssuesTo = Number(params.moveFixIssuesTo.trim())
|
|
||||||
if (params?.moveAffectedIssuesTo)
|
|
||||||
body.moveAffectedIssuesTo = Number(params.moveAffectedIssuesTo.trim())
|
|
||||||
|
|
||||||
const deleteResponse = await fetch(
|
|
||||||
`https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/version/${params!.versionId.trim()}/removeAndSwap`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${params!.accessToken}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (deleteResponse.status !== 204 && !deleteResponse.ok) {
|
|
||||||
let message = `Failed to delete version (${deleteResponse.status})`
|
|
||||||
try {
|
|
||||||
const err = await deleteResponse.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
versionId: params!.versionId,
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.status !== 204 && !response.ok) {
|
|
||||||
let message = `Failed to delete version (${response.status})`
|
|
||||||
try {
|
|
||||||
const err = await response.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
versionId: params?.versionId ?? 'unknown',
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
success: SUCCESS_OUTPUT,
|
|
||||||
versionId: { type: 'string', description: 'Deleted version ID' },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
import type { JiraGetBoardSprintsParams, JiraGetBoardSprintsResponse } from '@/tools/jira/types'
|
|
||||||
import { SPRINT_ITEM_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/jira/types'
|
|
||||||
import { getJiraCloudId } from '@/tools/jira/utils'
|
|
||||||
import type { ToolConfig } from '@/tools/types'
|
|
||||||
|
|
||||||
export const jiraGetBoardSprintsTool: ToolConfig<
|
|
||||||
JiraGetBoardSprintsParams,
|
|
||||||
JiraGetBoardSprintsResponse
|
|
||||||
> = {
|
|
||||||
id: 'jira_get_board_sprints',
|
|
||||||
name: 'Jira Get Board Sprints',
|
|
||||||
description: 'Get all sprints for a Jira board',
|
|
||||||
version: '1.0.0',
|
|
||||||
|
|
||||||
oauth: {
|
|
||||||
required: true,
|
|
||||||
provider: 'jira',
|
|
||||||
},
|
|
||||||
|
|
||||||
params: {
|
|
||||||
accessToken: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description: 'OAuth access token for Jira',
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-only',
|
|
||||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
|
||||||
},
|
|
||||||
boardId: {
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Board ID to get sprints from',
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Filter by sprint state: active, closed, future. Comma-separated for multiple.',
|
|
||||||
},
|
|
||||||
startAt: {
|
|
||||||
type: 'number',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Index of the first sprint to return (default: 0)',
|
|
||||||
},
|
|
||||||
maxResults: {
|
|
||||||
type: 'number',
|
|
||||||
required: false,
|
|
||||||
visibility: 'user-or-llm',
|
|
||||||
description: 'Maximum number of sprints to return (default: 50)',
|
|
||||||
},
|
|
||||||
cloudId: {
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
visibility: 'hidden',
|
|
||||||
description:
|
|
||||||
'Jira Cloud ID for the instance. If not provided, it will be fetched using the domain.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
request: {
|
|
||||||
url: (params: JiraGetBoardSprintsParams) => {
|
|
||||||
if (params.cloudId) {
|
|
||||||
const startAt = params.startAt ?? 0
|
|
||||||
const maxResults = params.maxResults ?? 50
|
|
||||||
let url = `https://api.atlassian.com/ex/jira/${params.cloudId}/rest/agile/1.0/board/${params.boardId}/sprint?startAt=${startAt}&maxResults=${maxResults}`
|
|
||||||
if (params.state) url += `&state=${encodeURIComponent(params.state)}`
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
return 'https://api.atlassian.com/oauth/token/accessible-resources'
|
|
||||||
},
|
|
||||||
method: 'GET',
|
|
||||||
headers: (params: JiraGetBoardSprintsParams) => ({
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params.accessToken}`,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
transformResponse: async (response: Response, params?: JiraGetBoardSprintsParams) => {
|
|
||||||
const fetchSprints = async (cloudId: string) => {
|
|
||||||
const startAt = params?.startAt ?? 0
|
|
||||||
const maxResults = params?.maxResults ?? 50
|
|
||||||
let url = `https://api.atlassian.com/ex/jira/${cloudId}/rest/agile/1.0/board/${params!.boardId}/sprint?startAt=${startAt}&maxResults=${maxResults}`
|
|
||||||
if (params?.state) url += `&state=${encodeURIComponent(params.state)}`
|
|
||||||
const res = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${params!.accessToken}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (!res.ok) {
|
|
||||||
let message = `Failed to get board sprints (${res.status})`
|
|
||||||
try {
|
|
||||||
const err = await res.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
return res.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
let data: any
|
|
||||||
if (!params?.cloudId) {
|
|
||||||
const cloudId = await getJiraCloudId(params!.domain, params!.accessToken)
|
|
||||||
data = await fetchSprints(cloudId)
|
|
||||||
} else {
|
|
||||||
if (!response.ok) {
|
|
||||||
let message = `Failed to get board sprints (${response.status})`
|
|
||||||
try {
|
|
||||||
const err = await response.json()
|
|
||||||
message = err?.errorMessages?.join(', ') || err?.message || message
|
|
||||||
} catch (_e) {}
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
data = await response.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
output: {
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
total: data.total ?? 0,
|
|
||||||
startAt: data.startAt ?? 0,
|
|
||||||
maxResults: data.maxResults ?? 0,
|
|
||||||
isLast: data.isLast ?? true,
|
|
||||||
sprints: (data.values ?? []).map((s: any) => ({
|
|
||||||
id: s.id ?? 0,
|
|
||||||
name: s.name ?? '',
|
|
||||||
state: s.state ?? '',
|
|
||||||
startDate: s.startDate ?? null,
|
|
||||||
endDate: s.endDate ?? null,
|
|
||||||
completeDate: s.completeDate ?? null,
|
|
||||||
goal: s.goal ?? null,
|
|
||||||
boardId: s.originBoardId ?? null,
|
|
||||||
self: s.self ?? '',
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
outputs: {
|
|
||||||
ts: TIMESTAMP_OUTPUT,
|
|
||||||
total: { type: 'number', description: 'Total number of sprints' },
|
|
||||||
startAt: { type: 'number', description: 'Pagination start index' },
|
|
||||||
maxResults: { type: 'number', description: 'Maximum results per page' },
|
|
||||||
isLast: { type: 'boolean', description: 'Whether this is the last page' },
|
|
||||||
sprints: {
|
|
||||||
type: 'array',
|
|
||||||
description: 'Array of sprints',
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
properties: SPRINT_ITEM_PROPERTIES,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user