mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
Compare commits
12 Commits
v0.6.43
...
waleedlati
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e542ed824 | ||
|
|
46cc5269d6 | ||
|
|
cb470ce8f2 | ||
|
|
050b4634cd | ||
|
|
d38cc98fd7 | ||
|
|
fdb2e7864f | ||
|
|
412fad7a84 | ||
|
|
9f032d98a8 | ||
|
|
7391cd1a49 | ||
|
|
149ad9f36a | ||
|
|
cfa30fbab9 | ||
|
|
827d1730c9 |
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
# Build AMD64 images and push to ECR immediately (+ GHCR for main)
|
||||
build-amd64:
|
||||
name: Build AMD64
|
||||
needs: [test-build, detect-version]
|
||||
needs: [detect-version]
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/dev')
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
permissions:
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
ecr_repo_secret: ECR_REALTIME
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
# Build ARM64 images for GHCR (main branch only, runs in parallel)
|
||||
build-ghcr-arm64:
|
||||
name: Build ARM64 (GHCR Only)
|
||||
needs: [test-build, detect-version]
|
||||
needs: [detect-version]
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404-arm
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
permissions:
|
||||
@@ -169,7 +169,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
@@ -264,10 +264,10 @@ jobs:
|
||||
outputs:
|
||||
docs_changed: ${{ steps.filter.outputs.docs }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 2 # Need at least 2 commits to detect changes
|
||||
- uses: dorny/paths-filter@v3
|
||||
- uses: dorny/paths-filter@v4
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
@@ -294,7 +294,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
2
.github/workflows/docs-embeddings.yml
vendored
2
.github/workflows/docs-embeddings.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
4
.github/workflows/i18n.yml
vendored
4
.github/workflows/i18n.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: staging
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: staging
|
||||
|
||||
|
||||
4
.github/workflows/images.yml
vendored
4
.github/workflows/images.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
|
||||
2
.github/workflows/migrations.yml
vendored
2
.github/workflows/migrations.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
2
.github/workflows/publish-cli.yml
vendored
2
.github/workflows/publish-cli.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
2
.github/workflows/publish-python-sdk.yml
vendored
2
.github/workflows/publish-python-sdk.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
|
||||
2
.github/workflows/publish-ts-sdk.yml
vendored
2
.github/workflows/publish-ts-sdk.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
2
.github/workflows/test-build.yml
vendored
2
.github/workflows/test-build.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"pages": [
|
||||
"listPausedExecutions",
|
||||
"getPausedExecution",
|
||||
"getPausedExecutionByResumePath",
|
||||
"getPauseContext",
|
||||
"resumeExecution"
|
||||
]
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
"typescript",
|
||||
"---Endpoints---",
|
||||
"(generated)/workflows",
|
||||
"(generated)/human-in-the-loop",
|
||||
"(generated)/logs",
|
||||
"(generated)/usage",
|
||||
"(generated)/audit-logs",
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
"name": "Workflows",
|
||||
"description": "Execute workflows and manage workflow resources"
|
||||
},
|
||||
{
|
||||
"name": "Human in the Loop",
|
||||
"description": "Manage paused workflow executions and resume them with input"
|
||||
},
|
||||
{
|
||||
"name": "Logs",
|
||||
"description": "Query execution logs and retrieve details"
|
||||
@@ -235,6 +239,544 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/workflows/{id}/paused": {
|
||||
"get": {
|
||||
"operationId": "listPausedExecutions",
|
||||
"summary": "List Paused Executions",
|
||||
"description": "List all paused executions for a workflow. Workflows pause at Human in the Loop blocks and wait for input before continuing. Use this endpoint to discover which executions need attention.",
|
||||
"tags": ["Human in the Loop"],
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"id": "curl",
|
||||
"label": "cURL",
|
||||
"lang": "bash",
|
||||
"source": "curl -X GET \\\n \"https://www.sim.ai/api/workflows/{id}/paused?status=paused\" \\\n -H \"X-API-Key: YOUR_API_KEY\""
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The unique identifier of the workflow.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "wf_1a2b3c4d5e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Filter paused executions by status.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "paused"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of paused executions.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pausedExecutions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PausedExecutionSummary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"pausedExecutions": [
|
||||
{
|
||||
"id": "pe_abc123",
|
||||
"workflowId": "wf_1a2b3c4d5e",
|
||||
"executionId": "exec_9f8e7d6c5b",
|
||||
"status": "paused",
|
||||
"totalPauseCount": 1,
|
||||
"resumedCount": 0,
|
||||
"pausedAt": "2026-01-15T10:30:00Z",
|
||||
"updatedAt": "2026-01-15T10:30:00Z",
|
||||
"expiresAt": null,
|
||||
"metadata": null,
|
||||
"triggerIds": [],
|
||||
"pausePoints": [
|
||||
{
|
||||
"contextId": "ctx_xyz789",
|
||||
"blockId": "block_hitl_1",
|
||||
"registeredAt": "2026-01-15T10:30:00Z",
|
||||
"resumeStatus": "paused",
|
||||
"snapshotReady": true,
|
||||
"resumeLinks": {
|
||||
"apiUrl": "https://www.sim.ai/api/resume/wf_1a2b3c4d5e/exec_9f8e7d6c5b/ctx_xyz789",
|
||||
"uiUrl": "https://www.sim.ai/resume/wf_1a2b3c4d5e/exec_9f8e7d6c5b",
|
||||
"contextId": "ctx_xyz789",
|
||||
"executionId": "exec_9f8e7d6c5b",
|
||||
"workflowId": "wf_1a2b3c4d5e"
|
||||
},
|
||||
"response": {
|
||||
"displayData": {
|
||||
"title": "Approval Required",
|
||||
"message": "Please review this request"
|
||||
},
|
||||
"formFields": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/BadRequest"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/NotFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/workflows/{id}/paused/{executionId}": {
|
||||
"get": {
|
||||
"operationId": "getPausedExecution",
|
||||
"summary": "Get Paused Execution",
|
||||
"description": "Get detailed information about a specific paused execution, including its pause points, execution snapshot, and resume queue. Use this to inspect the state before resuming.",
|
||||
"tags": ["Human in the Loop"],
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"id": "curl",
|
||||
"label": "cURL",
|
||||
"lang": "bash",
|
||||
"source": "curl -X GET \\\n \"https://www.sim.ai/api/workflows/{id}/paused/{executionId}\" \\\n -H \"X-API-Key: YOUR_API_KEY\""
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The unique identifier of the workflow.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "wf_1a2b3c4d5e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "executionId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The execution ID of the paused execution.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "exec_9f8e7d6c5b"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Paused execution details.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PausedExecutionDetail"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/NotFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/resume/{workflowId}/{executionId}": {
|
||||
"get": {
|
||||
"operationId": "getPausedExecutionByResumePath",
|
||||
"summary": "Get Paused Execution (Resume Path)",
|
||||
"description": "Get detailed information about a specific paused execution using the resume URL path. Returns the same data as the workflow paused execution detail endpoint.",
|
||||
"tags": ["Human in the Loop"],
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"id": "curl",
|
||||
"label": "cURL",
|
||||
"lang": "bash",
|
||||
"source": "curl -X GET \\\n \"https://www.sim.ai/api/resume/{workflowId}/{executionId}\" \\\n -H \"X-API-Key: YOUR_API_KEY\""
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "workflowId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The unique identifier of the workflow.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "wf_1a2b3c4d5e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "executionId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The execution ID of the paused execution.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "exec_9f8e7d6c5b"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Paused execution details.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PausedExecutionDetail"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/NotFound"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string",
|
||||
"description": "Human-readable error message."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/resume/{workflowId}/{executionId}/{contextId}": {
|
||||
"get": {
|
||||
"operationId": "getPauseContext",
|
||||
"summary": "Get Pause Context",
|
||||
"description": "Get detailed information about a specific pause context within a paused execution. Returns the pause point details, resume queue state, and any active resume entry.",
|
||||
"tags": ["Human in the Loop"],
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"id": "curl",
|
||||
"label": "cURL",
|
||||
"lang": "bash",
|
||||
"source": "curl -X GET \\\n \"https://www.sim.ai/api/resume/{workflowId}/{executionId}/{contextId}\" \\\n -H \"X-API-Key: YOUR_API_KEY\""
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "workflowId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The unique identifier of the workflow.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "wf_1a2b3c4d5e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "executionId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The execution ID of the paused execution.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "exec_9f8e7d6c5b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "contextId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The pause context ID to retrieve details for.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "ctx_xyz789"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Pause context details.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PauseContextDetail"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/NotFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"operationId": "resumeExecution",
|
||||
"summary": "Resume Execution",
|
||||
"description": "Resume a paused workflow execution by providing input for a specific pause context. The execution continues from where it paused, using the provided input. Supports synchronous, asynchronous, and streaming modes (determined by the original execution's configuration).",
|
||||
"tags": ["Human in the Loop"],
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"id": "curl",
|
||||
"label": "cURL",
|
||||
"lang": "bash",
|
||||
"source": "curl -X POST \\\n \"https://www.sim.ai/api/resume/{workflowId}/{executionId}/{contextId}\" \\\n -H \"X-API-Key: YOUR_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"input\": {\n \"approved\": true,\n \"comment\": \"Looks good to me\"\n }\n }'"
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "workflowId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The unique identifier of the workflow.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "wf_1a2b3c4d5e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "executionId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The execution ID of the paused execution.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "exec_9f8e7d6c5b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "contextId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The pause context ID to resume. Found in the pause point's contextId field or resumeLinks.",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "ctx_xyz789"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "Input data for the resumed execution. The structure depends on the workflow's Human in the Loop block configuration.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"input": {
|
||||
"type": "object",
|
||||
"description": "Key-value pairs to pass as input to the resumed execution. If omitted, the entire request body is used as input.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"input": {
|
||||
"approved": true,
|
||||
"comment": "Looks good to me"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Resume execution completed synchronously, or resume was queued behind another in-progress resume.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ResumeResult"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"description": "Resume has been queued behind another in-progress resume.",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["queued"],
|
||||
"description": "Indicates the resume is queued."
|
||||
},
|
||||
"executionId": {
|
||||
"type": "string",
|
||||
"description": "The execution ID assigned to this resume."
|
||||
},
|
||||
"queuePosition": {
|
||||
"type": "integer",
|
||||
"description": "Position in the resume queue."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "Human-readable status message."
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"description": "Resume execution started (non-API-key callers). The execution runs asynchronously.",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["started"],
|
||||
"description": "Indicates the resume execution has started."
|
||||
},
|
||||
"executionId": {
|
||||
"type": "string",
|
||||
"description": "The execution ID for the resumed workflow."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "Human-readable status message."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"examples": {
|
||||
"sync": {
|
||||
"summary": "Synchronous completion",
|
||||
"value": {
|
||||
"success": true,
|
||||
"status": "completed",
|
||||
"executionId": "exec_new123",
|
||||
"output": {
|
||||
"result": "Approved and processed"
|
||||
},
|
||||
"error": null,
|
||||
"metadata": {
|
||||
"duration": 850,
|
||||
"startTime": "2026-01-15T10:35:00Z",
|
||||
"endTime": "2026-01-15T10:35:01Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"queued": {
|
||||
"summary": "Queued behind another resume",
|
||||
"value": {
|
||||
"status": "queued",
|
||||
"executionId": "exec_new123",
|
||||
"queuePosition": 2,
|
||||
"message": "Resume queued. It will run after current resumes finish."
|
||||
}
|
||||
},
|
||||
"started": {
|
||||
"summary": "Execution started (fire and forget)",
|
||||
"value": {
|
||||
"status": "started",
|
||||
"executionId": "exec_new123",
|
||||
"message": "Resume execution started."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"202": {
|
||||
"description": "Resume execution has been queued for asynchronous processing. Poll the statusUrl for results.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AsyncExecutionResult"
|
||||
},
|
||||
"example": {
|
||||
"success": true,
|
||||
"async": true,
|
||||
"jobId": "job_4a3b2c1d0e",
|
||||
"executionId": "exec_new123",
|
||||
"message": "Resume execution queued",
|
||||
"statusUrl": "https://www.sim.ai/api/jobs/job_4a3b2c1d0e"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/components/responses/BadRequest"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/NotFound"
|
||||
},
|
||||
"503": {
|
||||
"description": "Failed to queue the resume execution. Retry the request.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string",
|
||||
"description": "Error message."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string",
|
||||
"description": "Human-readable error message."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/workflows": {
|
||||
"get": {
|
||||
"operationId": "listWorkflows",
|
||||
@@ -5788,6 +6330,346 @@
|
||||
"description": "Upper bound value for 'between' operator."
|
||||
}
|
||||
}
|
||||
},
|
||||
"PausedExecutionSummary": {
|
||||
"type": "object",
|
||||
"description": "Summary of a paused workflow execution.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Unique identifier for the paused execution record."
|
||||
},
|
||||
"workflowId": {
|
||||
"type": "string",
|
||||
"description": "The workflow this execution belongs to."
|
||||
},
|
||||
"executionId": {
|
||||
"type": "string",
|
||||
"description": "The execution that was paused."
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "Current status of the paused execution.",
|
||||
"example": "paused"
|
||||
},
|
||||
"totalPauseCount": {
|
||||
"type": "integer",
|
||||
"description": "Total number of pause points in this execution."
|
||||
},
|
||||
"resumedCount": {
|
||||
"type": "integer",
|
||||
"description": "Number of pause points that have been resumed."
|
||||
},
|
||||
"pausedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"nullable": true,
|
||||
"description": "When the execution was paused."
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"nullable": true,
|
||||
"description": "When the paused execution record was last updated."
|
||||
},
|
||||
"expiresAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"nullable": true,
|
||||
"description": "When the paused execution will expire and be cleaned up."
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"nullable": true,
|
||||
"description": "Additional metadata associated with the paused execution.",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"triggerIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "IDs of triggers that initiated the original execution."
|
||||
},
|
||||
"pausePoints": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PausePoint"
|
||||
},
|
||||
"description": "List of pause points in the execution."
|
||||
}
|
||||
}
|
||||
},
|
||||
"PausePoint": {
|
||||
"type": "object",
|
||||
"description": "A point in the workflow where execution has been paused awaiting human input.",
|
||||
"properties": {
|
||||
"contextId": {
|
||||
"type": "string",
|
||||
"description": "Unique identifier for this pause context. Used when resuming execution."
|
||||
},
|
||||
"blockId": {
|
||||
"type": "string",
|
||||
"description": "The block ID where execution paused."
|
||||
},
|
||||
"response": {
|
||||
"description": "Data returned by the block before pausing, including display data and form fields."
|
||||
},
|
||||
"registeredAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "When this pause point was registered."
|
||||
},
|
||||
"resumeStatus": {
|
||||
"type": "string",
|
||||
"enum": ["paused", "resumed", "failed", "queued", "resuming"],
|
||||
"description": "Current status of this pause point."
|
||||
},
|
||||
"snapshotReady": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the execution snapshot is ready for resumption."
|
||||
},
|
||||
"resumeLinks": {
|
||||
"type": "object",
|
||||
"description": "Links for resuming this pause point.",
|
||||
"properties": {
|
||||
"apiUrl": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "API endpoint URL to POST resume input to."
|
||||
},
|
||||
"uiUrl": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "UI URL for a human to review and approve."
|
||||
},
|
||||
"contextId": {
|
||||
"type": "string",
|
||||
"description": "The context ID for this pause point."
|
||||
},
|
||||
"executionId": {
|
||||
"type": "string",
|
||||
"description": "The execution ID."
|
||||
},
|
||||
"workflowId": {
|
||||
"type": "string",
|
||||
"description": "The workflow ID."
|
||||
}
|
||||
}
|
||||
},
|
||||
"queuePosition": {
|
||||
"type": "integer",
|
||||
"nullable": true,
|
||||
"description": "Position in the resume queue, if queued."
|
||||
},
|
||||
"latestResumeEntry": {
|
||||
"$ref": "#/components/schemas/ResumeQueueEntry",
|
||||
"nullable": true,
|
||||
"description": "The most recent resume queue entry for this pause point."
|
||||
},
|
||||
"parallelScope": {
|
||||
"type": "object",
|
||||
"description": "Scope information when the pause occurs inside a parallel branch.",
|
||||
"properties": {
|
||||
"parallelId": {
|
||||
"type": "string",
|
||||
"description": "Identifier of the parallel execution group."
|
||||
},
|
||||
"branchIndex": {
|
||||
"type": "integer",
|
||||
"description": "Index of the branch within the parallel group."
|
||||
},
|
||||
"branchTotal": {
|
||||
"type": "integer",
|
||||
"description": "Total number of branches in the parallel group."
|
||||
}
|
||||
}
|
||||
},
|
||||
"loopScope": {
|
||||
"type": "object",
|
||||
"description": "Scope information when the pause occurs inside a loop.",
|
||||
"properties": {
|
||||
"loopId": {
|
||||
"type": "string",
|
||||
"description": "Identifier of the loop."
|
||||
},
|
||||
"iteration": {
|
||||
"type": "integer",
|
||||
"description": "Current loop iteration number."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ResumeQueueEntry": {
|
||||
"type": "object",
|
||||
"description": "An entry in the resume execution queue.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Unique identifier for this queue entry."
|
||||
},
|
||||
"pausedExecutionId": {
|
||||
"type": "string",
|
||||
"description": "The paused execution this entry belongs to."
|
||||
},
|
||||
"parentExecutionId": {
|
||||
"type": "string",
|
||||
"description": "The original execution that was paused."
|
||||
},
|
||||
"newExecutionId": {
|
||||
"type": "string",
|
||||
"description": "The new execution ID created for the resume."
|
||||
},
|
||||
"contextId": {
|
||||
"type": "string",
|
||||
"description": "The pause context ID being resumed."
|
||||
},
|
||||
"resumeInput": {
|
||||
"description": "The input provided when resuming."
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "Status of this queue entry (e.g., pending, claimed, completed, failed)."
|
||||
},
|
||||
"queuedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"nullable": true,
|
||||
"description": "When the entry was added to the queue."
|
||||
},
|
||||
"claimedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"nullable": true,
|
||||
"description": "When execution started processing this entry."
|
||||
},
|
||||
"completedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"nullable": true,
|
||||
"description": "When execution completed."
|
||||
},
|
||||
"failureReason": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Reason for failure, if the resume failed."
|
||||
}
|
||||
}
|
||||
},
|
||||
"PausedExecutionDetail": {
|
||||
"type": "object",
|
||||
"description": "Detailed information about a paused execution, including the execution snapshot and resume queue.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/PausedExecutionSummary"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"executionSnapshot": {
|
||||
"type": "object",
|
||||
"description": "Serialized execution state for resumption.",
|
||||
"properties": {
|
||||
"snapshot": {
|
||||
"type": "string",
|
||||
"description": "Serialized execution snapshot data."
|
||||
},
|
||||
"triggerIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Trigger IDs from the snapshot."
|
||||
}
|
||||
}
|
||||
},
|
||||
"queue": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ResumeQueueEntry"
|
||||
},
|
||||
"description": "Resume queue entries for this execution."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"PauseContextDetail": {
|
||||
"type": "object",
|
||||
"description": "Detailed information about a specific pause context within a paused execution.",
|
||||
"properties": {
|
||||
"execution": {
|
||||
"$ref": "#/components/schemas/PausedExecutionSummary",
|
||||
"description": "Summary of the parent paused execution."
|
||||
},
|
||||
"pausePoint": {
|
||||
"$ref": "#/components/schemas/PausePoint",
|
||||
"description": "The specific pause point for this context."
|
||||
},
|
||||
"queue": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ResumeQueueEntry"
|
||||
},
|
||||
"description": "Resume queue entries for this context."
|
||||
},
|
||||
"activeResumeEntry": {
|
||||
"$ref": "#/components/schemas/ResumeQueueEntry",
|
||||
"nullable": true,
|
||||
"description": "The currently active resume entry, if any."
|
||||
}
|
||||
}
|
||||
},
|
||||
"ResumeResult": {
|
||||
"type": "object",
|
||||
"description": "Result of a synchronous resume execution.",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the resume execution completed successfully."
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "Execution status.",
|
||||
"enum": ["completed", "failed", "paused", "cancelled"],
|
||||
"example": "completed"
|
||||
},
|
||||
"executionId": {
|
||||
"type": "string",
|
||||
"description": "The new execution ID for the resumed workflow."
|
||||
},
|
||||
"output": {
|
||||
"type": "object",
|
||||
"description": "Workflow output from the resumed execution.",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"error": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Error message if the execution failed."
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"description": "Execution timing metadata.",
|
||||
"properties": {
|
||||
"duration": {
|
||||
"type": "integer",
|
||||
"description": "Total execution duration in milliseconds."
|
||||
},
|
||||
"startTime": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "When the resume execution started."
|
||||
},
|
||||
"endTime": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "When the resume execution completed."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
|
||||
@@ -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",
|
||||
@@ -10784,8 +10796,34 @@
|
||||
}
|
||||
],
|
||||
"operationCount": 4,
|
||||
"triggers": [],
|
||||
"triggerCount": 0,
|
||||
"triggers": [
|
||||
{
|
||||
"id": "servicenow_incident_created",
|
||||
"name": "ServiceNow Incident Created",
|
||||
"description": "Trigger workflow when a new incident is created in ServiceNow"
|
||||
},
|
||||
{
|
||||
"id": "servicenow_incident_updated",
|
||||
"name": "ServiceNow Incident Updated",
|
||||
"description": "Trigger workflow when an incident is updated in ServiceNow"
|
||||
},
|
||||
{
|
||||
"id": "servicenow_change_request_created",
|
||||
"name": "ServiceNow Change Request Created",
|
||||
"description": "Trigger workflow when a new change request is created in ServiceNow"
|
||||
},
|
||||
{
|
||||
"id": "servicenow_change_request_updated",
|
||||
"name": "ServiceNow Change Request Updated",
|
||||
"description": "Trigger workflow when a change request is updated in ServiceNow"
|
||||
},
|
||||
{
|
||||
"id": "servicenow_webhook",
|
||||
"name": "ServiceNow Webhook (All Events)",
|
||||
"description": "Trigger workflow on any ServiceNow webhook event"
|
||||
}
|
||||
],
|
||||
"triggerCount": 5,
|
||||
"authType": "none",
|
||||
"category": "tools",
|
||||
"integrationType": "customer-support",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { db } from '@sim/db'
|
||||
import { subscription, user, workflowExecutionLogs, workspace } from '@sim/db/schema'
|
||||
import { subscription, workflowExecutionLogs, workspace } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq, inArray, isNull, lt } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
@@ -26,38 +26,19 @@ export async function GET(request: NextRequest) {
|
||||
const retentionDate = new Date()
|
||||
retentionDate.setDate(retentionDate.getDate() - Number(env.FREE_PLAN_LOG_RETENTION_DAYS || '7'))
|
||||
|
||||
const freeUsers = await db
|
||||
.select({ userId: user.id })
|
||||
.from(user)
|
||||
const freeWorkspacesSubquery = db
|
||||
.select({ id: workspace.id })
|
||||
.from(workspace)
|
||||
.leftJoin(
|
||||
subscription,
|
||||
and(
|
||||
eq(user.id, subscription.referenceId),
|
||||
eq(subscription.referenceId, workspace.billedAccountUserId),
|
||||
inArray(subscription.status, ENTITLED_SUBSCRIPTION_STATUSES),
|
||||
sqlIsPaid(subscription.plan)
|
||||
)
|
||||
)
|
||||
.where(isNull(subscription.id))
|
||||
|
||||
if (freeUsers.length === 0) {
|
||||
logger.info('No free users found for log cleanup')
|
||||
return NextResponse.json({ message: 'No free users found for cleanup' })
|
||||
}
|
||||
|
||||
const freeUserIds = freeUsers.map((u) => u.userId)
|
||||
|
||||
const workspacesQuery = await db
|
||||
.select({ id: workspace.id })
|
||||
.from(workspace)
|
||||
.where(inArray(workspace.billedAccountUserId, freeUserIds))
|
||||
|
||||
if (workspacesQuery.length === 0) {
|
||||
logger.info('No workspaces found for free users')
|
||||
return NextResponse.json({ message: 'No workspaces found for cleanup' })
|
||||
}
|
||||
|
||||
const workspaceIds = workspacesQuery.map((w) => w.id)
|
||||
|
||||
const results = {
|
||||
enhancedLogs: {
|
||||
total: 0,
|
||||
@@ -83,7 +64,7 @@ export async function GET(request: NextRequest) {
|
||||
let batchesProcessed = 0
|
||||
let hasMoreLogs = true
|
||||
|
||||
logger.info(`Starting enhanced logs cleanup for ${workspaceIds.length} workspaces`)
|
||||
logger.info('Starting enhanced logs cleanup for free-plan workspaces')
|
||||
|
||||
while (hasMoreLogs && batchesProcessed < MAX_BATCHES) {
|
||||
const oldEnhancedLogs = await db
|
||||
@@ -105,8 +86,8 @@ export async function GET(request: NextRequest) {
|
||||
.from(workflowExecutionLogs)
|
||||
.where(
|
||||
and(
|
||||
inArray(workflowExecutionLogs.workspaceId, workspaceIds),
|
||||
lt(workflowExecutionLogs.createdAt, retentionDate)
|
||||
inArray(workflowExecutionLogs.workspaceId, freeWorkspacesSubquery),
|
||||
lt(workflowExecutionLogs.startedAt, retentionDate)
|
||||
)
|
||||
)
|
||||
.limit(BATCH_SIZE)
|
||||
|
||||
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)',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ServiceNowIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { IntegrationType } from '@/blocks/types'
|
||||
import type { ServiceNowResponse } from '@/tools/servicenow/types'
|
||||
import { getTrigger } from '@/triggers'
|
||||
|
||||
export const ServiceNowBlock: BlockConfig<ServiceNowResponse> = {
|
||||
type: 'servicenow',
|
||||
@@ -215,6 +216,11 @@ Output: {"state": "2", "assigned_to": "john.doe", "work_notes": "Assigned and st
|
||||
condition: { field: 'operation', value: 'servicenow_delete_record' },
|
||||
required: true,
|
||||
},
|
||||
...getTrigger('servicenow_incident_created').subBlocks,
|
||||
...getTrigger('servicenow_incident_updated').subBlocks,
|
||||
...getTrigger('servicenow_change_request_created').subBlocks,
|
||||
...getTrigger('servicenow_change_request_updated').subBlocks,
|
||||
...getTrigger('servicenow_webhook').subBlocks,
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
@@ -262,4 +268,14 @@ Output: {"state": "2", "assigned_to": "john.doe", "work_notes": "Assigned and st
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
metadata: { type: 'json', description: 'Operation metadata' },
|
||||
},
|
||||
triggers: {
|
||||
enabled: true,
|
||||
available: [
|
||||
'servicenow_incident_created',
|
||||
'servicenow_incident_updated',
|
||||
'servicenow_change_request_created',
|
||||
'servicenow_change_request_updated',
|
||||
'servicenow_webhook',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render } from '@react-email/components'
|
||||
import { render } from '@react-email/render'
|
||||
import {
|
||||
OnboardingFollowupEmail,
|
||||
OTPVerificationEmail,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render } from '@react-email/components'
|
||||
import { render } from '@react-email/render'
|
||||
import { db } from '@sim/db'
|
||||
import {
|
||||
member,
|
||||
|
||||
@@ -28,6 +28,7 @@ import { outlookHandler } from '@/lib/webhooks/providers/outlook'
|
||||
import { resendHandler } from '@/lib/webhooks/providers/resend'
|
||||
import { rssHandler } from '@/lib/webhooks/providers/rss'
|
||||
import { salesforceHandler } from '@/lib/webhooks/providers/salesforce'
|
||||
import { servicenowHandler } from '@/lib/webhooks/providers/servicenow'
|
||||
import { slackHandler } from '@/lib/webhooks/providers/slack'
|
||||
import { stripeHandler } from '@/lib/webhooks/providers/stripe'
|
||||
import { telegramHandler } from '@/lib/webhooks/providers/telegram'
|
||||
@@ -72,6 +73,7 @@ const PROVIDER_HANDLERS: Record<string, WebhookProviderHandler> = {
|
||||
outlook: outlookHandler,
|
||||
rss: rssHandler,
|
||||
salesforce: salesforceHandler,
|
||||
servicenow: servicenowHandler,
|
||||
slack: slackHandler,
|
||||
stripe: stripeHandler,
|
||||
telegram: telegramHandler,
|
||||
|
||||
57
apps/sim/lib/webhooks/providers/servicenow.ts
Normal file
57
apps/sim/lib/webhooks/providers/servicenow.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { NextResponse } from 'next/server'
|
||||
import type {
|
||||
AuthContext,
|
||||
EventMatchContext,
|
||||
WebhookProviderHandler,
|
||||
} from '@/lib/webhooks/providers/types'
|
||||
import { verifyTokenAuth } from '@/lib/webhooks/providers/utils'
|
||||
|
||||
const logger = createLogger('WebhookProvider:ServiceNow')
|
||||
|
||||
function asRecord(body: unknown): Record<string, unknown> {
|
||||
return body && typeof body === 'object' && !Array.isArray(body)
|
||||
? (body as Record<string, unknown>)
|
||||
: {}
|
||||
}
|
||||
|
||||
export const servicenowHandler: WebhookProviderHandler = {
|
||||
verifyAuth({ request, requestId, providerConfig }: AuthContext): NextResponse | null {
|
||||
const secret = providerConfig.webhookSecret as string | undefined
|
||||
if (!secret?.trim()) {
|
||||
logger.warn(`[${requestId}] ServiceNow webhook missing webhookSecret — rejecting`)
|
||||
return new NextResponse('Unauthorized - Webhook secret not configured', { status: 401 })
|
||||
}
|
||||
|
||||
if (
|
||||
!verifyTokenAuth(request, secret.trim(), 'x-sim-webhook-secret') &&
|
||||
!verifyTokenAuth(request, secret.trim())
|
||||
) {
|
||||
logger.warn(`[${requestId}] ServiceNow webhook secret verification failed`)
|
||||
return new NextResponse('Unauthorized - Invalid webhook secret', { status: 401 })
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
async matchEvent({ webhook, workflow, body, requestId, providerConfig }: EventMatchContext) {
|
||||
const triggerId = providerConfig.triggerId as string | undefined
|
||||
if (!triggerId) {
|
||||
return true
|
||||
}
|
||||
|
||||
const { isServiceNowEventMatch } = await import('@/triggers/servicenow/utils')
|
||||
const configuredTableName = providerConfig.tableName as string | undefined
|
||||
const obj = asRecord(body)
|
||||
|
||||
if (!isServiceNowEventMatch(triggerId, obj, configuredTableName)) {
|
||||
logger.debug(
|
||||
`[${requestId}] ServiceNow event mismatch for trigger ${triggerId}. Skipping execution.`,
|
||||
{ webhookId: webhook.id, workflowId: workflow.id, triggerId }
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
@@ -34,6 +34,8 @@ export const confluenceAddLabelTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -44,6 +44,8 @@ export const confluenceCreateBlogPostTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -31,6 +31,8 @@ export const confluenceCreateCommentTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -40,6 +40,8 @@ export const confluenceCreatePageTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -38,6 +38,8 @@ export const confluenceCreatePagePropertyTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -39,6 +39,8 @@ export const confluenceCreateSpaceTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -35,6 +35,8 @@ export const confluenceCreateSpacePropertyTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -30,6 +30,8 @@ export const confluenceDeleteAttachmentTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -31,6 +31,8 @@ export const confluenceDeleteBlogPostTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -30,6 +30,8 @@ export const confluenceDeleteCommentTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -33,6 +33,8 @@ export const confluenceDeleteLabelTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -32,6 +32,8 @@ export const confluenceDeletePageTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -33,6 +33,8 @@ export const confluenceDeletePagePropertyTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -31,6 +31,8 @@ export const confluenceDeleteSpaceTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -33,6 +33,8 @@ export const confluenceDeleteSpacePropertyTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -49,6 +49,8 @@ export const confluenceGetBlogPostTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -39,6 +39,8 @@ export const confluenceGetPageAncestorsTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -42,6 +42,8 @@ export const confluenceGetPageChildrenTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -43,6 +43,8 @@ export const confluenceGetPageDescendantsTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -54,6 +54,8 @@ export const confluenceGetPageVersionTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -47,6 +47,8 @@ export const confluenceGetPagesByLabelTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -42,6 +42,8 @@ export const confluenceGetSpaceTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -41,6 +41,8 @@ export const confluenceGetTaskTool: ToolConfig<ConfluenceGetTaskParams, Confluen
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -33,6 +33,8 @@ export const confluenceGetUserTool: ToolConfig<ConfluenceGetUserParams, Confluen
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -39,6 +39,8 @@ export const confluenceListAttachmentsTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -47,6 +47,8 @@ export const confluenceListBlogPostsTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -55,6 +55,8 @@ export const confluenceListBlogPostsInSpaceTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -39,6 +39,8 @@ export const confluenceListCommentsTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -37,6 +37,8 @@ export const confluenceListLabelsTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -43,6 +43,8 @@ export const confluenceListPagePropertiesTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -40,6 +40,8 @@ export const confluenceListPageVersionsTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -57,6 +57,8 @@ export const confluenceListPagesInSpaceTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -38,6 +38,8 @@ export const confluenceListSpaceLabelsTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -42,6 +42,8 @@ export const confluenceListSpacePermissionsTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -38,6 +38,8 @@ export const confluenceListSpacePropertiesTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -38,6 +38,8 @@ export const confluenceListSpacesTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -52,6 +52,8 @@ export const confluenceListTasksTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -21,6 +21,8 @@ export const confluenceRetrieveTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -34,6 +34,8 @@ export const confluenceSearchTool: ToolConfig<ConfluenceSearchParams, Confluence
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -44,6 +44,8 @@ export const confluenceSearchInSpaceTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -13,6 +13,8 @@ export const confluenceUpdateTool: ToolConfig<ConfluenceUpdateParams, Confluence
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -37,6 +37,8 @@ export const confluenceUpdateBlogPostTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -31,6 +31,8 @@ export const confluenceUpdateCommentTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -38,6 +38,8 @@ export const confluenceUpdateSpaceTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -44,6 +44,8 @@ export const confluenceUpdateTaskTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -37,6 +37,8 @@ export const confluenceUploadAttachmentTool: ToolConfig<
|
||||
provider: 'confluence',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -41,9 +41,41 @@ export interface ErrorExtractorConfig {
|
||||
const ERROR_EXTRACTORS: ErrorExtractorConfig[] = [
|
||||
{
|
||||
id: 'atlassian-errors',
|
||||
description: 'Atlassian REST API errorMessage field',
|
||||
examples: ['Jira', 'Jira Service Management', 'Confluence'],
|
||||
extract: (errorInfo) => errorInfo?.data?.errorMessage,
|
||||
description:
|
||||
'Atlassian REST API error formats (errorMessage, errorMessages, errors[].title, message)',
|
||||
examples: ['Jira', 'Jira Service Management', 'Confluence', 'JSM Forms/ProForma'],
|
||||
extract: (errorInfo) => {
|
||||
// JSM Service Desk: singular errorMessage string
|
||||
if (errorInfo?.data?.errorMessage) {
|
||||
return errorInfo.data.errorMessage
|
||||
}
|
||||
// Jira Platform: errorMessages array
|
||||
if (
|
||||
Array.isArray(errorInfo?.data?.errorMessages) &&
|
||||
errorInfo.data.errorMessages.length > 0
|
||||
) {
|
||||
return errorInfo.data.errorMessages.join(', ')
|
||||
}
|
||||
// Confluence v2 / Forms API: RFC 7807 errors array with title/detail
|
||||
if (Array.isArray(errorInfo?.data?.errors) && errorInfo.data.errors.length > 0) {
|
||||
const err = errorInfo.data.errors[0]
|
||||
if (err?.title) {
|
||||
return err.detail ? `${err.title}: ${err.detail}` : err.title
|
||||
}
|
||||
}
|
||||
// Jira Platform field-level errors object
|
||||
if (errorInfo?.data?.errors && !Array.isArray(errorInfo.data.errors)) {
|
||||
const fieldErrors = Object.entries(errorInfo.data.errors)
|
||||
.map(([field, msg]) => `${field}: ${msg}`)
|
||||
.join(', ')
|
||||
if (fieldErrors) return fieldErrors
|
||||
}
|
||||
// Generic message fallback (auth/gateway errors)
|
||||
if (errorInfo?.data?.message) {
|
||||
return errorInfo.data.message
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'graphql-errors',
|
||||
|
||||
@@ -26,6 +26,8 @@ const {
|
||||
mockListCustomTools,
|
||||
mockGetCustomToolByIdOrTitle,
|
||||
mockGenerateInternalToken,
|
||||
mockSecureFetchWithPinnedIP,
|
||||
mockValidateUrlWithDNS,
|
||||
} = vi.hoisted(() => ({
|
||||
mockIsHosted: { value: false },
|
||||
mockEnv: { NEXT_PUBLIC_APP_URL: 'http://localhost:3000' } as Record<string, string | undefined>,
|
||||
@@ -40,6 +42,8 @@ const {
|
||||
mockListCustomTools: vi.fn(),
|
||||
mockGetCustomToolByIdOrTitle: vi.fn(),
|
||||
mockGenerateInternalToken: vi.fn(),
|
||||
mockSecureFetchWithPinnedIP: vi.fn(),
|
||||
mockValidateUrlWithDNS: vi.fn(),
|
||||
}))
|
||||
|
||||
// Mock feature flags
|
||||
@@ -73,6 +77,11 @@ vi.mock('@/lib/auth/internal', () => ({
|
||||
|
||||
vi.mock('@/lib/billing/core/usage-log', () => ({}))
|
||||
|
||||
vi.mock('@/lib/core/security/input-validation.server', () => ({
|
||||
secureFetchWithPinnedIP: (...args: unknown[]) => mockSecureFetchWithPinnedIP(...args),
|
||||
validateUrlWithDNS: (...args: unknown[]) => mockValidateUrlWithDNS(...args),
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/core/rate-limiter/hosted-key', () => ({
|
||||
getHostedKeyRateLimiter: () => mockRateLimiterFns,
|
||||
}))
|
||||
@@ -476,6 +485,19 @@ describe('Automatic Internal Route Detection', () => {
|
||||
beforeEach(() => {
|
||||
process.env.NEXT_PUBLIC_APP_URL = 'http://localhost:3000'
|
||||
cleanupEnvVars = setupEnvVars({ NEXT_PUBLIC_APP_URL: 'http://localhost:3000' })
|
||||
|
||||
mockValidateUrlWithDNS.mockResolvedValue({ isValid: true, resolvedIP: '93.184.216.34' })
|
||||
mockSecureFetchWithPinnedIP.mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {
|
||||
get: (name: string) => (name.toLowerCase() === 'content-type' ? 'application/json' : null),
|
||||
toRecord: () => ({ 'content-type': 'application/json' }),
|
||||
},
|
||||
text: async () => JSON.stringify({}),
|
||||
json: async () => ({}),
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -14,6 +14,8 @@ export const jiraAddAttachmentTool: ToolConfig<JiraAddAttachmentParams, JiraAddA
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -30,6 +30,8 @@ export const jiraAddCommentTool: ToolConfig<JiraAddCommentParams, JiraAddComment
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -14,6 +14,8 @@ export const jiraAddWatcherTool: ToolConfig<JiraAddWatcherParams, JiraAddWatcher
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -57,6 +57,8 @@ export const jiraAddWorklogTool: ToolConfig<JiraAddWorklogParams, JiraAddWorklog
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -14,6 +14,8 @@ export const jiraAssignIssueTool: ToolConfig<JiraAssignIssueParams, JiraAssignIs
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -14,6 +14,8 @@ export const jiraBulkRetrieveTool: ToolConfig<JiraRetrieveBulkParams, JiraRetrie
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -17,6 +17,8 @@ export const jiraCreateIssueLinkTool: ToolConfig<
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -17,6 +17,8 @@ export const jiraDeleteAttachmentTool: ToolConfig<
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -15,6 +15,8 @@ export const jiraDeleteCommentTool: ToolConfig<JiraDeleteCommentParams, JiraDele
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -14,6 +14,8 @@ export const jiraDeleteIssueTool: ToolConfig<JiraDeleteIssueParams, JiraDeleteIs
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -17,6 +17,8 @@ export const jiraDeleteIssueLinkTool: ToolConfig<
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -15,6 +15,8 @@ export const jiraDeleteWorklogTool: ToolConfig<JiraDeleteWorklogParams, JiraDele
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -35,6 +35,8 @@ export const jiraGetAttachmentsTool: ToolConfig<
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -32,6 +32,8 @@ export const jiraGetCommentsTool: ToolConfig<JiraGetCommentsParams, JiraGetComme
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -32,6 +32,8 @@ export const jiraGetUsersTool: ToolConfig<JiraGetUsersParams, JiraGetUsersRespon
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -32,6 +32,8 @@ export const jiraGetWorklogsTool: ToolConfig<JiraGetWorklogsParams, JiraGetWorkl
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -15,6 +15,8 @@ export const jiraRemoveWatcherTool: ToolConfig<JiraRemoveWatcherParams, JiraRemo
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -195,6 +195,8 @@ export const jiraRetrieveTool: ToolConfig<JiraRetrieveParams, JiraRetrieveRespon
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -81,6 +81,8 @@ export const jiraSearchIssuesTool: ToolConfig<JiraSearchIssuesParams, JiraSearch
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -15,6 +15,8 @@ export const jiraSearchUsersTool: ToolConfig<JiraSearchUsersParams, JiraSearchUs
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -17,6 +17,8 @@ export const jiraTransitionIssueTool: ToolConfig<
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -13,6 +13,8 @@ export const jiraUpdateTool: ToolConfig<JiraUpdateParams, JiraUpdateResponse> =
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -31,6 +31,8 @@ export const jiraUpdateCommentTool: ToolConfig<JiraUpdateCommentParams, JiraUpda
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -58,6 +58,8 @@ export const jiraUpdateWorklogTool: ToolConfig<JiraUpdateWorklogParams, JiraUpda
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -13,6 +13,8 @@ export const jiraWriteTool: ToolConfig<JiraWriteParams, JiraWriteResponse> = {
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -13,6 +13,8 @@ export const jsmAddCommentTool: ToolConfig<JsmAddCommentParams, JsmAddCommentRes
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -12,6 +12,8 @@ export const jsmAddCustomerTool: ToolConfig<JsmAddCustomerParams, JsmAddCustomer
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
@@ -15,6 +15,8 @@ export const jsmAddOrganizationTool: ToolConfig<
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
errorExtractor: 'atlassian-errors',
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user