mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
feat(jsm): add ProForma/JSM Forms discovery tools (#4078)
* feat(jsm): add ProForma/JSM Forms discovery tools
Add three new tools for discovering and inspecting JSM Forms (ProForma) templates
and their structure, enabling dynamic form-based workflows:
- jsm_get_form_templates: List form templates in a project with request type bindings
- jsm_get_form_structure: Get full form design (questions, layout, conditions, sections)
- jsm_get_issue_forms: List forms attached to an issue with submission status
All endpoints validated against the official Atlassian Forms REST API OpenAPI spec.
Uses the Forms Cloud API base URL (jira/forms/cloud/{cloudId}) with X-ExperimentalApi header.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(jsm): add input validation and extract shared error parser
- Add validateJiraIssueKey for projectIdOrKey in templates and structure routes
- Add validateJiraCloudId for formId (UUID) in structure route
- Extract parseJsmErrorMessage to shared utils.ts (was duplicated across 3 routes)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(jsm): remove unused FORM_QUESTION_PROPERTIES constant
Dead code — the get_form_structure tool passes the raw design object
through as JSON, so this output constant had no consumers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -678,4 +678,84 @@ Get the fields required to create a request of a specific type in Jira Service M
|
||||
| ↳ `defaultValues` | json | Default values for the field |
|
||||
| ↳ `jiraSchema` | json | Jira field schema with type, system, custom, customId |
|
||||
|
||||
### `jsm_get_form_templates`
|
||||
|
||||
List forms (ProForma/JSM Forms) in a Jira project to discover form IDs for request types
|
||||
|
||||
#### 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 |
|
||||
| `projectIdOrKey` | string | Yes | Jira project ID or key \(e.g., "10001" or "SD"\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | Timestamp of the operation |
|
||||
| `projectIdOrKey` | string | Project ID or key |
|
||||
| `templates` | array | List of forms in the project |
|
||||
| ↳ `id` | string | Form template ID \(UUID\) |
|
||||
| ↳ `name` | string | Form template name |
|
||||
| ↳ `updated` | string | Last updated timestamp \(ISO 8601\) |
|
||||
| ↳ `issueCreateIssueTypeIds` | json | Issue type IDs that auto-attach this form on issue create |
|
||||
| ↳ `issueCreateRequestTypeIds` | json | Request type IDs that auto-attach this form on issue create |
|
||||
| ↳ `portalRequestTypeIds` | json | Request type IDs that show this form on the customer portal |
|
||||
| ↳ `recommendedIssueRequestTypeIds` | json | Request type IDs that recommend this form |
|
||||
| `total` | number | Total number of forms |
|
||||
|
||||
### `jsm_get_form_structure`
|
||||
|
||||
Get the full structure of a ProForma/JSM form including all questions, field types, choices, layout, and conditions
|
||||
|
||||
#### 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 |
|
||||
| `projectIdOrKey` | string | Yes | Jira project ID or key \(e.g., "10001" or "SD"\) |
|
||||
| `formId` | string | Yes | Form ID \(UUID from Get Form Templates\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | Timestamp of the operation |
|
||||
| `projectIdOrKey` | string | Project ID or key |
|
||||
| `formId` | string | Form ID |
|
||||
| `design` | json | Full form design with questions \(field types, labels, choices, validation\), layout \(field ordering\), and conditions |
|
||||
| `updated` | string | Last updated timestamp |
|
||||
| `publish` | json | Publishing and request type configuration |
|
||||
|
||||
### `jsm_get_issue_forms`
|
||||
|
||||
List forms (ProForma/JSM Forms) attached to a Jira issue with metadata (name, submitted status, lock)
|
||||
|
||||
#### 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", "10001"\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | Timestamp of the operation |
|
||||
| `issueIdOrKey` | string | Issue ID or key |
|
||||
| `forms` | array | List of forms attached to the issue |
|
||||
| ↳ `id` | string | Form instance ID \(UUID\) |
|
||||
| ↳ `name` | string | Form name |
|
||||
| ↳ `updated` | string | Last updated timestamp \(ISO 8601\) |
|
||||
| ↳ `submitted` | boolean | Whether the form has been submitted |
|
||||
| ↳ `lock` | boolean | Whether the form is locked |
|
||||
| ↳ `internal` | boolean | Whether the form is internal-only |
|
||||
| ↳ `formTemplateId` | string | Source form template ID \(UUID\) |
|
||||
| `total` | number | Total number of forms |
|
||||
|
||||
|
||||
|
||||
@@ -6614,9 +6614,21 @@
|
||||
{
|
||||
"name": "Get Request Type Fields",
|
||||
"description": "Get the fields required to create a request of a specific type in Jira Service Management"
|
||||
},
|
||||
{
|
||||
"name": "Get Form Templates",
|
||||
"description": "List forms (ProForma/JSM Forms) in a Jira project to discover form IDs for request types"
|
||||
},
|
||||
{
|
||||
"name": "Get Form Structure",
|
||||
"description": "Get the full structure of a ProForma/JSM form including all questions, field types, choices, layout, and conditions"
|
||||
},
|
||||
{
|
||||
"name": "Get Issue Forms",
|
||||
"description": "List forms (ProForma/JSM Forms) attached to a Jira issue with metadata (name, submitted status, lock)"
|
||||
}
|
||||
],
|
||||
"operationCount": 21,
|
||||
"operationCount": 24,
|
||||
"triggers": [],
|
||||
"triggerCount": 0,
|
||||
"authType": "oauth",
|
||||
|
||||
115
apps/sim/app/api/tools/jsm/forms/issue/route.ts
Normal file
115
apps/sim/app/api/tools/jsm/forms/issue/route.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import {
|
||||
getJiraCloudId,
|
||||
getJsmFormsApiBaseUrl,
|
||||
getJsmHeaders,
|
||||
parseJsmErrorMessage,
|
||||
} from '@/tools/jsm/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('JsmIssueFormsAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await checkInternalAuth(request)
|
||||
if (!auth.success || !auth.userId) {
|
||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { domain, accessToken, cloudId: cloudIdParam, issueIdOrKey } = 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 cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
||||
if (!cloudIdValidation.isValid) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
||||
if (!issueIdOrKeyValidation.isValid) {
|
||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
|
||||
const url = `${baseUrl}/issue/${encodeURIComponent(issueIdOrKey)}/form`
|
||||
|
||||
logger.info('Fetching issue forms from:', { url, issueIdOrKey })
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: getJsmHeaders(accessToken),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('JSM Forms API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: parseJsmErrorMessage(response.status, response.statusText, errorText),
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
const forms = Array.isArray(data) ? data : (data.values ?? data.forms ?? [])
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey,
|
||||
forms: forms.map((form: Record<string, unknown>) => ({
|
||||
id: form.id ?? null,
|
||||
name: form.name ?? null,
|
||||
updated: form.updated ?? null,
|
||||
submitted: form.submitted ?? false,
|
||||
lock: form.lock ?? false,
|
||||
internal: form.internal ?? null,
|
||||
formTemplateId: (form.formTemplate as Record<string, unknown>)?.id ?? null,
|
||||
})),
|
||||
total: forms.length,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching issue forms:', {
|
||||
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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
117
apps/sim/app/api/tools/jsm/forms/structure/route.ts
Normal file
117
apps/sim/app/api/tools/jsm/forms/structure/route.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import {
|
||||
getJiraCloudId,
|
||||
getJsmFormsApiBaseUrl,
|
||||
getJsmHeaders,
|
||||
parseJsmErrorMessage,
|
||||
} from '@/tools/jsm/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('JsmFormStructureAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await checkInternalAuth(request)
|
||||
if (!auth.success || !auth.userId) {
|
||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { domain, accessToken, cloudId: cloudIdParam, projectIdOrKey, formId } = 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 (!projectIdOrKey) {
|
||||
logger.error('Missing projectIdOrKey in request')
|
||||
return NextResponse.json({ error: 'Project ID or key is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!formId) {
|
||||
logger.error('Missing formId in request')
|
||||
return NextResponse.json({ error: 'Form ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
||||
|
||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
||||
if (!cloudIdValidation.isValid) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const projectIdOrKeyValidation = validateJiraIssueKey(projectIdOrKey, 'projectIdOrKey')
|
||||
if (!projectIdOrKeyValidation.isValid) {
|
||||
return NextResponse.json({ error: projectIdOrKeyValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const formIdValidation = validateJiraCloudId(formId, 'formId')
|
||||
if (!formIdValidation.isValid) {
|
||||
return NextResponse.json({ error: formIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
|
||||
const url = `${baseUrl}/project/${encodeURIComponent(projectIdOrKey)}/form/${encodeURIComponent(formId)}`
|
||||
|
||||
logger.info('Fetching form template from:', { url, projectIdOrKey, formId })
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: getJsmHeaders(accessToken),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('JSM Forms API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: parseJsmErrorMessage(response.status, response.statusText, errorText),
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
projectIdOrKey,
|
||||
formId,
|
||||
design: data.design ?? null,
|
||||
updated: data.updated ?? null,
|
||||
publish: data.publish ?? null,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching form structure:', {
|
||||
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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
115
apps/sim/app/api/tools/jsm/forms/templates/route.ts
Normal file
115
apps/sim/app/api/tools/jsm/forms/templates/route.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import {
|
||||
getJiraCloudId,
|
||||
getJsmFormsApiBaseUrl,
|
||||
getJsmHeaders,
|
||||
parseJsmErrorMessage,
|
||||
} from '@/tools/jsm/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('JsmFormTemplatesAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await checkInternalAuth(request)
|
||||
if (!auth.success || !auth.userId) {
|
||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { domain, accessToken, cloudId: cloudIdParam, projectIdOrKey } = 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 (!projectIdOrKey) {
|
||||
logger.error('Missing projectIdOrKey in request')
|
||||
return NextResponse.json({ error: 'Project ID or key is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
||||
|
||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
||||
if (!cloudIdValidation.isValid) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const projectIdOrKeyValidation = validateJiraIssueKey(projectIdOrKey, 'projectIdOrKey')
|
||||
if (!projectIdOrKeyValidation.isValid) {
|
||||
return NextResponse.json({ error: projectIdOrKeyValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
|
||||
const url = `${baseUrl}/project/${encodeURIComponent(projectIdOrKey)}/form`
|
||||
|
||||
logger.info('Fetching project form templates from:', { url, projectIdOrKey })
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: getJsmHeaders(accessToken),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('JSM Forms API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: parseJsmErrorMessage(response.status, response.statusText, errorText),
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
const templates = Array.isArray(data) ? data : (data.values ?? [])
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
projectIdOrKey,
|
||||
templates: templates.map((template: Record<string, unknown>) => ({
|
||||
id: template.id ?? null,
|
||||
name: template.name ?? null,
|
||||
updated: template.updated ?? null,
|
||||
issueCreateIssueTypeIds: template.issueCreateIssueTypeIds ?? [],
|
||||
issueCreateRequestTypeIds: template.issueCreateRequestTypeIds ?? [],
|
||||
portalRequestTypeIds: template.portalRequestTypeIds ?? [],
|
||||
recommendedIssueRequestTypeIds: template.recommendedIssueRequestTypeIds ?? [],
|
||||
})),
|
||||
total: templates.length,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching form templates:', {
|
||||
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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,9 @@ export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
|
||||
{ label: 'Get Approvals', id: 'get_approvals' },
|
||||
{ label: 'Answer Approval', id: 'answer_approval' },
|
||||
{ label: 'Get Request Type Fields', id: 'get_request_type_fields' },
|
||||
{ label: 'Get Form Templates', id: 'get_form_templates' },
|
||||
{ label: 'Get Form Structure', id: 'get_form_structure' },
|
||||
{ label: 'Get Issue Forms', id: 'get_issue_forms' },
|
||||
],
|
||||
value: () => 'get_service_desks',
|
||||
},
|
||||
@@ -191,9 +194,26 @@ export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
|
||||
'add_participants',
|
||||
'get_approvals',
|
||||
'answer_approval',
|
||||
'get_issue_forms',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'projectIdOrKey',
|
||||
title: 'Project ID or Key',
|
||||
type: 'short-input',
|
||||
required: { field: 'operation', value: ['get_form_templates', 'get_form_structure'] },
|
||||
placeholder: 'Enter Jira project ID or key (e.g., 10001 or SD)',
|
||||
condition: { field: 'operation', value: ['get_form_templates', 'get_form_structure'] },
|
||||
},
|
||||
{
|
||||
id: 'formId',
|
||||
title: 'Form ID',
|
||||
type: 'short-input',
|
||||
required: true,
|
||||
placeholder: 'Enter form ID (UUID from Get Form Templates)',
|
||||
condition: { field: 'operation', value: 'get_form_structure' },
|
||||
},
|
||||
{
|
||||
id: 'summary',
|
||||
title: 'Summary',
|
||||
@@ -503,6 +523,9 @@ Return ONLY the comment text - no explanations.`,
|
||||
'jsm_get_approvals',
|
||||
'jsm_answer_approval',
|
||||
'jsm_get_request_type_fields',
|
||||
'jsm_get_form_templates',
|
||||
'jsm_get_form_structure',
|
||||
'jsm_get_issue_forms',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
@@ -549,6 +572,12 @@ Return ONLY the comment text - no explanations.`,
|
||||
return 'jsm_answer_approval'
|
||||
case 'get_request_type_fields':
|
||||
return 'jsm_get_request_type_fields'
|
||||
case 'get_form_templates':
|
||||
return 'jsm_get_form_templates'
|
||||
case 'get_form_structure':
|
||||
return 'jsm_get_form_structure'
|
||||
case 'get_issue_forms':
|
||||
return 'jsm_get_issue_forms'
|
||||
default:
|
||||
return 'jsm_get_service_desks'
|
||||
}
|
||||
@@ -808,6 +837,34 @@ Return ONLY the comment text - no explanations.`,
|
||||
serviceDeskId: params.serviceDeskId,
|
||||
requestTypeId: params.requestTypeId,
|
||||
}
|
||||
case 'get_form_templates':
|
||||
if (!params.projectIdOrKey) {
|
||||
throw new Error('Project ID or key is required')
|
||||
}
|
||||
return {
|
||||
...baseParams,
|
||||
projectIdOrKey: params.projectIdOrKey,
|
||||
}
|
||||
case 'get_form_structure':
|
||||
if (!params.projectIdOrKey) {
|
||||
throw new Error('Project ID or key is required')
|
||||
}
|
||||
if (!params.formId) {
|
||||
throw new Error('Form ID is required')
|
||||
}
|
||||
return {
|
||||
...baseParams,
|
||||
projectIdOrKey: params.projectIdOrKey,
|
||||
formId: params.formId,
|
||||
}
|
||||
case 'get_issue_forms':
|
||||
if (!params.issueIdOrKey) {
|
||||
throw new Error('Issue ID or key is required')
|
||||
}
|
||||
return {
|
||||
...baseParams,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
}
|
||||
default:
|
||||
return baseParams
|
||||
}
|
||||
@@ -857,6 +914,8 @@ Return ONLY the comment text - no explanations.`,
|
||||
type: 'string',
|
||||
description: 'JSON object of form answers for form-based request types',
|
||||
},
|
||||
projectIdOrKey: { type: 'string', description: 'Jira project ID or key' },
|
||||
formId: { type: 'string', description: 'Form ID (UUID)' },
|
||||
searchQuery: { type: 'string', description: 'Filter request types by name' },
|
||||
groupId: { type: 'string', description: 'Filter by request type group ID' },
|
||||
expand: { type: 'string', description: 'Comma-separated fields to expand' },
|
||||
@@ -899,5 +958,25 @@ Return ONLY the comment text - no explanations.`,
|
||||
type: 'boolean',
|
||||
description: 'Whether requests can be raised on behalf of another user',
|
||||
},
|
||||
templates: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of form templates (id, name, updated, portalRequestTypeIds, issueCreateIssueTypeIds)',
|
||||
},
|
||||
design: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Full form design with questions (labels, types, choices, validation), layout, conditions, sections, settings',
|
||||
},
|
||||
publish: {
|
||||
type: 'json',
|
||||
description: 'Form publishing and request type configuration',
|
||||
},
|
||||
updated: { type: 'string', description: 'Last updated timestamp' },
|
||||
forms: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of forms attached to an issue (id, name, updated, submitted, lock, internal, formTemplateId)',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
121
apps/sim/tools/jsm/get_form_structure.ts
Normal file
121
apps/sim/tools/jsm/get_form_structure.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import type { JsmGetFormStructureParams, JsmGetFormStructureResponse } from '@/tools/jsm/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const jsmGetFormStructureTool: ToolConfig<
|
||||
JsmGetFormStructureParams,
|
||||
JsmGetFormStructureResponse
|
||||
> = {
|
||||
id: 'jsm_get_form_structure',
|
||||
name: 'JSM Get Form Structure',
|
||||
description:
|
||||
'Get the full structure of a ProForma/JSM form including all questions, field types, choices, layout, and conditions',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
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',
|
||||
},
|
||||
projectIdOrKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Jira project ID or key (e.g., "10001" or "SD")',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Form ID (UUID from Get Form Templates)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/jsm/forms/structure',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
domain: params.domain,
|
||||
accessToken: params.accessToken,
|
||||
cloudId: params.cloudId,
|
||||
projectIdOrKey: params.projectIdOrKey,
|
||||
formId: params.formId,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
projectIdOrKey: '',
|
||||
formId: '',
|
||||
design: null,
|
||||
updated: null,
|
||||
publish: 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(),
|
||||
projectIdOrKey: '',
|
||||
formId: '',
|
||||
design: null,
|
||||
updated: null,
|
||||
publish: null,
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||
projectIdOrKey: { type: 'string', description: 'Project ID or key' },
|
||||
formId: { type: 'string', description: 'Form ID' },
|
||||
design: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Full form design with questions (field types, labels, choices, validation), layout (field ordering), and conditions',
|
||||
},
|
||||
updated: { type: 'string', description: 'Last updated timestamp', optional: true },
|
||||
publish: {
|
||||
type: 'json',
|
||||
description: 'Publishing and request type configuration',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
108
apps/sim/tools/jsm/get_form_templates.ts
Normal file
108
apps/sim/tools/jsm/get_form_templates.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import type { JsmGetFormTemplatesParams, JsmGetFormTemplatesResponse } from '@/tools/jsm/types'
|
||||
import { FORM_TEMPLATE_PROPERTIES } from '@/tools/jsm/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const jsmGetFormTemplatesTool: ToolConfig<
|
||||
JsmGetFormTemplatesParams,
|
||||
JsmGetFormTemplatesResponse
|
||||
> = {
|
||||
id: 'jsm_get_form_templates',
|
||||
name: 'JSM Get Form Templates',
|
||||
description:
|
||||
'List forms (ProForma/JSM Forms) in a Jira project to discover form IDs for request types',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
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',
|
||||
},
|
||||
projectIdOrKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Jira project ID or key (e.g., "10001" or "SD")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/jsm/forms/templates',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
domain: params.domain,
|
||||
accessToken: params.accessToken,
|
||||
cloudId: params.cloudId,
|
||||
projectIdOrKey: params.projectIdOrKey,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
projectIdOrKey: '',
|
||||
templates: [],
|
||||
total: 0,
|
||||
},
|
||||
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(),
|
||||
projectIdOrKey: '',
|
||||
templates: [],
|
||||
total: 0,
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||
projectIdOrKey: { type: 'string', description: 'Project ID or key' },
|
||||
templates: {
|
||||
type: 'array',
|
||||
description: 'List of forms in the project',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: FORM_TEMPLATE_PROPERTIES,
|
||||
},
|
||||
},
|
||||
total: { type: 'number', description: 'Total number of forms' },
|
||||
},
|
||||
}
|
||||
105
apps/sim/tools/jsm/get_issue_forms.ts
Normal file
105
apps/sim/tools/jsm/get_issue_forms.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import type { JsmGetIssueFormsParams, JsmGetIssueFormsResponse } from '@/tools/jsm/types'
|
||||
import { ISSUE_FORM_PROPERTIES } from '@/tools/jsm/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const jsmGetIssueFormsTool: ToolConfig<JsmGetIssueFormsParams, JsmGetIssueFormsResponse> = {
|
||||
id: 'jsm_get_issue_forms',
|
||||
name: 'JSM Get Issue Forms',
|
||||
description:
|
||||
'List forms (ProForma/JSM Forms) attached to a Jira issue with metadata (name, submitted status, lock)',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
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", "10001")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/jsm/forms/issue',
|
||||
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: '',
|
||||
forms: [],
|
||||
total: 0,
|
||||
},
|
||||
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: '',
|
||||
forms: [],
|
||||
total: 0,
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
|
||||
forms: {
|
||||
type: 'array',
|
||||
description: 'List of forms attached to the issue',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: ISSUE_FORM_PROPERTIES,
|
||||
},
|
||||
},
|
||||
total: { type: 'number', description: 'Total number of forms' },
|
||||
},
|
||||
}
|
||||
@@ -8,6 +8,9 @@ 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 { jsmGetFormStructureTool } from '@/tools/jsm/get_form_structure'
|
||||
import { jsmGetFormTemplatesTool } from '@/tools/jsm/get_form_templates'
|
||||
import { jsmGetIssueFormsTool } from '@/tools/jsm/get_issue_forms'
|
||||
import { jsmGetOrganizationsTool } from '@/tools/jsm/get_organizations'
|
||||
import { jsmGetParticipantsTool } from '@/tools/jsm/get_participants'
|
||||
import { jsmGetQueuesTool } from '@/tools/jsm/get_queues'
|
||||
@@ -31,6 +34,9 @@ export {
|
||||
jsmGetApprovalsTool,
|
||||
jsmGetCommentsTool,
|
||||
jsmGetCustomersTool,
|
||||
jsmGetFormStructureTool,
|
||||
jsmGetFormTemplatesTool,
|
||||
jsmGetIssueFormsTool,
|
||||
jsmGetOrganizationsTool,
|
||||
jsmGetParticipantsTool,
|
||||
jsmGetQueuesTool,
|
||||
|
||||
@@ -222,6 +222,44 @@ export const REQUEST_TYPE_FIELD_PROPERTIES = {
|
||||
},
|
||||
} as const
|
||||
|
||||
/** Output properties for a FormTemplateIndexEntry (list endpoint) per OpenAPI spec */
|
||||
export const FORM_TEMPLATE_PROPERTIES = {
|
||||
id: { type: 'string', description: 'Form template ID (UUID)' },
|
||||
name: { type: 'string', description: 'Form template name' },
|
||||
updated: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
|
||||
issueCreateIssueTypeIds: {
|
||||
type: 'json',
|
||||
description: 'Issue type IDs that auto-attach this form on issue create',
|
||||
},
|
||||
issueCreateRequestTypeIds: {
|
||||
type: 'json',
|
||||
description: 'Request type IDs that auto-attach this form on issue create',
|
||||
},
|
||||
portalRequestTypeIds: {
|
||||
type: 'json',
|
||||
description: 'Request type IDs that show this form on the customer portal',
|
||||
},
|
||||
recommendedIssueRequestTypeIds: {
|
||||
type: 'json',
|
||||
description: 'Request type IDs that recommend this form',
|
||||
},
|
||||
} as const
|
||||
|
||||
/** Output properties for a FormIndexEntry (issue forms list endpoint) per OpenAPI spec */
|
||||
export const ISSUE_FORM_PROPERTIES = {
|
||||
id: { type: 'string', description: 'Form instance ID (UUID)' },
|
||||
name: { type: 'string', description: 'Form name' },
|
||||
updated: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
|
||||
submitted: { type: 'boolean', description: 'Whether the form has been submitted' },
|
||||
lock: { type: 'boolean', description: 'Whether the form is locked' },
|
||||
internal: { type: 'boolean', description: 'Whether the form is internal-only', optional: true },
|
||||
formTemplateId: {
|
||||
type: 'string',
|
||||
description: 'Source form template ID (UUID)',
|
||||
optional: true,
|
||||
},
|
||||
} as const
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Data model interfaces
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -778,6 +816,89 @@ export interface JsmGetRequestTypeFieldsResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export interface JsmGetFormTemplatesParams extends JsmBaseParams {
|
||||
projectIdOrKey: string
|
||||
}
|
||||
|
||||
export interface JsmGetFormStructureParams extends JsmBaseParams {
|
||||
projectIdOrKey: string
|
||||
formId: string
|
||||
}
|
||||
|
||||
export interface JsmGetIssueFormsParams extends JsmBaseParams {
|
||||
issueIdOrKey: string
|
||||
}
|
||||
|
||||
/** FormQuestion per OpenAPI spec */
|
||||
export interface JsmFormQuestion {
|
||||
label: string
|
||||
type: string
|
||||
validation: { rq?: boolean; [key: string]: unknown }
|
||||
choices?: Array<{ id: string; label: string; other?: boolean }>
|
||||
dcId?: string
|
||||
defaultAnswer?: Record<string, unknown>
|
||||
description?: string
|
||||
jiraField?: string
|
||||
questionKey?: string
|
||||
}
|
||||
|
||||
/** FormTemplateIndexEntry per OpenAPI spec */
|
||||
export interface JsmFormTemplate {
|
||||
id: string
|
||||
name: string
|
||||
updated: string
|
||||
issueCreateIssueTypeIds: number[]
|
||||
issueCreateRequestTypeIds: number[]
|
||||
portalRequestTypeIds: number[]
|
||||
recommendedIssueRequestTypeIds: number[]
|
||||
}
|
||||
|
||||
/** FormIndexEntry (issue form) per OpenAPI spec */
|
||||
export interface JsmIssueForm {
|
||||
id: string
|
||||
name: string
|
||||
updated: string
|
||||
submitted: boolean
|
||||
lock: boolean
|
||||
internal?: boolean
|
||||
formTemplateId?: string
|
||||
}
|
||||
|
||||
export interface JsmGetFormTemplatesResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
projectIdOrKey: string
|
||||
templates: JsmFormTemplate[]
|
||||
total: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface JsmGetFormStructureResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
projectIdOrKey: string
|
||||
formId: string
|
||||
design: {
|
||||
questions: Record<string, JsmFormQuestion>
|
||||
layout: unknown[]
|
||||
conditions: Record<string, unknown>
|
||||
sections: Record<string, unknown>
|
||||
settings: { name: string; submit: { lock: boolean; pdf: boolean }; language?: string }
|
||||
} | null
|
||||
updated: string | null
|
||||
publish: Record<string, unknown> | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface JsmGetIssueFormsResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
issueIdOrKey: string
|
||||
forms: JsmIssueForm[]
|
||||
total: number
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Union type for all JSM responses
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -805,3 +926,6 @@ export type JsmResponse =
|
||||
| JsmGetApprovalsResponse
|
||||
| JsmAnswerApprovalResponse
|
||||
| JsmGetRequestTypeFieldsResponse
|
||||
| JsmGetFormTemplatesResponse
|
||||
| JsmGetFormStructureResponse
|
||||
| JsmGetIssueFormsResponse
|
||||
|
||||
@@ -13,6 +13,15 @@ export function getJsmApiBaseUrl(cloudId: string): string {
|
||||
return `https://api.atlassian.com/ex/jira/${cloudId}/rest/servicedeskapi`
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the base URL for JSM Forms (ProForma) API
|
||||
* @param cloudId - The Jira Cloud ID
|
||||
* @returns The base URL for the JSM Forms API
|
||||
*/
|
||||
export function getJsmFormsApiBaseUrl(cloudId: string): string {
|
||||
return `https://api.atlassian.com/jira/forms/cloud/${cloudId}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Build common headers for JSM API requests
|
||||
* @param accessToken - The OAuth access token
|
||||
@@ -26,3 +35,28 @@ export function getJsmHeaders(accessToken: string): Record<string, string> {
|
||||
'X-ExperimentalApi': 'opt-in',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse error messages from JSM/Forms API responses
|
||||
* @param status - HTTP status code
|
||||
* @param statusText - HTTP status text
|
||||
* @param errorText - Raw error response body
|
||||
* @returns Formatted error message string
|
||||
*/
|
||||
export function parseJsmErrorMessage(
|
||||
status: number,
|
||||
statusText: string,
|
||||
errorText: string
|
||||
): string {
|
||||
try {
|
||||
const errorData = JSON.parse(errorText)
|
||||
if (errorData.errorMessage) {
|
||||
return `JSM Forms API error: ${errorData.errorMessage}`
|
||||
}
|
||||
} catch {
|
||||
if (errorText) {
|
||||
return `JSM Forms API error: ${errorText}`
|
||||
}
|
||||
}
|
||||
return `JSM Forms API error: ${status} ${statusText}`
|
||||
}
|
||||
|
||||
@@ -1292,6 +1292,9 @@ import {
|
||||
jsmGetApprovalsTool,
|
||||
jsmGetCommentsTool,
|
||||
jsmGetCustomersTool,
|
||||
jsmGetFormStructureTool,
|
||||
jsmGetFormTemplatesTool,
|
||||
jsmGetIssueFormsTool,
|
||||
jsmGetOrganizationsTool,
|
||||
jsmGetParticipantsTool,
|
||||
jsmGetQueuesTool,
|
||||
@@ -3093,6 +3096,9 @@ export const tools: Record<string, ToolConfig> = {
|
||||
jsm_add_participants: jsmAddParticipantsTool,
|
||||
jsm_get_approvals: jsmGetApprovalsTool,
|
||||
jsm_answer_approval: jsmAnswerApprovalTool,
|
||||
jsm_get_form_templates: jsmGetFormTemplatesTool,
|
||||
jsm_get_form_structure: jsmGetFormStructureTool,
|
||||
jsm_get_issue_forms: jsmGetIssueFormsTool,
|
||||
kalshi_get_markets: kalshiGetMarketsTool,
|
||||
kalshi_get_markets_v2: kalshiGetMarketsV2Tool,
|
||||
kalshi_get_market: kalshiGetMarketTool,
|
||||
|
||||
Reference in New Issue
Block a user