Co-authored-by: aadamgough <adam@sim.ai>
This commit is contained in:
Adam Gough
2025-12-30 14:46:31 -08:00
committed by GitHub
parent 9208375523
commit 7356edccbb
49 changed files with 5818 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -51,6 +51,7 @@ import {
IntercomIcon,
JinaAIIcon,
JiraIcon,
JiraServiceManagementIcon,
KalshiIcon,
LinearIcon,
LinkedInIcon,
@@ -168,6 +169,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
intercom: IntercomIcon,
jina: JinaAIIcon,
jira: JiraIcon,
jira_service_management: JiraServiceManagementIcon,
kalshi: KalshiIcon,
knowledge: PackageSearchIcon,
linear: LinearIcon,

View File

@@ -0,0 +1,490 @@
---
title: Jira Service Management
description: Interact with Jira Service Management
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="jira_service_management"
color="#E0E0E0"
/>
## 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.
## Tools
### `jsm_get_service_desks`
Get all service desks from 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 |
| `start` | number | No | Start index for pagination \(default: 0\) |
| `limit` | number | No | Maximum results to return \(default: 50\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `serviceDesks` | json | Array of service desks |
| `total` | number | Total number of service desks |
| `isLastPage` | boolean | Whether this is the last page |
### `jsm_get_request_types`
Get request types 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 to get request types for |
| `start` | number | No | Start index for pagination \(default: 0\) |
| `limit` | number | No | Maximum results to return \(default: 50\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `requestTypes` | json | Array of request types |
| `total` | number | Total number of request types |
| `isLastPage` | boolean | Whether this is the last page |
### `jsm_create_request`
Create a new 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 |
| `serviceDeskId` | string | Yes | Service Desk ID to create the request in |
| `requestTypeId` | string | Yes | Request Type ID for the new request |
| `summary` | string | Yes | Summary/title for the service request |
| `description` | string | No | Description for the service request |
| `raiseOnBehalfOf` | string | No | Account ID of customer to raise request on behalf of |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `issueId` | string | Created request issue ID |
| `issueKey` | string | Created request issue key \(e.g., SD-123\) |
| `requestTypeId` | string | Request type ID |
| `serviceDeskId` | string | Service desk ID |
| `success` | boolean | Whether the request was created successfully |
| `url` | string | URL to the created request |
### `jsm_get_request`
Get a single service request from 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 |
### `jsm_get_requests`
Get multiple service requests from 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 | Filter by service desk ID |
| `requestOwnership` | string | No | Filter by ownership: OWNED_REQUESTS, PARTICIPATED_REQUESTS, ORGANIZATION, ALL_REQUESTS |
| `requestStatus` | string | No | Filter by status: OPEN, CLOSED, ALL |
| `searchTerm` | string | No | Search term to filter requests |
| `start` | number | No | Start index for pagination \(default: 0\) |
| `limit` | number | No | Maximum results to return \(default: 50\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `requests` | json | Array of service requests |
| `total` | number | Total number of requests |
| `isLastPage` | boolean | Whether this is the last page |
### `jsm_add_comment`
Add a comment (public or internal) to 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\) |
| `body` | string | Yes | Comment body text |
| `isPublic` | boolean | Yes | Whether the comment is public \(visible to customer\) or internal |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `issueIdOrKey` | string | Issue ID or key |
| `commentId` | string | Created comment ID |
| `body` | string | Comment body text |
| `isPublic` | boolean | Whether the comment is public |
| `success` | boolean | Whether the comment was added successfully |
### `jsm_get_comments`
Get comments 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\) |
| `isPublic` | boolean | No | Filter to only public comments |
| `internal` | boolean | No | Filter to only internal comments |
| `start` | number | No | Start index for pagination \(default: 0\) |
| `limit` | number | No | Maximum results to return \(default: 50\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `issueIdOrKey` | string | Issue ID or key |
| `comments` | json | Array of comments |
| `total` | number | Total number of comments |
| `isLastPage` | boolean | Whether this is the last page |
### `jsm_get_customers`
Get customers 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 to get customers for |
| `query` | string | No | Search query to filter customers |
| `start` | number | No | Start index for pagination \(default: 0\) |
| `limit` | number | No | Maximum results to return \(default: 50\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `customers` | json | Array of customers |
| `total` | number | Total number of customers |
| `isLastPage` | boolean | Whether this is the last page |
### `jsm_add_customer`
Add customers to 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 to add customers to |
| `emails` | string | Yes | Comma-separated email addresses to add as customers |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `serviceDeskId` | string | Service desk ID |
| `success` | boolean | Whether customers were added successfully |
### `jsm_get_organizations`
Get organizations 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 to get organizations for |
| `start` | number | No | Start index for pagination \(default: 0\) |
| `limit` | number | No | Maximum results to return \(default: 50\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `organizations` | json | Array of organizations |
| `total` | number | Total number of organizations |
| `isLastPage` | boolean | Whether this is the last page |
### `jsm_create_organization`
Create a new 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 |
| `name` | string | Yes | Name of the organization to create |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `organizationId` | string | ID of the created organization |
| `name` | string | Name of the created organization |
| `success` | boolean | Whether the operation succeeded |
### `jsm_add_organization_to_service_desk`
Add an organization to 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 to add the organization to |
| `organizationId` | string | Yes | Organization ID to add to the service desk |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `serviceDeskId` | string | Service Desk ID |
| `organizationId` | string | Organization ID added |
| `success` | boolean | Whether the operation succeeded |
### `jsm_get_queues`
Get queues 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 to get queues for |
| `includeCount` | boolean | No | Include issue count for each queue |
| `start` | number | No | Start index for pagination \(default: 0\) |
| `limit` | number | No | Maximum results to return \(default: 50\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `queues` | json | Array of queues |
| `total` | number | Total number of queues |
| `isLastPage` | boolean | Whether this is the last page |
### `jsm_get_sla`
Get SLA information 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 \(default: 0\) |
| `limit` | number | No | Maximum results to return \(default: 50\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `issueIdOrKey` | string | Issue ID or key |
| `slas` | json | Array of SLA information |
| `total` | number | Total number of SLAs |
| `isLastPage` | boolean | Whether this is the last page |
### `jsm_get_transitions`
Get available transitions 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\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `issueIdOrKey` | string | Issue ID or key |
| `transitions` | json | Array of available transitions |
### `jsm_transition_request`
Transition a service request to a new status 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\) |
| `transitionId` | string | Yes | Transition ID to apply |
| `comment` | string | No | Optional comment to add during transition |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `issueIdOrKey` | string | Issue ID or key |
| `transitionId` | string | Applied transition ID |
| `success` | boolean | Whether the transition was successful |
### `jsm_get_participants`
Get participants 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\) |
| `start` | number | No | Start index for pagination \(default: 0\) |
| `limit` | number | No | Maximum results to return \(default: 50\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `issueIdOrKey` | string | Issue ID or key |
| `participants` | json | Array of participants |
| `total` | number | Total number of participants |
| `isLastPage` | boolean | Whether this is the last page |
### `jsm_add_participants`
Add participants to 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 add as participants |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `issueIdOrKey` | string | Issue ID or key |
| `participants` | json | Array of added participants |
| `success` | boolean | Whether the operation succeeded |
### `jsm_get_approvals`
Get approvals 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\) |
| `start` | number | No | Start index for pagination \(default: 0\) |
| `limit` | number | No | Maximum results to return \(default: 50\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `issueIdOrKey` | string | Issue ID or key |
| `approvals` | json | Array of approvals |
| `total` | number | Total number of approvals |
| `isLastPage` | boolean | Whether this is the last page |
### `jsm_answer_approval`
Approve or decline an approval 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\) |
| `approvalId` | string | Yes | Approval ID to answer |
| `decision` | string | Yes | Decision: "approve" or "decline" |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `issueIdOrKey` | string | Issue ID or key |
| `approvalId` | string | Approval ID |
| `decision` | string | Decision made \(approve/decline\) |
| `success` | boolean | Whether the operation succeeded |
## Notes
- Category: `tools`
- Type: `jira_service_management`

View File

@@ -46,6 +46,7 @@
"intercom",
"jina",
"jira",
"jira_service_management",
"kalshi",
"knowledge",
"linear",

View File

@@ -0,0 +1,156 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmApprovalsAPI')
export async function POST(request: Request) {
try {
const body = await request.json()
const {
domain,
accessToken,
cloudId: cloudIdParam,
action,
issueIdOrKey,
approvalId,
decision,
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 })
}
if (!action) {
logger.error('Missing action in request')
return NextResponse.json({ error: 'Action is required' }, { status: 400 })
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const baseUrl = getJsmApiBaseUrl(cloudId)
if (action === 'get') {
const params = new URLSearchParams()
if (start) params.append('start', start)
if (limit) params.append('limit', limit)
const url = `${baseUrl}/request/${issueIdOrKey}/approval${params.toString() ? `?${params.toString()}` : ''}`
logger.info('Fetching approvals 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,
approvals: data.values || [],
total: data.size || 0,
isLastPage: data.isLastPage ?? true,
},
})
}
if (action === 'answer') {
if (!approvalId) {
logger.error('Missing approvalId in request')
return NextResponse.json({ error: 'Approval ID is required' }, { status: 400 })
}
if (!decision || !['approve', 'decline'].includes(decision)) {
logger.error('Invalid or missing decision in request')
return NextResponse.json(
{ error: 'Decision is required and must be "approve" or "decline"' },
{ status: 400 }
)
}
const url = `${baseUrl}/request/${issueIdOrKey}/approval/${approvalId}`
logger.info('Answering approval:', { issueIdOrKey, approvalId, decision })
const response = await fetch(url, {
method: 'POST',
headers: getJsmHeaders(accessToken),
body: JSON.stringify({ decision }),
})
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,
approvalId,
decision,
success: true,
approval: data,
},
})
}
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
} catch (error) {
logger.error('Error in approvals 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 }
)
}
}

View File

@@ -0,0 +1,97 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmCommentAPI')
export async function POST(request: Request) {
try {
const {
domain,
accessToken,
cloudId: providedCloudId,
issueIdOrKey,
body: commentBody,
isPublic,
} = await request.json()
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 (!commentBody) {
logger.error('Missing comment body in request')
return NextResponse.json({ error: 'Comment body is required' }, { status: 400 })
}
const cloudId = providedCloudId || (await getJiraCloudId(domain, accessToken))
const baseUrl = getJsmApiBaseUrl(cloudId)
const url = `${baseUrl}/request/${issueIdOrKey}/comment`
logger.info('Adding comment to:', url)
const response = await fetch(url, {
method: 'POST',
headers: getJsmHeaders(accessToken),
body: JSON.stringify({
body: commentBody,
public: isPublic ?? true,
}),
})
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,
commentId: data.id,
body: data.body,
isPublic: data.public,
success: true,
},
})
} catch (error) {
logger.error('Error adding comment:', {
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 }
)
}
}

