mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(jira): support raw ADF in description and environment fields (#4164)
* fix(security): resolve ReDoS vulnerability in function execute tag pattern Simplified regex to eliminate overlapping quantifiers that caused exponential backtracking on malformed input without closing delimiter. * feat(jira): support raw ADF document objects in description and environment fields Add toAdf() helper that passes through ADF objects as-is or wraps plain text in a single-paragraph ADF doc. Update write and update routes to use it, replacing inline ADF wrapping. Update Zod schema to accept string or object for description. Fully backward compatible — plain text still works, but callers can now pass rich ADF with expand nodes, tables, code blocks, etc. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(jira): handle partial ADF nodes and non-ADF objects in toAdf() Wrap partial ADF nodes (type + content but not doc) in a doc envelope. Fall back to JSON.stringify for non-ADF objects instead of String() which produces [object Object]. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * lint * fix(jira): handle JSON-stringified ADF in toAdf() for variable resolution The executor's formatValueForBlock() JSON.stringify's object values when resolving <Block.output> references. This means an ADF object from an upstream Agent block arrives at the route as a JSON string. toAdf() now detects JSON strings containing valid ADF documents or nodes and parses them back, ensuring rich formatting is preserved through the pipeline. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * lint changes * fix(jira): update environment Zod schema to accept ADF objects Match the description field schema change — environment also passes through toAdf() so its Zod schema must accept objects too. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * updated lobkc --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
48e174b21f
commit
3d6660ba4d
@@ -3,7 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage, toAdf } from '@/tools/jira/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -15,14 +15,14 @@ const jiraUpdateSchema = z.object({
|
||||
issueKey: z.string().min(1, 'Issue key is required'),
|
||||
summary: z.string().optional(),
|
||||
title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
description: z.union([z.string(), z.record(z.unknown())]).optional(),
|
||||
priority: z.string().optional(),
|
||||
assignee: z.string().optional(),
|
||||
labels: z.array(z.string()).optional(),
|
||||
components: z.array(z.string()).optional(),
|
||||
duedate: z.string().optional(),
|
||||
fixVersions: z.array(z.string()).optional(),
|
||||
environment: z.string().optional(),
|
||||
environment: z.union([z.string(), z.record(z.unknown())]).optional(),
|
||||
customFieldId: z.string().optional(),
|
||||
customFieldValue: z.string().optional(),
|
||||
notifyUsers: z.boolean().optional(),
|
||||
@@ -91,21 +91,7 @@ export async function PUT(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (description !== undefined && description !== null && description !== '') {
|
||||
fields.description = {
|
||||
type: 'doc',
|
||||
version: 1,
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: description,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
fields.description = toAdf(description)
|
||||
}
|
||||
|
||||
if (priority !== undefined && priority !== null && priority !== '') {
|
||||
@@ -136,21 +122,7 @@ export async function PUT(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (environment !== undefined && environment !== null && environment !== '') {
|
||||
fields.environment = {
|
||||
type: 'doc',
|
||||
version: 1,
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: environment,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
fields.environment = toAdf(environment)
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage, toAdf } from '@/tools/jira/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -85,21 +85,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (description !== undefined && description !== null && description !== '') {
|
||||
fields.description = {
|
||||
type: 'doc',
|
||||
version: 1,
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: description,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
fields.description = toAdf(description)
|
||||
}
|
||||
|
||||
if (parent !== undefined && parent !== null && parent !== '') {
|
||||
@@ -144,21 +130,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (environment !== undefined && environment !== null && environment !== '') {
|
||||
fields.environment = {
|
||||
type: 'doc',
|
||||
version: 1,
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: environment,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
fields.environment = toAdf(environment)
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@@ -42,7 +42,8 @@ export const jiraUpdateTool: ToolConfig<JiraUpdateParams, JiraUpdateResponse> =
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New description for the issue',
|
||||
description:
|
||||
'New description for the issue. Accepts plain text (auto-wrapped in ADF) or a raw ADF document object',
|
||||
},
|
||||
priority: {
|
||||
type: 'string',
|
||||
|
||||
@@ -5,6 +5,51 @@ const logger = createLogger('JiraUtils')
|
||||
|
||||
const MAX_ATTACHMENT_SIZE = 50 * 1024 * 1024
|
||||
|
||||
/**
|
||||
* Converts a value to ADF format. If the value is already an ADF document object,
|
||||
* it is returned as-is. If it is a plain string, it is wrapped in a single-paragraph ADF doc.
|
||||
*/
|
||||
export function toAdf(value: string | Record<string, unknown>): Record<string, unknown> {
|
||||
if (typeof value === 'object') {
|
||||
if (value.type === 'doc') {
|
||||
return value
|
||||
}
|
||||
if (value.type && Array.isArray(value.content)) {
|
||||
return { type: 'doc', version: 1, content: [value] }
|
||||
}
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(value)
|
||||
if (typeof parsed === 'object' && parsed !== null && parsed.type === 'doc') {
|
||||
return parsed
|
||||
}
|
||||
if (
|
||||
typeof parsed === 'object' &&
|
||||
parsed !== null &&
|
||||
parsed.type &&
|
||||
Array.isArray(parsed.content)
|
||||
) {
|
||||
return { type: 'doc', version: 1, content: [parsed] }
|
||||
}
|
||||
} catch {
|
||||
// Not JSON — treat as plain text below
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'doc',
|
||||
version: 1,
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value) },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts plain text from Atlassian Document Format (ADF) content.
|
||||
* Returns null if content is falsy.
|
||||
|
||||
@@ -42,7 +42,8 @@ export const jiraWriteTool: ToolConfig<JiraWriteParams, JiraWriteResponse> = {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Description for the issue',
|
||||
description:
|
||||
'Description for the issue. Accepts plain text (auto-wrapped in ADF) or a raw ADF document object',
|
||||
},
|
||||
priority: {
|
||||
type: 'string',
|
||||
|
||||
Reference in New Issue
Block a user