feat(confluence): return page content in get page version tool (#3344)

* feat(confluence): return page content in get page version tool

* lint
This commit is contained in:
Waleed
2026-02-25 13:45:19 -08:00
committed by GitHub
parent 16f337f6fd
commit f625482bcb
4 changed files with 110 additions and 26 deletions
@@ -326,6 +326,8 @@ Get details about a specific version of a Confluence page.
| --------- | ---- | ----------- |
| `ts` | string | ISO 8601 timestamp of the operation |
| `pageId` | string | ID of the page |
| `title` | string | Page title at this version |
| `content` | string | Page content with HTML tags stripped at this version |
| `version` | object | Detailed version information |
| ↳ `number` | number | Version number |
| ↳ `message` | string | Version message |
@@ -336,6 +338,9 @@ Get details about a specific version of a Confluence page.
| ↳ `collaborators` | array | List of collaborator account IDs for this version |
| ↳ `prevVersion` | number | Previous version number |
| ↳ `nextVersion` | number | Next version number |
| `body` | object | Raw page body content in storage format at this version |
| ↳ `value` | string | The content value in the specified format |
| ↳ `representation` | string | Content representation type |
### `confluence_list_page_properties`
@@ -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 { getConfluenceCloudId } from '@/tools/confluence/utils'
import { cleanHtmlContent, getConfluenceCloudId } from '@/tools/confluence/utils'
const logger = createLogger('ConfluencePageVersionsAPI')
@@ -55,42 +55,79 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
}
// If versionNumber is provided, get specific version
// If versionNumber is provided, get specific version with page content
if (versionNumber !== undefined && versionNumber !== null) {
const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}/versions/${versionNumber}`
const versionUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}/versions/${versionNumber}`
const pageUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}?version=${versionNumber}&body-format=storage`
logger.info(`Fetching version ${versionNumber} for page ${pageId}`)
const response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})
const [versionResponse, pageResponse] = await Promise.all([
fetch(versionUrl, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
}),
fetch(pageUrl, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
}),
])
if (!response.ok) {
const errorData = await response.json().catch(() => null)
if (!versionResponse.ok) {
const errorData = await versionResponse.json().catch(() => null)
logger.error('Confluence API error response:', {
status: response.status,
statusText: response.statusText,
status: versionResponse.status,
statusText: versionResponse.statusText,
error: JSON.stringify(errorData, null, 2),
})
const errorMessage = errorData?.message || `Failed to get page version (${response.status})`
return NextResponse.json({ error: errorMessage }, { status: response.status })
const errorMessage =
errorData?.message || `Failed to get page version (${versionResponse.status})`
return NextResponse.json({ error: errorMessage }, { status: versionResponse.status })
}
const data = await response.json()
const versionData = await versionResponse.json()
let title: string | null = null
let content: string | null = null
let body: Record<string, unknown> | null = null
if (pageResponse.ok) {
const pageData = await pageResponse.json()
title = pageData.title ?? null
body = pageData.body ?? null
const rawContent =
pageData.body?.storage?.value ||
pageData.body?.view?.value ||
pageData.body?.atlas_doc_format?.value ||
''
if (rawContent) {
content = cleanHtmlContent(rawContent)
}
} else {
logger.warn(
`Could not fetch page content for version ${versionNumber}: ${pageResponse.status}`
)
}
return NextResponse.json({
version: {
number: data.number,
message: data.message ?? null,
minorEdit: data.minorEdit ?? false,
authorId: data.authorId ?? null,
createdAt: data.createdAt ?? null,
number: versionData.number,
message: versionData.message ?? null,
minorEdit: versionData.minorEdit ?? false,
authorId: versionData.authorId ?? null,
createdAt: versionData.createdAt ?? null,
},
pageId,
title,
content,
body,
})
}
// List all versions
+35 -1
View File
@@ -1,4 +1,8 @@
import { DETAILED_VERSION_OUTPUT_PROPERTIES, TIMESTAMP_OUTPUT } from '@/tools/confluence/types'
import {
BODY_FORMAT_PROPERTIES,
DETAILED_VERSION_OUTPUT_PROPERTIES,
TIMESTAMP_OUTPUT,
} from '@/tools/confluence/types'
import type { ToolConfig } from '@/tools/types'
export interface ConfluenceGetPageVersionParams {
@@ -14,6 +18,8 @@ export interface ConfluenceGetPageVersionResponse {
output: {
ts: string
pageId: string
title: string | null
content: string | null
version: {
number: number
message: string | null
@@ -25,6 +31,12 @@ export interface ConfluenceGetPageVersionResponse {
prevVersion: number | null
nextVersion: number | null
}
body: {
storage?: {
value: string
representation: string
}
} | null
}
}
@@ -100,6 +112,8 @@ export const confluenceGetPageVersionTool: ToolConfig<
output: {
ts: new Date().toISOString(),
pageId: data.pageId ?? '',
title: data.title ?? null,
content: data.content ?? null,
version: data.version ?? {
number: 0,
message: null,
@@ -107,6 +121,7 @@ export const confluenceGetPageVersionTool: ToolConfig<
authorId: null,
createdAt: null,
},
body: data.body ?? null,
},
}
},
@@ -114,10 +129,29 @@ export const confluenceGetPageVersionTool: ToolConfig<
outputs: {
ts: TIMESTAMP_OUTPUT,
pageId: { type: 'string', description: 'ID of the page' },
title: { type: 'string', description: 'Page title at this version', optional: true },
content: {
type: 'string',
description: 'Page content with HTML tags stripped at this version',
optional: true,
},
version: {
type: 'object',
description: 'Detailed version information',
properties: DETAILED_VERSION_OUTPUT_PROPERTIES,
},
body: {
type: 'object',
description: 'Raw page body content in storage format at this version',
properties: {
storage: {
type: 'object',
description: 'Body in storage format (Confluence markup)',
properties: BODY_FORMAT_PROPERTIES,
optional: true,
},
},
optional: true,
},
},
}
+11 -3
View File
@@ -56,13 +56,21 @@ function stripHtmlTags(html: string): string {
return text.trim()
}
/**
* Strips HTML tags and decodes HTML entities from raw Confluence content.
*/
export function cleanHtmlContent(rawContent: string): string {
let content = stripHtmlTags(rawContent)
content = decodeHtmlEntities(content)
content = content.replace(/\s+/g, ' ').trim()
return content
}
export function transformPageData(data: any) {
const rawContent =
data.body?.storage?.value || data.body?.view?.value || data.body?.atlas_doc_format?.value || ''
let cleanContent = stripHtmlTags(rawContent)
cleanContent = decodeHtmlEntities(cleanContent)
cleanContent = cleanContent.replace(/\s+/g, ' ').trim()
const cleanContent = cleanHtmlContent(rawContent)
return {
success: true,