View File

@@ -0,0 +1,96 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmCommentsAPI')
export async function POST(request: Request) {
try {
const body = await request.json()
const {
domain,
accessToken,
cloudId: cloudIdParam,
issueIdOrKey,
isPublic,
internal,
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 cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const baseUrl = getJsmApiBaseUrl(cloudId)
const params = new URLSearchParams()
if (isPublic) params.append('public', isPublic)
if (internal) params.append('internal', internal)
if (start) params.append('start', start)
if (limit) params.append('limit', limit)
const url = `${baseUrl}/request/${issueIdOrKey}/comment${params.toString() ? `?${params.toString()}` : ''}`
logger.info('Fetching comments 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,
comments: data.values || [],
total: data.size || 0,
isLastPage: data.isLastPage ?? true,
},
})
} catch (error) {
logger.error('Error fetching comments:', {
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 }
)
}
}

View File

@@ -0,0 +1,143 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmCustomersAPI')
export async function POST(request: Request) {
try {
const body = await request.json()
const {
domain,
accessToken,
cloudId: cloudIdParam,
serviceDeskId,
query,
start,
limit,
emails,
} = 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 })
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const baseUrl = getJsmApiBaseUrl(cloudId)
const parsedEmails = emails
? typeof emails === 'string'
? emails
.split(',')
.map((email: string) => email.trim())
.filter((email: string) => email)
: emails
: []
const isAddOperation = parsedEmails.length > 0
if (isAddOperation) {
const url = `${baseUrl}/servicedesk/${serviceDeskId}/customer`
logger.info('Adding customers to:', url, { emails: parsedEmails })
const requestBody: Record<string, unknown> = {
usernames: parsedEmails,
}
const response = await fetch(url, {
method: 'POST',
headers: getJsmHeaders(accessToken),
body: JSON.stringify(requestBody),
})
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 }
)
}
return NextResponse.json({
success: true,
output: {
ts: new Date().toISOString(),
serviceDeskId,
success: true,
},
})
}
const params = new URLSearchParams()
if (query) params.append('query', query)
if (start) params.append('start', start)
if (limit) params.append('limit', limit)
const url = `${baseUrl}/servicedesk/${serviceDeskId}/customer${params.toString() ? `?${params.toString()}` : ''}`
logger.info('Fetching customers 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(),
customers: data.values || [],
total: data.size || 0,
isLastPage: data.isLastPage ?? true,
},
})
} catch (error) {
logger.error('Error with customers 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 }
)
}
}

View File

@@ -0,0 +1,143 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmOrganizationAPI')
export async function POST(request: Request) {
try {
const body = await request.json()
const {
domain,
accessToken,
cloudId: cloudIdParam,
action,
name,
serviceDeskId,
organizationId,
} = 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 (!action) {
logger.error('Missing action in request')
return NextResponse.json({ error: 'Action is required' }, { status: 400 })
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const baseUrl = getJsmApiBaseUrl(cloudId)
if (action === 'create') {
if (!name) {
logger.error('Missing organization name in request')
return NextResponse.json({ error: 'Organization name is required' }, { status: 400 })
}
const url = `${baseUrl}/organization`
logger.info('Creating organization:', { name })
const response = await fetch(url, {
method: 'POST',
headers: getJsmHeaders(accessToken),
body: JSON.stringify({ name }),
})
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: data.id,
name: data.name,
success: true,
},
})
}
if (action === 'add_to_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 url = `${baseUrl}/servicedesk/${serviceDeskId}/organization`
logger.info('Adding organization to service desk:', { serviceDeskId, organizationId })
const response = await fetch(url, {
method: 'POST',
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 }
)
}
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
} catch (error) {
logger.error('Error in organization 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 }
)
}
}

View File

@@ -0,0 +1,84 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmOrganizationsAPI')
export async function POST(request: Request) {
try {
const body = await request.json()
const { domain, accessToken, cloudId: cloudIdParam, serviceDeskId, 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 })
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
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}/organization${params.toString() ? `?${params.toString()}` : ''}`
logger.info('Fetching organizations 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(),
organizations: data.values || [],
total: data.size || 0,
isLastPage: data.isLastPage ?? true,
},
})
} catch (error) {
logger.error('Error fetching organizations:', {
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 }
)
}
}

View File

@@ -0,0 +1,153 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmParticipantsAPI')
export async function POST(request: Request) {
try {
const body = await request.json()
const {
domain,
accessToken,
cloudId: cloudIdParam,
action,
issueIdOrKey,
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 (!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 cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const baseUrl = getJsmApiBaseUrl(cloudId)
if (action === 'get') {
const params = new URLSearchParams()
if (start) params.append('start', start)
if (limit) params.append('limit', limit)
const url = `${baseUrl}/request/${issueIdOrKey}/participant${params.toString() ? `?${params.toString()}` : ''}`
logger.info('Fetching participants 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,
participants: data.values || [],
total: data.size || 0,
isLastPage: data.isLastPage ?? true,
},
})
}
if (action === 'add') {
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
const url = `${baseUrl}/request/${issueIdOrKey}/participant`
logger.info('Adding participants to:', url, { accountIds: parsedAccountIds })
const response = await fetch(url, {
method: 'POST',
headers: getJsmHeaders(accessToken),
body: JSON.stringify({ accountIds: parsedAccountIds }),
})
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,
participants: data.values || [],
success: true,
},
})
}
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
} catch (error) {
logger.error('Error in participants 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 }
)
}
}

View File

@@ -0,0 +1,93 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmQueuesAPI')
export async function POST(request: Request) {
try {
const body = await request.json()
const {
domain,
accessToken,
cloudId: cloudIdParam,
serviceDeskId,
includeCount,
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 })
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const baseUrl = getJsmApiBaseUrl(cloudId)
const params = new URLSearchParams()
if (includeCount) params.append('includeCount', includeCount)
if (start) params.append('start', start)
if (limit) params.append('limit', limit)
const url = `${baseUrl}/servicedesk/${serviceDeskId}/queue${params.toString() ? `?${params.toString()}` : ''}`
logger.info('Fetching queues 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(),
queues: data.values || [],
total: data.size || 0,
isLastPage: data.isLastPage ?? true,
},
})
} catch (error) {
logger.error('Error fetching queues:', {
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 }
)
}
}

View File

@@ -0,0 +1,144 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmRequestAPI')
export async function POST(request: Request) {
try {
const body = await request.json()
const {
domain,
accessToken,
cloudId: cloudIdParam,
issueIdOrKey,
serviceDeskId,
requestTypeId,
summary,
description,
raiseOnBehalfOf,
requestFieldValues,
} = 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 })
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const baseUrl = getJsmApiBaseUrl(cloudId)
const isCreateOperation = serviceDeskId && requestTypeId && summary
if (isCreateOperation) {
const url = `${baseUrl}/request`
logger.info('Creating request at:', url)
const requestBody: Record<string, unknown> = {
serviceDeskId,
requestTypeId,
requestFieldValues: requestFieldValues || {
summary,
...(description && { description }),
},
}
if (raiseOnBehalfOf) {
requestBody.raiseOnBehalfOf = raiseOnBehalfOf
}
const response = await fetch(url, {
method: 'POST',
headers: getJsmHeaders(accessToken),
body: JSON.stringify(requestBody),
})
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(),
issueId: data.issueId,
issueKey: data.issueKey,
requestTypeId: data.requestTypeId,
serviceDeskId: data.serviceDeskId,
success: true,
url: `https://${domain}/browse/${data.issueKey}`,
},
})
}
if (!issueIdOrKey) {
logger.error('Missing issueIdOrKey in request')
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
}
const url = `${baseUrl}/request/${issueIdOrKey}`
logger.info('Fetching request 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(),
request: data,
},
})
} catch (error) {
logger.error('Error with request 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 }
)
}
}

View File

@@ -0,0 +1,97 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmRequestsAPI')
export async function POST(request: Request) {
try {
const body = await request.json()
const {
domain,
accessToken,
cloudId: cloudIdParam,
serviceDeskId,
requestOwnership,
requestStatus,
searchTerm,
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 })
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const baseUrl = getJsmApiBaseUrl(cloudId)
const params = new URLSearchParams()
if (serviceDeskId) params.append('serviceDeskId', serviceDeskId)
if (requestOwnership && requestOwnership !== 'ALL_REQUESTS') {
params.append('requestOwnership', requestOwnership)
}
if (requestStatus && requestStatus !== 'ALL') {
params.append('requestStatus', requestStatus)
}
if (searchTerm) params.append('searchTerm', searchTerm)
if (start) params.append('start', start)
if (limit) params.append('limit', limit)
const url = `${baseUrl}/request${params.toString() ? `?${params.toString()}` : ''}`
logger.info('Fetching requests 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(),
requests: data.values || [],
total: data.size || 0,
isLastPage: data.isLastPage ?? true,
},
})
} catch (error) {
logger.error('Error fetching requests:', {
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 }
)
}
}

View File

@@ -0,0 +1,84 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmRequestTypesAPI')
export async function POST(request: Request) {
try {
const body = await request.json()
const { domain, accessToken, cloudId: cloudIdParam, serviceDeskId, 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 })
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
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}/requesttype${params.toString() ? `?${params.toString()}` : ''}`
logger.info('Fetching request types 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(),
requestTypes: data.values || [],
total: data.size || 0,
isLastPage: data.isLastPage ?? true,
},
})
} catch (error) {
logger.error('Error fetching request types:', {
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 }
)
}
}

View File

@@ -0,0 +1,79 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmServiceDesksAPI')
export async function POST(request: Request) {
try {
const body = await request.json()
const { domain, accessToken, cloudId: cloudIdParam, 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 })
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const baseUrl = getJsmApiBaseUrl(cloudId)
const params = new URLSearchParams()
if (start) params.append('start', start)
if (limit) params.append('limit', limit)
const url = `${baseUrl}/servicedesk${params.toString() ? `?${params.toString()}` : ''}`
logger.info('Fetching service desks 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(),
serviceDesks: data.values || [],
total: data.size || 0,
isLastPage: data.isLastPage ?? true,
},
})
} catch (error) {
logger.error('Error fetching service desks:', {
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 }
)
}
}

View File

@@ -0,0 +1,85 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmSlaAPI')
export async function POST(request: Request) {
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 cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
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}/sla${params.toString() ? `?${params.toString()}` : ''}`
logger.info('Fetching SLA info 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,
slas: data.values || [],
total: data.size || 0,
isLastPage: data.isLastPage ?? true,
},
})
} catch (error) {
logger.error('Error fetching SLA info:', {
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 }
)
}
}

View File

@@ -0,0 +1,100 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmTransitionAPI')
export async function POST(request: Request) {
try {
const {
domain,
accessToken,
cloudId: providedCloudId,
issueIdOrKey,
transitionId,
comment,
} = await request.json()
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 (!transitionId) {
logger.error('Missing transitionId in request')
return NextResponse.json({ error: 'Transition ID is required' }, { status: 400 })
}
const cloudId = providedCloudId || (await getJiraCloudId(domain, accessToken))
const baseUrl = getJsmApiBaseUrl(cloudId)
const url = `${baseUrl}/request/${issueIdOrKey}/transition`
logger.info('Transitioning request at:', url)
const body: Record<string, unknown> = {
id: transitionId,
}
if (comment) {
body.additionalComment = {
body: comment,
}
}
const response = await fetch(url, {
method: 'POST',
headers: getJsmHeaders(accessToken),
body: JSON.stringify(body),
})
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 }
)
}
return NextResponse.json({
success: true,
output: {
ts: new Date().toISOString(),
issueIdOrKey,
transitionId,
success: true,
},
})
} catch (error) {
logger.error('Error transitioning request:', {
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 }
)
}
}

View File

@@ -0,0 +1,79 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { getJiraCloudId, getJsmApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
export const dynamic = 'force-dynamic'
const logger = createLogger('JsmTransitionsAPI')
export async function POST(request: Request) {
try {
const body = await request.json()
const { domain, accessToken, cloudId: cloudIdParam, 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 })
}
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
const baseUrl = getJsmApiBaseUrl(cloudId)
const url = `${baseUrl}/request/${issueIdOrKey}/transition`
logger.info('Fetching transitions 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,
transitions: data.values || [],
},
})
} catch (error) {
logger.error('Error fetching transitions:', {
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 }
)
}
}

View File

@@ -0,0 +1,687 @@
import { JiraServiceManagementIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { JsmResponse } from '@/tools/jsm/types'
export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
type: 'jira_service_management',
name: 'Jira Service Management',
description: 'Interact with Jira Service Management',
authMode: AuthMode.OAuth,
longDescription:
'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',
category: 'tools',
bgColor: '#E0E0E0',
icon: JiraServiceManagementIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Get Service Desks', id: 'get_service_desks' },
{ label: 'Get Request Types', id: 'get_request_types' },
{ label: 'Create Request', id: 'create_request' },
{ label: 'Get Request', id: 'get_request' },
{ label: 'Get Requests', id: 'get_requests' },
{ label: 'Add Comment', id: 'add_comment' },
{ label: 'Get Comments', id: 'get_comments' },
{ label: 'Get Customers', id: 'get_customers' },
{ label: 'Add Customer', id: 'add_customer' },
{ label: 'Get Organizations', id: 'get_organizations' },
{ label: 'Create Organization', id: 'create_organization' },
{ label: 'Add Organization to Service Desk', id: 'add_organization_to_service_desk' },
{ label: 'Get Queues', id: 'get_queues' },
{ label: 'Get SLA', id: 'get_sla' },
{ label: 'Get Transitions', id: 'get_transitions' },
{ label: 'Transition Request', id: 'transition_request' },
{ label: 'Get Participants', id: 'get_participants' },
{ label: 'Add Participants', id: 'add_participants' },
{ label: 'Get Approvals', id: 'get_approvals' },
{ label: 'Answer Approval', id: 'answer_approval' },
],
value: () => 'get_service_desks',
},
{
id: 'domain',
title: 'Domain',
type: 'short-input',
required: true,
placeholder: 'Enter Jira domain (e.g., simstudio.atlassian.net)',
},
{
id: 'credential',
title: 'Jira Service Management Account',
type: 'oauth-input',
required: true,
serviceId: 'jira-service-management',
requiredScopes: [
'read:jira-user',
'read:jira-work',
'write:jira-work',
'read:project:jira',
'read:me',
'offline_access',
'read:issue:jira',
'read:status:jira',
'read:user:jira',
'read:issue-details:jira',
'write:comment:jira',
'read:comment:jira',
'read:servicedesk:jira-service-management',
'read:requesttype:jira-service-management',
'read:request:jira-service-management',
'write:request:jira-service-management',
'read:request.comment:jira-service-management',
'write:request.comment:jira-service-management',
'read:customer:jira-service-management',
'write:customer:jira-service-management',
'read:servicedesk.customer:jira-service-management',
'write:servicedesk.customer:jira-service-management',
'read:organization:jira-service-management',
'write:organization:jira-service-management',
'read:servicedesk.organization:jira-service-management',
'write:servicedesk.organization:jira-service-management',
'read:queue:jira-service-management',
'read:request.sla:jira-service-management',
'read:request.status:jira-service-management',
'write:request.status:jira-service-management',
'read:request.participant:jira-service-management',
'write:request.participant:jira-service-management',
'read:request.approval:jira-service-management',
'write:request.approval:jira-service-management',
],
placeholder: 'Select Jira Service Management account',
},
{
id: 'serviceDeskId',
title: 'Service Desk ID',
type: 'short-input',
placeholder: 'Enter service desk ID',
condition: {
field: 'operation',
value: [
'get_request_types',
'create_request',
'get_customers',
'add_customer',
'get_organizations',
'add_organization_to_service_desk',
'get_queues',
],
},
},
{
id: 'requestTypeId',
title: 'Request Type ID',
type: 'short-input',
required: true,
placeholder: 'Enter request type ID',
condition: { field: 'operation', value: 'create_request' },
},
{
id: 'issueIdOrKey',
title: 'Issue ID or Key',
type: 'short-input',
required: true,
placeholder: 'Enter issue ID or key (e.g., SD-123)',
condition: {
field: 'operation',
value: [
'get_request',
'add_comment',
'get_comments',
'get_sla',
'get_transitions',
'transition_request',
'get_participants',
'add_participants',
'get_approvals',
'answer_approval',
],
},
},
{
id: 'summary',
title: 'Summary',
type: 'short-input',
required: true,
placeholder: 'Enter request summary',
condition: { field: 'operation', value: 'create_request' },
},
{
id: 'description',
title: 'Description',
type: 'long-input',
placeholder: 'Enter request description',
condition: { field: 'operation', value: 'create_request' },
},
{
id: 'raiseOnBehalfOf',
title: 'Raise on Behalf Of',
type: 'short-input',
placeholder: 'Account ID to raise request on behalf of',
condition: { field: 'operation', value: 'create_request' },
},
{
id: 'commentBody',
title: 'Comment',
type: 'long-input',
required: true,
placeholder: 'Enter comment text',
condition: { field: 'operation', value: 'add_comment' },
},
{
id: 'isPublic',
title: 'Comment Visibility',
type: 'dropdown',
options: [
{ label: 'Public (visible to customer)', id: 'true' },
{ label: 'Internal (agents only)', id: 'false' },
],
value: () => 'true',
condition: { field: 'operation', value: 'add_comment' },
},
{
id: 'emails',
title: 'Email Addresses',
type: 'short-input',
required: true,
placeholder: 'Comma-separated email addresses',
condition: { field: 'operation', value: 'add_customer' },
},
{
id: 'customerQuery',
title: 'Search Query',
type: 'short-input',
placeholder: 'Search customers by name or email',
condition: { field: 'operation', value: 'get_customers' },
},
{
id: 'transitionId',
title: 'Transition ID',
type: 'short-input',
required: true,
placeholder: 'Enter transition ID',
condition: { field: 'operation', value: 'transition_request' },
},
{
id: 'transitionComment',
title: 'Comment',
type: 'long-input',
placeholder: 'Add optional comment during transition',
condition: { field: 'operation', value: 'transition_request' },
},
{
id: 'requestOwnership',
title: 'Request Ownership',
type: 'dropdown',
options: [
{ label: 'All Requests', id: 'ALL_REQUESTS' },
{ label: 'My Requests', id: 'OWNED_REQUESTS' },
{ label: 'Participated', id: 'PARTICIPATED_REQUESTS' },
{ label: 'Organization', id: 'ORGANIZATION' },
],
value: () => 'ALL_REQUESTS',
condition: { field: 'operation', value: 'get_requests' },
},
{
id: 'requestStatus',
title: 'Request Status',
type: 'dropdown',
options: [
{ label: 'All', id: 'ALL' },
{ label: 'Open', id: 'OPEN' },
{ label: 'Closed', id: 'CLOSED' },
],
value: () => 'ALL',
condition: { field: 'operation', value: 'get_requests' },
},
{
id: 'searchTerm',
title: 'Search Term',
type: 'short-input',
placeholder: 'Search requests',
condition: { field: 'operation', value: 'get_requests' },
},
{
id: 'includeCount',
title: 'Include Issue Count',
type: 'dropdown',
options: [
{ label: 'No', id: 'false' },
{ label: 'Yes', id: 'true' },
],
value: () => 'false',
condition: { field: 'operation', value: 'get_queues' },
},
{
id: 'organizationName',
title: 'Organization Name',
type: 'short-input',
required: true,
placeholder: 'Enter organization name',
condition: { field: 'operation', value: 'create_organization' },
},
{
id: 'organizationId',
title: 'Organization ID',
type: 'short-input',
required: true,
placeholder: 'Enter organization ID',
condition: { field: 'operation', value: 'add_organization_to_service_desk' },
},
{
id: 'participantAccountIds',
title: 'Account IDs',
type: 'short-input',
required: true,
placeholder: 'Comma-separated account IDs',
condition: { field: 'operation', value: 'add_participants' },
},
{
id: 'approvalId',
title: 'Approval ID',
type: 'short-input',
required: true,
placeholder: 'Enter approval ID',
condition: { field: 'operation', value: 'answer_approval' },
},
{
id: 'approvalDecision',
title: 'Decision',
type: 'dropdown',
options: [
{ label: 'Approve', id: 'approve' },
{ label: 'Decline', id: 'decline' },
],
value: () => 'approve',
condition: { field: 'operation', value: 'answer_approval' },
},
{
id: 'maxResults',
title: 'Max Results',
type: 'short-input',
placeholder: 'Maximum results (default: 50)',
condition: {
field: 'operation',
value: [
'get_service_desks',
'get_request_types',
'get_requests',
'get_comments',
'get_customers',
'get_organizations',
'get_queues',
'get_sla',
],
},
},
],
tools: {
access: [
'jsm_get_service_desks',
'jsm_get_request_types',
'jsm_create_request',
'jsm_get_request',
'jsm_get_requests',
'jsm_add_comment',
'jsm_get_comments',
'jsm_get_customers',
'jsm_add_customer',
'jsm_get_organizations',
'jsm_create_organization',
'jsm_add_organization_to_service_desk',
'jsm_get_queues',
'jsm_get_sla',
'jsm_get_transitions',
'jsm_transition_request',
'jsm_get_participants',
'jsm_add_participants',
'jsm_get_approvals',
'jsm_answer_approval',
],
config: {
tool: (params) => {
switch (params.operation) {
case 'get_service_desks':
return 'jsm_get_service_desks'
case 'get_request_types':
return 'jsm_get_request_types'
case 'create_request':
return 'jsm_create_request'
case 'get_request':
return 'jsm_get_request'
case 'get_requests':
return 'jsm_get_requests'
case 'add_comment':
return 'jsm_add_comment'
case 'get_comments':
return 'jsm_get_comments'
case 'get_customers':
return 'jsm_get_customers'
case 'add_customer':
return 'jsm_add_customer'
case 'get_organizations':
return 'jsm_get_organizations'
case 'create_organization':
return 'jsm_create_organization'
case 'add_organization_to_service_desk':
return 'jsm_add_organization_to_service_desk'
case 'get_queues':
return 'jsm_get_queues'
case 'get_sla':
return 'jsm_get_sla'
case 'get_transitions':
return 'jsm_get_transitions'
case 'transition_request':
return 'jsm_transition_request'
case 'get_participants':
return 'jsm_get_participants'
case 'add_participants':
return 'jsm_add_participants'
case 'get_approvals':
return 'jsm_get_approvals'
case 'answer_approval':
return 'jsm_answer_approval'
default:
return 'jsm_get_service_desks'
}
},
params: (params) => {
const baseParams = {
credential: params.credential,
domain: params.domain,
}
switch (params.operation) {
case 'get_service_desks':
return {
...baseParams,
limit: params.maxResults ? Number.parseInt(params.maxResults) : undefined,
}
case 'get_request_types':
if (!params.serviceDeskId) {
throw new Error('Service Desk ID is required')
}
return {
...baseParams,
serviceDeskId: params.serviceDeskId,
limit: params.maxResults ? Number.parseInt(params.maxResults) : undefined,
}
case 'create_request':
if (!params.serviceDeskId) {
throw new Error('Service Desk ID is required')
}
if (!params.requestTypeId) {
throw new Error('Request Type ID is required')
}
if (!params.summary) {
throw new Error('Summary is required')
}
return {
...baseParams,
serviceDeskId: params.serviceDeskId,
requestTypeId: params.requestTypeId,
summary: params.summary,
description: params.description,
raiseOnBehalfOf: params.raiseOnBehalfOf,
}
case 'get_request':
if (!params.issueIdOrKey) {
throw new Error('Issue ID or key is required')
}
return {
...baseParams,
issueIdOrKey: params.issueIdOrKey,
}
case 'get_requests':
return {
...baseParams,
serviceDeskId: params.serviceDeskId,
requestOwnership: params.requestOwnership,
requestStatus: params.requestStatus,
searchTerm: params.searchTerm,
limit: params.maxResults ? Number.parseInt(params.maxResults) : undefined,
}
case 'add_comment':
if (!params.issueIdOrKey) {
throw new Error('Issue ID or key is required')
}
if (!params.commentBody) {
throw new Error('Comment body is required')
}
return {
...baseParams,
issueIdOrKey: params.issueIdOrKey,
body: params.commentBody,
isPublic: params.isPublic === 'true',
}
case 'get_comments':
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_customers':
if (!params.serviceDeskId) {
throw new Error('Service Desk ID is required')
}
return {
...baseParams,
serviceDeskId: params.serviceDeskId,
query: params.customerQuery,
limit: params.maxResults ? Number.parseInt(params.maxResults) : undefined,
}
case 'add_customer': {
if (!params.serviceDeskId) {
throw new Error('Service Desk ID is required')
}
const accountIds = params.accountIds
? params.accountIds
.split(',')
.map((id: string) => id.trim())
.filter((id: string) => id)
: undefined
const emails = params.emails
? params.emails
.split(',')
.map((email: string) => email.trim())
.filter((email: string) => email)
: undefined
if ((!accountIds || accountIds.length === 0) && (!emails || emails.length === 0)) {
throw new Error('At least one account ID or email is required')
}
return {
...baseParams,
serviceDeskId: params.serviceDeskId,
accountIds,
emails,
}
}
case 'get_organizations':
if (!params.serviceDeskId) {
throw new Error('Service Desk ID is required')
}
return {
...baseParams,
serviceDeskId: params.serviceDeskId,
limit: params.maxResults ? Number.parseInt(params.maxResults) : undefined,
}
case 'get_queues':
if (!params.serviceDeskId) {
throw new Error('Service Desk ID is required')
}
return {
...baseParams,
serviceDeskId: params.serviceDeskId,
includeCount: params.includeCount === 'true',
limit: params.maxResults ? Number.parseInt(params.maxResults) : undefined,
}
case 'get_sla':
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_transitions':
if (!params.issueIdOrKey) {
throw new Error('Issue ID or key is required')
}
return {
...baseParams,
issueIdOrKey: params.issueIdOrKey,
}
case 'transition_request':
if (!params.issueIdOrKey) {
throw new Error('Issue ID or key is required')
}
if (!params.transitionId) {
throw new Error('Transition ID is required')
}
return {
...baseParams,
issueIdOrKey: params.issueIdOrKey,
transitionId: params.transitionId,
comment: params.transitionComment,
}
case 'create_organization':
if (!params.organizationName) {
throw new Error('Organization name is required')
}
return {
...baseParams,
name: params.organizationName,
}
case 'add_organization_to_service_desk':
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 'get_participants':
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 'add_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_approvals':
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 'answer_approval':
if (!params.issueIdOrKey) {
throw new Error('Issue ID or key is required')
}
if (!params.approvalId) {
throw new Error('Approval ID is required')
}
if (!params.approvalDecision) {
throw new Error('Decision is required')
}
return {
...baseParams,
issueIdOrKey: params.issueIdOrKey,
approvalId: params.approvalId,
decision: params.approvalDecision,
}
default:
return baseParams
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
domain: { type: 'string', description: 'Jira domain' },
credential: { type: 'string', description: 'Jira Service Management access token' },
serviceDeskId: { type: 'string', description: 'Service desk ID' },
requestTypeId: { type: 'string', description: 'Request type ID' },
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
summary: { type: 'string', description: 'Request summary' },
description: { type: 'string', description: 'Request description' },
raiseOnBehalfOf: { type: 'string', description: 'Account ID to raise request on behalf of' },
commentBody: { type: 'string', description: 'Comment text' },
isPublic: { type: 'string', description: 'Whether comment is public or internal' },
accountIds: { type: 'string', description: 'Comma-separated account IDs' },
emails: { type: 'string', description: 'Comma-separated email addresses' },
customerQuery: { type: 'string', description: 'Customer search query' },
transitionId: { type: 'string', description: 'Transition ID' },
transitionComment: { type: 'string', description: 'Transition comment' },
requestOwnership: { type: 'string', description: 'Request ownership filter' },
requestStatus: { type: 'string', description: 'Request status filter' },
searchTerm: { type: 'string', description: 'Search term for requests' },
includeCount: { type: 'string', description: 'Include issue count for queues' },
maxResults: { type: 'string', description: 'Maximum results to return' },
organizationName: { type: 'string', description: 'Organization name' },
organizationId: { type: 'string', description: 'Organization ID' },
participantAccountIds: {
type: 'string',
description: 'Comma-separated account IDs for participants',
},
approvalId: { type: 'string', description: 'Approval ID' },
approvalDecision: { type: 'string', description: 'Approval decision (approve/decline)' },
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
success: { type: 'boolean', description: 'Whether the operation succeeded' },
serviceDesks: { type: 'json', description: 'Array of service desks' },
requestTypes: { type: 'json', description: 'Array of request types' },
issueId: { type: 'string', description: 'Issue ID' },
issueKey: { type: 'string', description: 'Issue key (e.g., SD-123)' },
request: { type: 'json', description: 'Request object' },
requests: { type: 'json', description: 'Array of requests' },
url: { type: 'string', description: 'URL to the request' },
commentId: { type: 'string', description: 'Comment ID' },
body: { type: 'string', description: 'Comment body' },
isPublic: { type: 'boolean', description: 'Whether comment is public' },
comments: { type: 'json', description: 'Array of comments' },
customers: { type: 'json', description: 'Array of customers' },
organizations: { type: 'json', description: 'Array of organizations' },
organizationId: { type: 'string', description: 'Created organization ID' },
name: { type: 'string', description: 'Organization name' },
queues: { type: 'json', description: 'Array of queues' },
slas: { type: 'json', description: 'Array of SLA information' },
transitions: { type: 'json', description: 'Array of available transitions' },
transitionId: { type: 'string', description: 'Applied transition ID' },
participants: { type: 'json', description: 'Array of participants' },
approvals: { type: 'json', description: 'Array of approvals' },
approvalId: { type: 'string', description: 'Approval ID' },
decision: { type: 'string', description: 'Approval decision' },
total: { type: 'number', description: 'Total count' },
isLastPage: { type: 'boolean', description: 'Whether this is the last page' },
},
}

View File

@@ -55,6 +55,7 @@ import { InputTriggerBlock } from '@/blocks/blocks/input_trigger'
import { IntercomBlock } from '@/blocks/blocks/intercom'
import { JinaBlock } from '@/blocks/blocks/jina'
import { JiraBlock } from '@/blocks/blocks/jira'
import { JiraServiceManagementBlock } from '@/blocks/blocks/jira_service_management'
import { KalshiBlock } from '@/blocks/blocks/kalshi'
import { KnowledgeBlock } from '@/blocks/blocks/knowledge'
import { LinearBlock } from '@/blocks/blocks/linear'
@@ -200,6 +201,7 @@ export const registry: Record<string, BlockConfig> = {
intercom: IntercomBlock,
jina: JinaBlock,
jira: JiraBlock,
jira_service_management: JiraServiceManagementBlock,
kalshi: KalshiBlock,
knowledge: KnowledgeBlock,
linear: LinearBlock,

File diff suppressed because one or more lines are too long

View File

@@ -1227,6 +1227,98 @@ export const auth = betterAuth({
},
},
// Jira Service Management provider
{
providerId: 'jira-service-management',
clientId: env.JIRA_CLIENT_ID as string,
clientSecret: env.JIRA_CLIENT_SECRET as string,
authorizationUrl: 'https://auth.atlassian.com/authorize',
tokenUrl: 'https://auth.atlassian.com/oauth/token',
userInfoUrl: 'https://api.atlassian.com/me',
scopes: [
'read:jira-user',
'read:jira-work',
'write:jira-work',
'read:project:jira',
'read:me',
'offline_access',
'read:issue:jira',
'read:status:jira',
'read:user:jira',
'read:issue-details:jira',
'write:comment:jira',
'read:comment:jira',
'read:servicedesk:jira-service-management',
'read:requesttype:jira-service-management',
'read:request:jira-service-management',
'write:request:jira-service-management',
'read:request.comment:jira-service-management',
'write:request.comment:jira-service-management',
'read:customer:jira-service-management',
'write:customer:jira-service-management',
'read:servicedesk.customer:jira-service-management',
'write:servicedesk.customer:jira-service-management',
'read:organization:jira-service-management',
'write:organization:jira-service-management',
'read:servicedesk.organization:jira-service-management',
'write:servicedesk.organization:jira-service-management',
'read:organization.user:jira-service-management',
'write:organization.user:jira-service-management',
'read:organization.property:jira-service-management',
'write:organization.property:jira-service-management',
'read:organization.profile:jira-service-management',
'write:organization.profile:jira-service-management',
'read:queue:jira-service-management',
'read:request.sla:jira-service-management',
'read:request.status:jira-service-management',
'write:request.status:jira-service-management',
'read:request.participant:jira-service-management',
'write:request.participant:jira-service-management',
'read:request.approval:jira-service-management',
'write:request.approval:jira-service-management',
],
responseType: 'code',
pkce: true,
accessType: 'offline',
authentication: 'basic',
prompt: 'consent',
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/jira-service-management`,
getUserInfo: async (tokens) => {
try {
const response = await fetch('https://api.atlassian.com/me', {
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
},
})
if (!response.ok) {
logger.error('Error fetching Jira Service Management user info:', {
status: response.status,
statusText: response.statusText,
})
return null
}
const profile = await response.json()
const now = new Date()
return {
id: profile.account_id,
name: profile.name || profile.display_name || 'JSM User',
email: profile.email || `${profile.account_id}@atlassian.com`,
image: profile.picture || undefined,
emailVerified: true,
createdAt: now,
updatedAt: now,
}
} catch (error) {
logger.error('Error in Jira Service Management getUserInfo:', { error })
return null
}
},
},
// Airtable provider
{
providerId: 'airtable',

View File

@@ -367,6 +367,55 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
'read:field:jira',
],
},
'jira-service-management': {
name: 'Jira Service Management',
description: 'Access Jira Service Management service desks, requests, and customers.',
providerId: 'jira-service-management',
icon: JiraIcon,
baseProviderIcon: JiraIcon,
scopes: [
'read:jira-user',
'read:jira-work',
'write:jira-work',
'read:project:jira',
'read:me',
'offline_access',
'read:issue:jira',
'read:status:jira',
'read:user:jira',
'read:issue-details:jira',
'write:comment:jira',
'read:comment:jira',
'read:servicedesk:jira-service-management',
'read:requesttype:jira-service-management',
'read:request:jira-service-management',
'write:request:jira-service-management',
'read:request.comment:jira-service-management',
'write:request.comment:jira-service-management',
'read:customer:jira-service-management',
'write:customer:jira-service-management',
'read:servicedesk.customer:jira-service-management',
'write:servicedesk.customer:jira-service-management',
'read:organization:jira-service-management',
'write:organization:jira-service-management',
'read:servicedesk.organization:jira-service-management',
'write:servicedesk.organization:jira-service-management',
'read:organization.user:jira-service-management',
'write:organization.user:jira-service-management',
'read:organization.property:jira-service-management',
'write:organization.property:jira-service-management',
'read:organization.profile:jira-service-management',
'write:organization.profile:jira-service-management',
'read:queue:jira-service-management',
'read:request.sla:jira-service-management',
'read:request.status:jira-service-management',
'write:request.status:jira-service-management',
'read:request.participant:jira-service-management',
'write:request.participant:jira-service-management',
'read:request.approval:jira-service-management',
'write:request.approval:jira-service-management',
],
},
},
defaultService: 'jira',
},

View File

@@ -18,6 +18,7 @@ export type OAuthProvider =
| 'airtable'
| 'notion'
| 'jira'
| 'jira-service-management'
| 'dropbox'
| 'microsoft'
| 'microsoft-excel'
@@ -59,6 +60,7 @@ export type OAuthService =
| 'airtable'
| 'notion'
| 'jira'
| 'jira-service-management'
| 'dropbox'
| 'microsoft-excel'
| 'microsoft-teams'

View File

@@ -0,0 +1,116 @@
import type { JsmAddCommentParams, JsmAddCommentResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmAddCommentTool: ToolConfig<JsmAddCommentParams, JsmAddCommentResponse> = {
id: 'jsm_add_comment',
name: 'JSM Add Comment',
description: 'Add a comment (public or internal) to a service request in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
issueIdOrKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue ID or key (e.g., SD-123)',
},
body: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comment body text',
},
isPublic: {
type: 'boolean',
required: true,
visibility: 'user-only',
description: 'Whether the comment is public (visible to customer) or internal',
},
},
request: {
url: '/api/tools/jsm/comment',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
issueIdOrKey: params.issueIdOrKey,
body: params.body,
isPublic: params.isPublic,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
issueIdOrKey: '',
commentId: '',
body: '',
isPublic: false,
success: false,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
issueIdOrKey: '',
commentId: '',
body: '',
isPublic: false,
success: false,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
commentId: { type: 'string', description: 'Created comment ID' },
body: { type: 'string', description: 'Comment body text' },
isPublic: { type: 'boolean', description: 'Whether the comment is public' },
success: { type: 'boolean', description: 'Whether the comment was added successfully' },
},
}

View File

@@ -0,0 +1,100 @@
import type { JsmAddCustomerParams, JsmAddCustomerResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmAddCustomerTool: ToolConfig<JsmAddCustomerParams, JsmAddCustomerResponse> = {
id: 'jsm_add_customer',
name: 'JSM Add Customer',
description: 'Add customers to a service desk in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
serviceDeskId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Service Desk ID to add customers to',
},
emails: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated email addresses to add as customers',
},
},
request: {
url: '/api/tools/jsm/customers',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
serviceDeskId: params.serviceDeskId,
emails: params.emails,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
serviceDeskId: '',
success: false,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
serviceDeskId: '',
success: false,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
serviceDeskId: { type: 'string', description: 'Service desk ID' },
success: { type: 'boolean', description: 'Whether customers were added successfully' },
},
}

View File

@@ -0,0 +1,110 @@
import type {
JsmAddOrganizationToServiceDeskParams,
JsmAddOrganizationToServiceDeskResponse,
} from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmAddOrganizationToServiceDeskTool: ToolConfig<
JsmAddOrganizationToServiceDeskParams,
JsmAddOrganizationToServiceDeskResponse
> = {
id: 'jsm_add_organization_to_service_desk',
name: 'JSM Add Organization to Service Desk',
description: 'Add an organization to a service desk in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
serviceDeskId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Service Desk ID to add the organization to',
},
organizationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Organization ID to add to the service desk',
},
},
request: {
url: '/api/tools/jsm/organization',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
serviceDeskId: params.serviceDeskId,
organizationId: params.organizationId,
action: 'add_to_service_desk',
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
serviceDeskId: '',
organizationId: '',
success: false,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
serviceDeskId: '',
organizationId: '',
success: false,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
serviceDeskId: { type: 'string', description: 'Service Desk ID' },
organizationId: { type: 'string', description: 'Organization ID added' },
success: { type: 'boolean', description: 'Whether the operation succeeded' },
},
}

View File

@@ -0,0 +1,107 @@
import type { JsmAddParticipantsParams, JsmAddParticipantsResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmAddParticipantsTool: ToolConfig<
JsmAddParticipantsParams,
JsmAddParticipantsResponse
> = {
id: 'jsm_add_participants',
name: 'JSM Add Participants',
description: 'Add participants to a request in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
issueIdOrKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue ID or key (e.g., SD-123)',
},
accountIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated account IDs to add as participants',
},
},
request: {
url: '/api/tools/jsm/participants',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
issueIdOrKey: params.issueIdOrKey,
accountIds: params.accountIds,
action: 'add',
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
issueIdOrKey: '',
participants: [],
success: false,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
issueIdOrKey: '',
participants: [],
success: false,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
participants: { type: 'json', description: 'Array of added participants' },
success: { type: 'boolean', description: 'Whether the operation succeeded' },
},
}

View File

@@ -0,0 +1,115 @@
import type { JsmAnswerApprovalParams, JsmAnswerApprovalResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmAnswerApprovalTool: ToolConfig<JsmAnswerApprovalParams, JsmAnswerApprovalResponse> =
{
id: 'jsm_answer_approval',
name: 'JSM Answer Approval',
description: 'Approve or decline an approval request in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
issueIdOrKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue ID or key (e.g., SD-123)',
},
approvalId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Approval ID to answer',
},
decision: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Decision: "approve" or "decline"',
},
},
request: {
url: '/api/tools/jsm/approvals',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
issueIdOrKey: params.issueIdOrKey,
approvalId: params.approvalId,
decision: params.decision,
action: 'answer',
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
issueIdOrKey: '',
approvalId: '',
decision: '',
success: false,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
issueIdOrKey: '',
approvalId: '',
decision: '',
success: false,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
approvalId: { type: 'string', description: 'Approval ID' },
decision: { type: 'string', description: 'Decision made (approve/decline)' },
success: { type: 'boolean', description: 'Whether the operation succeeded' },
},
}

View File

@@ -0,0 +1,100 @@
import type { JsmCreateOrganizationParams, JsmCreateOrganizationResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmCreateOrganizationTool: ToolConfig<
JsmCreateOrganizationParams,
JsmCreateOrganizationResponse
> = {
id: 'jsm_create_organization',
name: 'JSM Create Organization',
description: 'Create a new organization in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the organization to create',
},
},
request: {
url: '/api/tools/jsm/organization',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
name: params.name,
action: 'create',
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
organizationId: '',
name: '',
success: false,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
organizationId: '',
name: '',
success: false,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
organizationId: { type: 'string', description: 'ID of the created organization' },
name: { type: 'string', description: 'Name of the created organization' },
success: { type: 'boolean', description: 'Whether the operation succeeded' },
},
}

View File

@@ -0,0 +1,134 @@
import type { JsmCreateRequestParams, JsmCreateRequestResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmCreateRequestTool: ToolConfig<JsmCreateRequestParams, JsmCreateRequestResponse> = {
id: 'jsm_create_request',
name: 'JSM Create Request',
description: 'Create a new service request in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
serviceDeskId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Service Desk ID to create the request in',
},
requestTypeId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Request Type ID for the new request',
},
summary: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Summary/title for the service request',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Description for the service request',
},
raiseOnBehalfOf: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Account ID of customer to raise request on behalf of',
},
},
request: {
url: '/api/tools/jsm/request',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
serviceDeskId: params.serviceDeskId,
requestTypeId: params.requestTypeId,
summary: params.summary,
description: params.description,
raiseOnBehalfOf: params.raiseOnBehalfOf,
requestFieldValues: params.requestFieldValues,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
issueId: '',
issueKey: '',
requestTypeId: '',
serviceDeskId: '',
success: false,
url: '',
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
issueId: '',
issueKey: '',
requestTypeId: '',
serviceDeskId: '',
success: false,
url: '',
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
issueId: { type: 'string', description: 'Created request issue ID' },
issueKey: { type: 'string', description: 'Created request issue key (e.g., SD-123)' },
requestTypeId: { type: 'string', description: 'Request type ID' },
serviceDeskId: { type: 'string', description: 'Service desk ID' },
success: { type: 'boolean', description: 'Whether the request was created successfully' },
url: { type: 'string', description: 'URL to the created request' },
},
}

View File

@@ -0,0 +1,114 @@
import type { JsmGetApprovalsParams, JsmGetApprovalsResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmGetApprovalsTool: ToolConfig<JsmGetApprovalsParams, JsmGetApprovalsResponse> = {
id: 'jsm_get_approvals',
name: 'JSM Get Approvals',
description: 'Get approvals for a request in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
issueIdOrKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue ID or key (e.g., SD-123)',
},
start: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Start index for pagination (default: 0)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum results to return (default: 50)',
},
},
request: {
url: '/api/tools/jsm/approvals',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
issueIdOrKey: params.issueIdOrKey,
start: params.start,
limit: params.limit,
action: 'get',
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
issueIdOrKey: '',
approvals: [],
total: 0,
isLastPage: true,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
issueIdOrKey: '',
approvals: [],
total: 0,
isLastPage: true,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
approvals: { type: 'json', description: 'Array of approvals' },
total: { type: 'number', description: 'Total number of approvals' },
isLastPage: { type: 'boolean', description: 'Whether this is the last page' },
},
}

View File

@@ -0,0 +1,127 @@
import type { JsmGetCommentsParams, JsmGetCommentsResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmGetCommentsTool: ToolConfig<JsmGetCommentsParams, JsmGetCommentsResponse> = {
id: 'jsm_get_comments',
name: 'JSM Get Comments',
description: 'Get comments for a service request in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
issueIdOrKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue ID or key (e.g., SD-123)',
},
isPublic: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Filter to only public comments',
},
internal: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Filter to only internal comments',
},
start: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Start index for pagination (default: 0)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum results to return (default: 50)',
},
},
request: {
url: '/api/tools/jsm/comments',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
issueIdOrKey: params.issueIdOrKey,
isPublic: params.isPublic,
internal: params.internal,
start: params.start,
limit: params.limit,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
issueIdOrKey: '',
comments: [],
total: 0,
isLastPage: true,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
issueIdOrKey: '',
comments: [],
total: 0,
isLastPage: true,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
comments: { type: 'json', description: 'Array of comments' },
total: { type: 'number', description: 'Total number of comments' },
isLastPage: { type: 'boolean', description: 'Whether this is the last page' },
},
}

View File

@@ -0,0 +1,117 @@
import type { JsmGetCustomersParams, JsmGetCustomersResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmGetCustomersTool: ToolConfig<JsmGetCustomersParams, JsmGetCustomersResponse> = {
id: 'jsm_get_customers',
name: 'JSM Get Customers',
description: 'Get customers for a service desk in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
serviceDeskId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Service Desk ID to get customers for',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search query to filter customers',
},
start: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Start index for pagination (default: 0)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum results to return (default: 50)',
},
},
request: {
url: '/api/tools/jsm/customers',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
serviceDeskId: params.serviceDeskId,
query: params.query,
start: params.start,
limit: params.limit,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
customers: [],
total: 0,
isLastPage: true,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
customers: [],
total: 0,
isLastPage: true,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
customers: { type: 'json', description: 'Array of customers' },
total: { type: 'number', description: 'Total number of customers' },
isLastPage: { type: 'boolean', description: 'Whether this is the last page' },
},
}

View File

@@ -0,0 +1,113 @@
import type { JsmGetOrganizationsParams, JsmGetOrganizationsResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmGetOrganizationsTool: ToolConfig<
JsmGetOrganizationsParams,
JsmGetOrganizationsResponse
> = {
id: 'jsm_get_organizations',
name: 'JSM Get Organizations',
description: 'Get organizations for a service desk in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
serviceDeskId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Service Desk ID to get organizations for',
},
start: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Start index for pagination (default: 0)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum results to return (default: 50)',
},
},
request: {
url: '/api/tools/jsm/organizations',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
serviceDeskId: params.serviceDeskId,
start: params.start,
limit: params.limit,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
organizations: [],
total: 0,
isLastPage: true,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
organizations: [],
total: 0,
isLastPage: true,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
organizations: { type: 'json', description: 'Array of organizations' },
total: { type: 'number', description: 'Total number of organizations' },
isLastPage: { type: 'boolean', description: 'Whether this is the last page' },
},
}

View File

@@ -0,0 +1,117 @@
import type { JsmGetParticipantsParams, JsmGetParticipantsResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmGetParticipantsTool: ToolConfig<
JsmGetParticipantsParams,
JsmGetParticipantsResponse
> = {
id: 'jsm_get_participants',
name: 'JSM Get Participants',
description: 'Get participants for a request in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
issueIdOrKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue ID or key (e.g., SD-123)',
},
start: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Start index for pagination (default: 0)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum results to return (default: 50)',
},
},
request: {
url: '/api/tools/jsm/participants',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
issueIdOrKey: params.issueIdOrKey,
start: params.start,
limit: params.limit,
action: 'get',
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
issueIdOrKey: '',
participants: [],
total: 0,
isLastPage: true,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
issueIdOrKey: '',
participants: [],
total: 0,
isLastPage: true,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
participants: { type: 'json', description: 'Array of participants' },
total: { type: 'number', description: 'Total number of participants' },
isLastPage: { type: 'boolean', description: 'Whether this is the last page' },
},
}

View File

@@ -0,0 +1,117 @@
import type { JsmGetQueuesParams, JsmGetQueuesResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmGetQueuesTool: ToolConfig<JsmGetQueuesParams, JsmGetQueuesResponse> = {
id: 'jsm_get_queues',
name: 'JSM Get Queues',
description: 'Get queues for a service desk in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
serviceDeskId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Service Desk ID to get queues for',
},
includeCount: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Include issue count for each queue',
},
start: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Start index for pagination (default: 0)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum results to return (default: 50)',
},
},
request: {
url: '/api/tools/jsm/queues',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
serviceDeskId: params.serviceDeskId,
includeCount: params.includeCount,
start: params.start,
limit: params.limit,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
queues: [],
total: 0,
isLastPage: true,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
queues: [],
total: 0,
isLastPage: true,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
queues: { type: 'json', description: 'Array of queues' },
total: { type: 'number', description: 'Total number of queues' },
isLastPage: { type: 'boolean', description: 'Whether this is the last page' },
},
}

View File

@@ -0,0 +1,90 @@
import type { JsmGetRequestParams, JsmGetRequestResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmGetRequestTool: ToolConfig<JsmGetRequestParams, JsmGetRequestResponse> = {
id: 'jsm_get_request',
name: 'JSM Get Request',
description: 'Get a single service request from Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
issueIdOrKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue ID or key (e.g., SD-123)',
},
},
request: {
url: '/api/tools/jsm/request',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
issueIdOrKey: params.issueIdOrKey,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
request: null,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
request: null,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
request: { type: 'json', description: 'The service request object' },
},
}

View File

@@ -0,0 +1,113 @@
import type { JsmGetRequestTypesParams, JsmGetRequestTypesResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmGetRequestTypesTool: ToolConfig<
JsmGetRequestTypesParams,
JsmGetRequestTypesResponse
> = {
id: 'jsm_get_request_types',
name: 'JSM Get Request Types',
description: 'Get request types for a service desk in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
serviceDeskId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Service Desk ID to get request types for',
},
start: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Start index for pagination (default: 0)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum results to return (default: 50)',
},
},
request: {
url: '/api/tools/jsm/requesttypes',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
serviceDeskId: params.serviceDeskId,
start: params.start,
limit: params.limit,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
requestTypes: [],
total: 0,
isLastPage: true,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
requestTypes: [],
total: 0,
isLastPage: true,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
requestTypes: { type: 'json', description: 'Array of request types' },
total: { type: 'number', description: 'Total number of request types' },
isLastPage: { type: 'boolean', description: 'Whether this is the last page' },
},
}

View File

@@ -0,0 +1,132 @@
import type { JsmGetRequestsParams, JsmGetRequestsResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmGetRequestsTool: ToolConfig<JsmGetRequestsParams, JsmGetRequestsResponse> = {
id: 'jsm_get_requests',
name: 'JSM Get Requests',
description: 'Get multiple service requests from Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
serviceDeskId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Filter by service desk ID',
},
requestOwnership: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Filter by ownership: OWNED_REQUESTS, PARTICIPATED_REQUESTS, ORGANIZATION, ALL_REQUESTS',
},
requestStatus: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Filter by status: OPEN, CLOSED, ALL',
},
searchTerm: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search term to filter requests',
},
start: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Start index for pagination (default: 0)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum results to return (default: 50)',
},
},
request: {
url: '/api/tools/jsm/requests',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
serviceDeskId: params.serviceDeskId,
requestOwnership: params.requestOwnership,
requestStatus: params.requestStatus,
searchTerm: params.searchTerm,
start: params.start,
limit: params.limit,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
requests: [],
total: 0,
isLastPage: true,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
requests: [],
total: 0,
isLastPage: true,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
requests: { type: 'json', description: 'Array of service requests' },
total: { type: 'number', description: 'Total number of requests' },
isLastPage: { type: 'boolean', description: 'Whether this is the last page' },
},
}

View File

@@ -0,0 +1,106 @@
import type { JsmGetServiceDesksParams, JsmGetServiceDesksResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmGetServiceDesksTool: ToolConfig<
JsmGetServiceDesksParams,
JsmGetServiceDesksResponse
> = {
id: 'jsm_get_service_desks',
name: 'JSM Get Service Desks',
description: 'Get all service desks from Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
start: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Start index for pagination (default: 0)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum results to return (default: 50)',
},
},
request: {
url: '/api/tools/jsm/servicedesks',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
start: params.start,
limit: params.limit,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
serviceDesks: [],
total: 0,
isLastPage: true,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
serviceDesks: [],
total: 0,
isLastPage: true,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
serviceDesks: { type: 'json', description: 'Array of service desks' },
total: { type: 'number', description: 'Total number of service desks' },
isLastPage: { type: 'boolean', description: 'Whether this is the last page' },
},
}

View File

@@ -0,0 +1,113 @@
import type { JsmGetSlaParams, JsmGetSlaResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmGetSlaTool: ToolConfig<JsmGetSlaParams, JsmGetSlaResponse> = {
id: 'jsm_get_sla',
name: 'JSM Get SLA',
description: 'Get SLA information for a service request in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
issueIdOrKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue ID or key (e.g., SD-123)',
},
start: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Start index for pagination (default: 0)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum results to return (default: 50)',
},
},
request: {
url: '/api/tools/jsm/sla',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
issueIdOrKey: params.issueIdOrKey,
start: params.start,
limit: params.limit,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
issueIdOrKey: '',
slas: [],
total: 0,
isLastPage: true,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
issueIdOrKey: '',
slas: [],
total: 0,
isLastPage: true,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
slas: { type: 'json', description: 'Array of SLA information' },
total: { type: 'number', description: 'Total number of SLAs' },
isLastPage: { type: 'boolean', description: 'Whether this is the last page' },
},
}

View File

@@ -0,0 +1,94 @@
import type { JsmGetTransitionsParams, JsmGetTransitionsResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmGetTransitionsTool: ToolConfig<JsmGetTransitionsParams, JsmGetTransitionsResponse> =
{
id: 'jsm_get_transitions',
name: 'JSM Get Transitions',
description: 'Get available transitions for a service request in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
issueIdOrKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue ID or key (e.g., SD-123)',
},
},
request: {
url: '/api/tools/jsm/transitions',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
issueIdOrKey: params.issueIdOrKey,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
issueIdOrKey: '',
transitions: [],
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
issueIdOrKey: '',
transitions: [],
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
transitions: { type: 'json', description: 'Array of available transitions' },
},
}

View File

@@ -0,0 +1,43 @@
import { jsmAddCommentTool } from '@/tools/jsm/add_comment'
import { jsmAddCustomerTool } from '@/tools/jsm/add_customer'
import { jsmAddOrganizationToServiceDeskTool } from '@/tools/jsm/add_organization_to_service_desk'
import { jsmAddParticipantsTool } from '@/tools/jsm/add_participants'
import { jsmAnswerApprovalTool } from '@/tools/jsm/answer_approval'
import { jsmCreateOrganizationTool } from '@/tools/jsm/create_organization'
import { jsmCreateRequestTool } from '@/tools/jsm/create_request'
import { jsmGetApprovalsTool } from '@/tools/jsm/get_approvals'
import { jsmGetCommentsTool } from '@/tools/jsm/get_comments'
import { jsmGetCustomersTool } from '@/tools/jsm/get_customers'
import { jsmGetOrganizationsTool } from '@/tools/jsm/get_organizations'
import { jsmGetParticipantsTool } from '@/tools/jsm/get_participants'
import { jsmGetQueuesTool } from '@/tools/jsm/get_queues'
import { jsmGetRequestTool } from '@/tools/jsm/get_request'
import { jsmGetRequestTypesTool } from '@/tools/jsm/get_request_types'
import { jsmGetRequestsTool } from '@/tools/jsm/get_requests'
import { jsmGetServiceDesksTool } from '@/tools/jsm/get_service_desks'
import { jsmGetSlaTool } from '@/tools/jsm/get_sla'
import { jsmGetTransitionsTool } from '@/tools/jsm/get_transitions'
import { jsmTransitionRequestTool } from '@/tools/jsm/transition_request'
export {
jsmAddCommentTool,
jsmAddCustomerTool,
jsmAddOrganizationToServiceDeskTool,
jsmAddParticipantsTool,
jsmAnswerApprovalTool,
jsmCreateOrganizationTool,
jsmCreateRequestTool,
jsmGetApprovalsTool,
jsmGetCommentsTool,
jsmGetCustomersTool,
jsmGetOrganizationsTool,
jsmGetParticipantsTool,
jsmGetQueuesTool,
jsmGetRequestTool,
jsmGetRequestsTool,
jsmGetRequestTypesTool,
jsmGetServiceDesksTool,
jsmGetSlaTool,
jsmGetTransitionsTool,
jsmTransitionRequestTool,
}

View File

@@ -0,0 +1,113 @@
import type { JsmTransitionRequestParams, JsmTransitionRequestResponse } from '@/tools/jsm/types'
import type { ToolConfig } from '@/tools/types'
export const jsmTransitionRequestTool: ToolConfig<
JsmTransitionRequestParams,
JsmTransitionRequestResponse
> = {
id: 'jsm_transition_request',
name: 'JSM Transition Request',
description: 'Transition a service request to a new status in Jira Service Management',
version: '1.0.0',
oauth: {
required: true,
provider: 'jira-service-management',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Jira Service Management',
},
domain: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
},
cloudId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Jira Cloud ID for the instance',
},
issueIdOrKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue ID or key (e.g., SD-123)',
},
transitionId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Transition ID to apply',
},
comment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional comment to add during transition',
},
},
request: {
url: '/api/tools/jsm/transition',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
domain: params.domain,
accessToken: params.accessToken,
cloudId: params.cloudId,
issueIdOrKey: params.issueIdOrKey,
transitionId: params.transitionId,
comment: params.comment,
}),
},
transformResponse: async (response: Response) => {
const responseText = await response.text()
if (!responseText) {
return {
success: false,
output: {
ts: new Date().toISOString(),
issueIdOrKey: '',
transitionId: '',
success: false,
},
error: 'Empty response from API',
}
}
const data = JSON.parse(responseText)
if (data.success && data.output) {
return data
}
return {
success: data.success || false,
output: data.output || {
ts: new Date().toISOString(),
issueIdOrKey: '',
transitionId: '',
success: false,
},
error: data.error,
}
},
outputs: {
ts: { type: 'string', description: 'Timestamp of the operation' },
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
transitionId: { type: 'string', description: 'Applied transition ID' },
success: { type: 'boolean', description: 'Whether the transition was successful' },
},
}

469
apps/sim/tools/jsm/types.ts Normal file
View File

@@ -0,0 +1,469 @@
import type { ToolResponse } from '@/tools/types'
/** Common parameters for all JSM API calls */
export interface JsmBaseParams {
accessToken: string
domain: string
cloudId?: string
}
/** Service Desk representation */
export interface JsmServiceDesk {
id: string
projectId: string
projectName: string
projectKey: string
}
/** Request Type representation */
export interface JsmRequestType {
id: string
name: string
description: string
helpText?: string
serviceDeskId: string
groupIds: string[]
icon: {
id: string
name: string
}
}
/** Customer representation */
export interface JsmCustomer {
accountId: string
name: string
key: string
emailAddress: string
displayName: string
active: boolean
timeZone: string
}
/** Organization representation */
export interface JsmOrganization {
id: string
name: string
}
/** Queue representation */
export interface JsmQueue {
id: string
name: string
jql: string
fields: string[]
issueCount: number
}
/** SLA representation */
export interface JsmSla {
id: string
name: string
completedCycles: Array<{
startTime: { iso8601: string }
stopTime: { iso8601: string }
breached: boolean
}>
ongoingCycle?: {
startTime: { iso8601: string }
breachTime?: { iso8601: string }
breached: boolean
paused: boolean
withinCalendarHours: boolean
goalDuration?: { millis: number; friendly: string }
elapsedTime?: { millis: number; friendly: string }
remainingTime?: { millis: number; friendly: string }
}
}
/** Request (ticket) representation */
export interface JsmRequest {
issueId: string
issueKey: string
requestTypeId: string
serviceDeskId: string
createdDate: { iso8601: string; friendly: string }
reporter: JsmCustomer
requestFieldValues: Array<{
fieldId: string
label: string
value: unknown
}>
currentStatus: {
status: string
statusCategory: string
statusDate: { iso8601: string; friendly: string }
}
}
/** Comment representation */
export interface JsmComment {
id: string
body: string
public: boolean
author: {
accountId: string
displayName: string
emailAddress?: string
}
created: { iso8601: string; friendly: string }
}
/** Transition representation */
export interface JsmTransition {
id: string
name: string
}
export interface JsmGetServiceDesksParams extends JsmBaseParams {
start?: number
limit?: number
}
export interface JsmGetServiceDesksResponse extends ToolResponse {
output: {
ts: string
serviceDesks: JsmServiceDesk[]
total: number
isLastPage: boolean
}
}
export interface JsmGetRequestTypesParams extends JsmBaseParams {
serviceDeskId: string
start?: number
limit?: number
}
export interface JsmGetRequestTypesResponse extends ToolResponse {
output: {
ts: string
requestTypes: JsmRequestType[]
total: number
isLastPage: boolean
}
}
export interface JsmCreateRequestParams extends JsmBaseParams {
serviceDeskId: string
requestTypeId: string
summary: string
description?: string
requestFieldValues?: Record<string, unknown>
raiseOnBehalfOf?: string
}
export interface JsmCreateRequestResponse extends ToolResponse {
output: {
ts: string
issueId: string
issueKey: string
requestTypeId: string
serviceDeskId: string
success: boolean
url: string
}
}
export interface JsmGetRequestParams extends JsmBaseParams {
issueIdOrKey: string
}
export interface JsmGetRequestResponse extends ToolResponse {
output: {
ts: string
request: JsmRequest
}
}
export interface JsmGetRequestsParams extends JsmBaseParams {
serviceDeskId?: string
requestOwnership?: 'OWNED_REQUESTS' | 'PARTICIPATED_REQUESTS' | 'ORGANIZATION' | 'ALL_REQUESTS'
requestStatus?: 'OPEN' | 'CLOSED' | 'ALL'
searchTerm?: string
start?: number
limit?: number
}
export interface JsmGetRequestsResponse extends ToolResponse {
output: {
ts: string
requests: JsmRequest[]
total: number
isLastPage: boolean
}
}
export interface JsmAddCommentParams extends JsmBaseParams {
issueIdOrKey: string
body: string
isPublic: boolean
}
export interface JsmAddCommentResponse extends ToolResponse {
output: {
ts: string
issueIdOrKey: string
commentId: string
body: string
isPublic: boolean
success: boolean
}
}
export interface JsmGetCommentsParams extends JsmBaseParams {
issueIdOrKey: string
isPublic?: boolean
internal?: boolean
start?: number
limit?: number
}
export interface JsmGetCommentsResponse extends ToolResponse {
output: {
ts: string
issueIdOrKey: string
comments: JsmComment[]
total: number
isLastPage: boolean
}
}
export interface JsmGetCustomersParams extends JsmBaseParams {
serviceDeskId: string
query?: string
start?: number
limit?: number
}
export interface JsmGetCustomersResponse extends ToolResponse {
output: {
ts: string
customers: JsmCustomer[]
total: number
isLastPage: boolean
}
}
export interface JsmAddCustomerParams extends JsmBaseParams {
serviceDeskId: string
emails: string
}
export interface JsmAddCustomerResponse extends ToolResponse {
output: {
ts: string
serviceDeskId: string
success: boolean
}
}
export interface JsmGetOrganizationsParams extends JsmBaseParams {
serviceDeskId: string
start?: number
limit?: number
}
export interface JsmGetOrganizationsResponse extends ToolResponse {
output: {
ts: string
organizations: JsmOrganization[]
total: number
isLastPage: boolean
}
}
export interface JsmGetQueuesParams extends JsmBaseParams {
serviceDeskId: string
includeCount?: boolean
start?: number
limit?: number
}
export interface JsmGetQueuesResponse extends ToolResponse {
output: {
ts: string
queues: JsmQueue[]
total: number
isLastPage: boolean
}
}
export interface JsmGetSlaParams extends JsmBaseParams {
issueIdOrKey: string
start?: number
limit?: number
}
export interface JsmGetSlaResponse extends ToolResponse {
output: {
ts: string
issueIdOrKey: string
slas: JsmSla[]
total: number
isLastPage: boolean
}
}
export interface JsmTransitionRequestParams extends JsmBaseParams {
issueIdOrKey: string
transitionId: string
comment?: string
}
export interface JsmTransitionRequestResponse extends ToolResponse {
output: {
ts: string
issueIdOrKey: string
transitionId: string
success: boolean
}
}
export interface JsmGetTransitionsParams extends JsmBaseParams {
issueIdOrKey: string
}
export interface JsmGetTransitionsResponse extends ToolResponse {
output: {
ts: string
issueIdOrKey: string
transitions: JsmTransition[]
}
}
export interface JsmCreateOrganizationParams extends JsmBaseParams {
name: string
}
export interface JsmCreateOrganizationResponse extends ToolResponse {
output: {
ts: string
organizationId: string
name: string
success: boolean
}
}
export interface JsmAddOrganizationToServiceDeskParams extends JsmBaseParams {
serviceDeskId: string
organizationId: string
}
export interface JsmAddOrganizationToServiceDeskResponse extends ToolResponse {
output: {
ts: string
serviceDeskId: string
organizationId: string
success: boolean
}
}
export interface JsmParticipant {
accountId: string
displayName: string
emailAddress?: string
active: boolean
}
export interface JsmGetParticipantsParams extends JsmBaseParams {
issueIdOrKey: string
start?: number
limit?: number
}
export interface JsmGetParticipantsResponse extends ToolResponse {
output: {
ts: string
issueIdOrKey: string
participants: JsmParticipant[]
total: number
isLastPage: boolean
}
}
export interface JsmAddParticipantsParams extends JsmBaseParams {
issueIdOrKey: string
accountIds: string
}
export interface JsmAddParticipantsResponse extends ToolResponse {
output: {
ts: string
issueIdOrKey: string
participants: JsmParticipant[]
success: boolean
}
}
export interface JsmApprover {
accountId: string
displayName: string
emailAddress?: string
approverDecision: 'pending' | 'approved' | 'declined'
}
export interface JsmApproval {
id: string
name: string
finalDecision: 'pending' | 'approved' | 'declined'
canAnswerApproval: boolean
approvers: JsmApprover[]
createdDate?: { iso8601: string; friendly: string }
completedDate?: { iso8601: string; friendly: string }
}
export interface JsmGetApprovalsParams extends JsmBaseParams {
issueIdOrKey: string
start?: number
limit?: number
}
export interface JsmGetApprovalsResponse extends ToolResponse {
output: {
ts: string
issueIdOrKey: string
approvals: JsmApproval[]
total: number
isLastPage: boolean
}
}
export interface JsmAnswerApprovalParams extends JsmBaseParams {
issueIdOrKey: string
approvalId: string
decision: 'approve' | 'decline'
}
export interface JsmAnswerApprovalResponse extends ToolResponse {
output: {
ts: string
issueIdOrKey: string
approvalId: string
decision: string
success: boolean
}
}
/** Union type for all JSM responses */
export type JsmResponse =
| JsmGetServiceDesksResponse
| JsmGetRequestTypesResponse
| JsmCreateRequestResponse
| JsmGetRequestResponse
| JsmGetRequestsResponse
| JsmAddCommentResponse
| JsmGetCommentsResponse
| JsmGetCustomersResponse
| JsmAddCustomerResponse
| JsmGetOrganizationsResponse
| JsmGetQueuesResponse
| JsmGetSlaResponse
| JsmTransitionRequestResponse
| JsmGetTransitionsResponse
| JsmCreateOrganizationResponse
| JsmAddOrganizationToServiceDeskResponse
| JsmGetParticipantsResponse
| JsmAddParticipantsResponse
| JsmGetApprovalsResponse
| JsmAnswerApprovalResponse

View File

@@ -0,0 +1,28 @@
/**
* Shared utilities for Jira Service Management tools
* Reuses the getJiraCloudId from the Jira integration since JSM uses the same Atlassian Cloud ID
*/
export { getJiraCloudId } from '@/tools/jira/utils'
/**
* Build the base URL for JSM Service Desk API
* @param cloudId - The Jira Cloud ID
* @returns The base URL for the Service Desk API
*/
export function getJsmApiBaseUrl(cloudId: string): string {
return `https://api.atlassian.com/ex/jira/${cloudId}/rest/servicedeskapi`
}
/**
* Build common headers for JSM API requests
* @param accessToken - The OAuth access token
* @returns Headers object for API requests
*/
export function getJsmHeaders(accessToken: string): Record<string, string> {
return {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/json',
'Content-Type': 'application/json',
'X-ExperimentalApi': 'opt-in',
}
}

View File

@@ -491,6 +491,28 @@ import {
jiraUpdateWorklogTool,
jiraWriteTool,
} from '@/tools/jira'
import {
jsmAddCommentTool,
jsmAddCustomerTool,
jsmAddOrganizationToServiceDeskTool,
jsmAddParticipantsTool,
jsmAnswerApprovalTool,
jsmCreateOrganizationTool,
jsmCreateRequestTool,
jsmGetApprovalsTool,
jsmGetCommentsTool,
jsmGetCustomersTool,
jsmGetOrganizationsTool,
jsmGetParticipantsTool,
jsmGetQueuesTool,
jsmGetRequestsTool,
jsmGetRequestTool,
jsmGetRequestTypesTool,
jsmGetServiceDesksTool,
jsmGetSlaTool,
jsmGetTransitionsTool,
jsmTransitionRequestTool,
} from '@/tools/jsm'
import {
kalshiAmendOrderTool,
kalshiCancelOrderTool,
@@ -1498,6 +1520,26 @@ export const tools: Record<string, ToolConfig> = {
jira_add_watcher: jiraAddWatcherTool,
jira_remove_watcher: jiraRemoveWatcherTool,
jira_get_users: jiraGetUsersTool,
jsm_get_service_desks: jsmGetServiceDesksTool,
jsm_get_request_types: jsmGetRequestTypesTool,
jsm_create_request: jsmCreateRequestTool,
jsm_get_request: jsmGetRequestTool,
jsm_get_requests: jsmGetRequestsTool,
jsm_add_comment: jsmAddCommentTool,
jsm_get_comments: jsmGetCommentsTool,
jsm_get_customers: jsmGetCustomersTool,
jsm_add_customer: jsmAddCustomerTool,
jsm_get_organizations: jsmGetOrganizationsTool,
jsm_create_organization: jsmCreateOrganizationTool,
jsm_add_organization_to_service_desk: jsmAddOrganizationToServiceDeskTool,
jsm_get_queues: jsmGetQueuesTool,
jsm_get_sla: jsmGetSlaTool,
jsm_get_transitions: jsmGetTransitionsTool,
jsm_transition_request: jsmTransitionRequestTool,
jsm_get_participants: jsmGetParticipantsTool,
jsm_add_participants: jsmAddParticipantsTool,
jsm_get_approvals: jsmGetApprovalsTool,
jsm_answer_approval: jsmAnswerApprovalTool,
kalshi_get_markets: kalshiGetMarketsTool,
kalshi_get_market: kalshiGetMarketTool,
kalshi_get_events: kalshiGetEventsTool,