chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ServiceNowAggregateParams,
|
||||
ServiceNowAggregateResponse,
|
||||
} from '@/tools/servicenow/types'
|
||||
import { createBasicAuthHeader } from '@/tools/servicenow/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ServiceNowAggregateTool')
|
||||
|
||||
export const aggregateTool: ToolConfig<ServiceNowAggregateParams, ServiceNowAggregateResponse> = {
|
||||
id: 'servicenow_aggregate',
|
||||
name: 'Aggregate ServiceNow Records',
|
||||
description:
|
||||
'Compute aggregate statistics (count, sum, average, min, max, group by) over a ServiceNow table',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow instance URL (e.g., https://instance.service-now.com)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow password',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table name (e.g., incident, change_request, task)',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Encoded query string to filter records before aggregating (e.g., "active=true")',
|
||||
},
|
||||
count: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return the count of matching records',
|
||||
},
|
||||
groupBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated fields to group results by (e.g., category,priority)',
|
||||
},
|
||||
avgFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated numeric fields to average (e.g., reassignment_count)',
|
||||
},
|
||||
sumFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated numeric fields to sum',
|
||||
},
|
||||
minFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated fields to compute the minimum of',
|
||||
},
|
||||
maxFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated fields to compute the maximum of',
|
||||
},
|
||||
having: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter on aggregate results (e.g., "count>5")',
|
||||
},
|
||||
displayValue: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return display values for grouped reference fields: "true", "false", or "all"',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
const url = `${baseUrl}/api/now/stats/${params.tableName.trim()}`
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.query) {
|
||||
queryParams.append('sysparm_query', params.query)
|
||||
}
|
||||
if (params.count) {
|
||||
queryParams.append('sysparm_count', 'true')
|
||||
}
|
||||
if (params.groupBy) {
|
||||
queryParams.append('sysparm_group_by', params.groupBy)
|
||||
}
|
||||
if (params.avgFields) {
|
||||
queryParams.append('sysparm_avg_fields', params.avgFields)
|
||||
}
|
||||
if (params.sumFields) {
|
||||
queryParams.append('sysparm_sum_fields', params.sumFields)
|
||||
}
|
||||
if (params.minFields) {
|
||||
queryParams.append('sysparm_min_fields', params.minFields)
|
||||
}
|
||||
if (params.maxFields) {
|
||||
queryParams.append('sysparm_max_fields', params.maxFields)
|
||||
}
|
||||
if (params.having) {
|
||||
queryParams.append('sysparm_having', params.having)
|
||||
}
|
||||
if (params.displayValue) {
|
||||
queryParams.append('sysparm_display_value', params.displayValue)
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${url}?${queryString}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.username || !params.password) {
|
||||
throw new Error('ServiceNow username and password are required')
|
||||
}
|
||||
return {
|
||||
Authorization: createBasicAuthHeader(params.username, params.password),
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
try {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
const error = data.error || data
|
||||
throw new Error(typeof error === 'string' ? error : error.message || JSON.stringify(error))
|
||||
}
|
||||
|
||||
const result = data.result ?? null
|
||||
const grouped = Array.isArray(result)
|
||||
const count = !grouped && result?.stats?.count != null ? Number(result.stats.count) : null
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
result,
|
||||
count,
|
||||
metadata: {
|
||||
grouped,
|
||||
groupCount: grouped ? result.length : null,
|
||||
},
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('ServiceNow aggregate - Error processing response:', { error })
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
result: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Aggregate result. Ungrouped: {stats: {count, sum, avg, min, max}}. Grouped: array of {stats, groupby_fields}.',
|
||||
},
|
||||
count: {
|
||||
type: 'number',
|
||||
description: 'Total matching record count (only present for ungrouped count queries)',
|
||||
optional: true,
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata (grouped, groupCount)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ServiceNowCreateParams, ServiceNowCreateResponse } from '@/tools/servicenow/types'
|
||||
import { createBasicAuthHeader } from '@/tools/servicenow/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ServiceNowCreateRecordTool')
|
||||
|
||||
export const createRecordTool: ToolConfig<ServiceNowCreateParams, ServiceNowCreateResponse> = {
|
||||
id: 'servicenow_create_record',
|
||||
name: 'Create ServiceNow Record',
|
||||
description: 'Create a new record in a ServiceNow table',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow instance URL (e.g., https://instance.service-now.com)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow password',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table name (e.g., incident, task, sys_user)',
|
||||
},
|
||||
fields: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Fields to set on the record as JSON object (e.g., {"short_description": "Issue title", "priority": "1"})',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
return `${baseUrl}/api/now/table/${params.tableName.trim()}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.username || !params.password) {
|
||||
throw new Error('ServiceNow username and password are required')
|
||||
}
|
||||
return {
|
||||
Authorization: createBasicAuthHeader(params.username, params.password),
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
if (!params.fields || typeof params.fields !== 'object') {
|
||||
throw new Error('Fields must be a JSON object')
|
||||
}
|
||||
return params.fields
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
try {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
const error = data.error || data
|
||||
throw new Error(typeof error === 'string' ? error : error.message || JSON.stringify(error))
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
record: data.result,
|
||||
metadata: {
|
||||
recordCount: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('ServiceNow create record - Error processing response:', { error })
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
record: {
|
||||
type: 'json',
|
||||
description: 'Created ServiceNow record with sys_id and other fields',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ServiceNowDeleteParams, ServiceNowDeleteResponse } from '@/tools/servicenow/types'
|
||||
import { createBasicAuthHeader } from '@/tools/servicenow/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ServiceNowDeleteRecordTool')
|
||||
|
||||
export const deleteRecordTool: ToolConfig<ServiceNowDeleteParams, ServiceNowDeleteResponse> = {
|
||||
id: 'servicenow_delete_record',
|
||||
name: 'Delete ServiceNow Record',
|
||||
description: 'Delete a record from a ServiceNow table',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow instance URL (e.g., https://instance.service-now.com)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow password',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table name (e.g., incident, task, sys_user, change_request)',
|
||||
},
|
||||
sysId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Record sys_id to delete (e.g., 6816f79cc0a8016401c5a33be04be441)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
return `${baseUrl}/api/now/table/${params.tableName.trim()}/${params.sysId.trim()}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
if (!params.username || !params.password) {
|
||||
throw new Error('ServiceNow username and password are required')
|
||||
}
|
||||
return {
|
||||
Authorization: createBasicAuthHeader(params.username, params.password),
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: ServiceNowDeleteParams) => {
|
||||
try {
|
||||
if (!response.ok) {
|
||||
let errorData: any
|
||||
try {
|
||||
errorData = await response.json()
|
||||
} catch {
|
||||
errorData = { status: response.status, statusText: response.statusText }
|
||||
}
|
||||
throw new Error(
|
||||
typeof errorData === 'string'
|
||||
? errorData
|
||||
: errorData.error?.message || JSON.stringify(errorData)
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
success: true,
|
||||
metadata: {
|
||||
deletedSysId: params?.sysId || '',
|
||||
},
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('ServiceNow delete record - Error processing response:', { error })
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the deletion was successful',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ServiceNowDownloadAttachmentParams,
|
||||
ServiceNowDownloadAttachmentResponse,
|
||||
} from '@/tools/servicenow/types'
|
||||
import { createBasicAuthHeader } from '@/tools/servicenow/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ServiceNowDownloadAttachmentTool')
|
||||
|
||||
export const downloadAttachmentTool: ToolConfig<
|
||||
ServiceNowDownloadAttachmentParams,
|
||||
ServiceNowDownloadAttachmentResponse
|
||||
> = {
|
||||
id: 'servicenow_download_attachment',
|
||||
name: 'Download ServiceNow Attachment',
|
||||
description: 'Download an attachment file from ServiceNow by its sys_id',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow instance URL (e.g., https://instance.service-now.com)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow password',
|
||||
},
|
||||
attachmentSysId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'sys_id of the attachment to download (from List Attachments)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
return `${baseUrl}/api/now/attachment/${params.attachmentSysId.trim()}/file`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.username || !params.password) {
|
||||
throw new Error('ServiceNow username and password are required')
|
||||
}
|
||||
return {
|
||||
Authorization: createBasicAuthHeader(params.username, params.password),
|
||||
Accept: '*/*',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('ServiceNow download attachment - request failed', {
|
||||
status: response.status,
|
||||
errorText,
|
||||
})
|
||||
throw new Error(errorText || `Failed to download attachment: ${response.status}`)
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') || 'application/octet-stream'
|
||||
const contentDisposition = response.headers.get('content-disposition')
|
||||
let fileName = 'attachment'
|
||||
|
||||
if (contentDisposition) {
|
||||
const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
|
||||
if (match?.[1]) {
|
||||
fileName = match[1].replace(/['"]/g, '')
|
||||
}
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer()
|
||||
const buffer = Buffer.from(arrayBuffer)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
file: {
|
||||
name: fileName,
|
||||
mimeType: contentType,
|
||||
data: buffer.toString('base64'),
|
||||
size: buffer.length,
|
||||
},
|
||||
content: buffer.toString('base64'),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
file: {
|
||||
type: 'file',
|
||||
description: 'Downloaded attachment stored in execution files',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Base64 encoded file content',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { aggregateTool } from '@/tools/servicenow/aggregate'
|
||||
import { createRecordTool } from '@/tools/servicenow/create_record'
|
||||
import { deleteRecordTool } from '@/tools/servicenow/delete_record'
|
||||
import { downloadAttachmentTool } from '@/tools/servicenow/download_attachment'
|
||||
import { listAttachmentsTool } from '@/tools/servicenow/list_attachments'
|
||||
import { readRecordTool } from '@/tools/servicenow/read_record'
|
||||
import { updateRecordTool } from '@/tools/servicenow/update_record'
|
||||
import { uploadAttachmentTool } from '@/tools/servicenow/upload_attachment'
|
||||
|
||||
export {
|
||||
createRecordTool as servicenowCreateRecordTool,
|
||||
readRecordTool as servicenowReadRecordTool,
|
||||
updateRecordTool as servicenowUpdateRecordTool,
|
||||
deleteRecordTool as servicenowDeleteRecordTool,
|
||||
aggregateTool as servicenowAggregateTool,
|
||||
listAttachmentsTool as servicenowListAttachmentsTool,
|
||||
downloadAttachmentTool as servicenowDownloadAttachmentTool,
|
||||
uploadAttachmentTool as servicenowUploadAttachmentTool,
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ServiceNowListAttachmentsParams,
|
||||
ServiceNowListAttachmentsResponse,
|
||||
} from '@/tools/servicenow/types'
|
||||
import { createBasicAuthHeader } from '@/tools/servicenow/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ServiceNowListAttachmentsTool')
|
||||
|
||||
export const listAttachmentsTool: ToolConfig<
|
||||
ServiceNowListAttachmentsParams,
|
||||
ServiceNowListAttachmentsResponse
|
||||
> = {
|
||||
id: 'servicenow_list_attachments',
|
||||
name: 'List ServiceNow Attachments',
|
||||
description: 'List the attachments on a ServiceNow record',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow instance URL (e.g., https://instance.service-now.com)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow password',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table that owns the record (e.g., incident, change_request)',
|
||||
},
|
||||
recordSysId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'sys_id of the record whose attachments should be listed',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of attachments to return',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append(
|
||||
'sysparm_query',
|
||||
`table_name=${params.tableName.trim()}^table_sys_id=${params.recordSysId.trim()}`
|
||||
)
|
||||
if (params.limit) {
|
||||
queryParams.append('sysparm_limit', params.limit.toString())
|
||||
}
|
||||
|
||||
return `${baseUrl}/api/now/attachment?${queryParams.toString()}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.username || !params.password) {
|
||||
throw new Error('ServiceNow username and password are required')
|
||||
}
|
||||
return {
|
||||
Authorization: createBasicAuthHeader(params.username, params.password),
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
try {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
const error = data.error || data
|
||||
throw new Error(typeof error === 'string' ? error : error.message || JSON.stringify(error))
|
||||
}
|
||||
|
||||
const attachments = Array.isArray(data.result) ? data.result : []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
attachments,
|
||||
metadata: {
|
||||
recordCount: attachments.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('ServiceNow list attachments - Error processing response:', { error })
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
attachments: {
|
||||
type: 'array',
|
||||
description: 'Attachment metadata records',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sys_id: { type: 'string', description: 'Attachment sys_id' },
|
||||
file_name: { type: 'string', description: 'File name' },
|
||||
content_type: { type: 'string', description: 'MIME type' },
|
||||
size_bytes: { type: 'string', description: 'File size in bytes' },
|
||||
download_link: { type: 'string', description: 'Direct download URL for the file' },
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata (recordCount)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ServiceNowReadParams, ServiceNowReadResponse } from '@/tools/servicenow/types'
|
||||
import { createBasicAuthHeader } from '@/tools/servicenow/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ServiceNowReadRecordTool')
|
||||
|
||||
export const readRecordTool: ToolConfig<ServiceNowReadParams, ServiceNowReadResponse> = {
|
||||
id: 'servicenow_read_record',
|
||||
name: 'Read ServiceNow Records',
|
||||
description: 'Read records from a ServiceNow table',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow instance URL (e.g., https://instance.service-now.com)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow password',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table name (e.g., incident, task, sys_user, change_request)',
|
||||
},
|
||||
sysId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Specific record sys_id (e.g., 6816f79cc0a8016401c5a33be04be441)',
|
||||
},
|
||||
number: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Record number (e.g., INC0010001)',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Encoded query string (e.g., "active=true^priority=1")',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of records to return (e.g., 10, 50, 100)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of records to skip for pagination (e.g., 0, 10, 20)',
|
||||
},
|
||||
fields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Comma-separated list of fields to return (e.g., sys_id,number,short_description,state)',
|
||||
},
|
||||
displayValue: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Return display values for reference fields: "true" (display only), "false" (sys_id only), or "all" (both)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
let url = `${baseUrl}/api/now/table/${params.tableName.trim()}`
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.sysId) {
|
||||
url = `${url}/${params.sysId.trim()}`
|
||||
} else if (params.number) {
|
||||
const numberQuery = `number=${params.number}`
|
||||
const existingQuery = params.query
|
||||
queryParams.append(
|
||||
'sysparm_query',
|
||||
existingQuery ? `${existingQuery}^${numberQuery}` : numberQuery
|
||||
)
|
||||
} else if (params.query) {
|
||||
queryParams.append('sysparm_query', params.query)
|
||||
}
|
||||
|
||||
if (params.limit) {
|
||||
queryParams.append('sysparm_limit', params.limit.toString())
|
||||
}
|
||||
|
||||
if (params.offset !== undefined && params.offset !== null) {
|
||||
queryParams.append('sysparm_offset', params.offset.toString())
|
||||
}
|
||||
|
||||
if (params.fields) {
|
||||
queryParams.append('sysparm_fields', params.fields)
|
||||
}
|
||||
|
||||
if (params.displayValue) {
|
||||
queryParams.append('sysparm_display_value', params.displayValue)
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString ? `${url}?${queryString}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.username || !params.password) {
|
||||
throw new Error('ServiceNow username and password are required')
|
||||
}
|
||||
return {
|
||||
Authorization: createBasicAuthHeader(params.username, params.password),
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
try {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
const error = data.error || data
|
||||
throw new Error(typeof error === 'string' ? error : error.message || JSON.stringify(error))
|
||||
}
|
||||
|
||||
const records = Array.isArray(data.result) ? data.result : [data.result]
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
records,
|
||||
metadata: {
|
||||
recordCount: records.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('ServiceNow read record - Error processing response:', { error })
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
records: {
|
||||
type: 'array',
|
||||
description: 'Array of ServiceNow records',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
interface ServiceNowRecord {
|
||||
sys_id: string
|
||||
number?: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface ServiceNowBaseParams {
|
||||
instanceUrl: string
|
||||
username: string
|
||||
password: string
|
||||
tableName: string
|
||||
}
|
||||
|
||||
export interface ServiceNowCreateParams extends ServiceNowBaseParams {
|
||||
fields: Record<string, any>
|
||||
}
|
||||
|
||||
export interface ServiceNowCreateResponse extends ToolResponse {
|
||||
output: {
|
||||
record: ServiceNowRecord
|
||||
metadata: {
|
||||
recordCount: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceNowReadParams extends ServiceNowBaseParams {
|
||||
sysId?: string
|
||||
number?: string
|
||||
query?: string
|
||||
limit?: number
|
||||
offset?: number
|
||||
fields?: string
|
||||
displayValue?: string
|
||||
}
|
||||
|
||||
export interface ServiceNowReadResponse extends ToolResponse {
|
||||
output: {
|
||||
records: ServiceNowRecord[]
|
||||
metadata: {
|
||||
recordCount: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceNowUpdateParams extends ServiceNowBaseParams {
|
||||
sysId: string
|
||||
fields: Record<string, any>
|
||||
}
|
||||
|
||||
export interface ServiceNowUpdateResponse extends ToolResponse {
|
||||
output: {
|
||||
record: ServiceNowRecord
|
||||
metadata: {
|
||||
recordCount: 1
|
||||
updatedFields: string[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceNowDeleteParams extends ServiceNowBaseParams {
|
||||
sysId: string
|
||||
}
|
||||
|
||||
export interface ServiceNowDeleteResponse extends ToolResponse {
|
||||
output: {
|
||||
success: boolean
|
||||
metadata: {
|
||||
deletedSysId: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceNowAggregateParams extends ServiceNowBaseParams {
|
||||
query?: string
|
||||
count?: boolean
|
||||
groupBy?: string
|
||||
avgFields?: string
|
||||
sumFields?: string
|
||||
minFields?: string
|
||||
maxFields?: string
|
||||
having?: string
|
||||
displayValue?: string
|
||||
}
|
||||
|
||||
export interface ServiceNowAggregateResponse extends ToolResponse {
|
||||
output: {
|
||||
result: Record<string, any> | Record<string, any>[] | null
|
||||
count: number | null
|
||||
metadata: {
|
||||
grouped: boolean
|
||||
groupCount: number | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceNowAttachment {
|
||||
sys_id: string
|
||||
file_name: string
|
||||
content_type: string
|
||||
size_bytes?: string
|
||||
table_name?: string
|
||||
table_sys_id?: string
|
||||
download_link?: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface ServiceNowListAttachmentsParams {
|
||||
instanceUrl: string
|
||||
username: string
|
||||
password: string
|
||||
tableName: string
|
||||
recordSysId: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface ServiceNowListAttachmentsResponse extends ToolResponse {
|
||||
output: {
|
||||
attachments: ServiceNowAttachment[]
|
||||
metadata: {
|
||||
recordCount: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceNowDownloadAttachmentParams {
|
||||
instanceUrl: string
|
||||
username: string
|
||||
password: string
|
||||
attachmentSysId: string
|
||||
}
|
||||
|
||||
export interface ServiceNowDownloadAttachmentResponse extends ToolResponse {
|
||||
output: {
|
||||
file: {
|
||||
name: string
|
||||
mimeType: string
|
||||
data: string
|
||||
size: number
|
||||
}
|
||||
content: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServiceNowUploadAttachmentParams {
|
||||
instanceUrl: string
|
||||
username: string
|
||||
password: string
|
||||
tableName: string
|
||||
recordSysId: string
|
||||
fileName: string
|
||||
file?: unknown
|
||||
}
|
||||
|
||||
export interface ServiceNowUploadAttachmentResponse extends ToolResponse {
|
||||
output: {
|
||||
attachment: ServiceNowAttachment
|
||||
metadata: {
|
||||
recordCount: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ServiceNowResponse =
|
||||
| ServiceNowCreateResponse
|
||||
| ServiceNowReadResponse
|
||||
| ServiceNowUpdateResponse
|
||||
| ServiceNowDeleteResponse
|
||||
| ServiceNowAggregateResponse
|
||||
| ServiceNowListAttachmentsResponse
|
||||
| ServiceNowDownloadAttachmentResponse
|
||||
| ServiceNowUploadAttachmentResponse
|
||||
@@ -0,0 +1,115 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ServiceNowUpdateParams, ServiceNowUpdateResponse } from '@/tools/servicenow/types'
|
||||
import { createBasicAuthHeader } from '@/tools/servicenow/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ServiceNowUpdateRecordTool')
|
||||
|
||||
export const updateRecordTool: ToolConfig<ServiceNowUpdateParams, ServiceNowUpdateResponse> = {
|
||||
id: 'servicenow_update_record',
|
||||
name: 'Update ServiceNow Record',
|
||||
description: 'Update an existing record in a ServiceNow table',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow instance URL (e.g., https://instance.service-now.com)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow password',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table name (e.g., incident, task, sys_user, change_request)',
|
||||
},
|
||||
sysId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Record sys_id to update (e.g., 6816f79cc0a8016401c5a33be04be441)',
|
||||
},
|
||||
fields: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Fields to update as JSON object (e.g., {"state": "2", "priority": "1"})',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.instanceUrl.trim().replace(/\/$/, '')
|
||||
if (!baseUrl) {
|
||||
throw new Error('ServiceNow instance URL is required')
|
||||
}
|
||||
return `${baseUrl}/api/now/table/${params.tableName.trim()}/${params.sysId.trim()}`
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
if (!params.username || !params.password) {
|
||||
throw new Error('ServiceNow username and password are required')
|
||||
}
|
||||
return {
|
||||
Authorization: createBasicAuthHeader(params.username, params.password),
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
if (!params.fields || typeof params.fields !== 'object') {
|
||||
throw new Error('Fields must be a JSON object')
|
||||
}
|
||||
return params.fields
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: ServiceNowUpdateParams) => {
|
||||
try {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
const error = data.error || data
|
||||
throw new Error(typeof error === 'string' ? error : error.message || JSON.stringify(error))
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
record: data.result,
|
||||
metadata: {
|
||||
recordCount: 1,
|
||||
updatedFields: params ? Object.keys(params.fields || {}) : [],
|
||||
},
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('ServiceNow update record - Error processing response:', { error })
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
record: {
|
||||
type: 'json',
|
||||
description: 'Updated ServiceNow record',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ServiceNowUploadAttachmentParams,
|
||||
ServiceNowUploadAttachmentResponse,
|
||||
} from '@/tools/servicenow/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ServiceNowUploadAttachmentTool')
|
||||
|
||||
export const uploadAttachmentTool: ToolConfig<
|
||||
ServiceNowUploadAttachmentParams,
|
||||
ServiceNowUploadAttachmentResponse
|
||||
> = {
|
||||
id: 'servicenow_upload_attachment',
|
||||
name: 'Upload ServiceNow Attachment',
|
||||
description: 'Attach a file to a ServiceNow record',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow instance URL (e.g., https://instance.service-now.com)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'ServiceNow password',
|
||||
},
|
||||
tableName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table that owns the record (e.g., incident, change_request)',
|
||||
},
|
||||
recordSysId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'sys_id of the record to attach the file to',
|
||||
},
|
||||
fileName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name to give the uploaded file (e.g., logs.txt)',
|
||||
},
|
||||
file: {
|
||||
type: 'file',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'File to upload (UserFile object)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/servicenow/upload-attachment',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
instanceUrl: params.instanceUrl,
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
tableName: params.tableName,
|
||||
recordSysId: params.recordSysId,
|
||||
fileName: params.fileName,
|
||||
file: params.file,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
logger.error('ServiceNow upload attachment failed', { error: data.error })
|
||||
throw new Error(data.error || 'ServiceNow upload attachment failed')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: data.output,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
attachment: {
|
||||
type: 'json',
|
||||
description: 'Created attachment metadata (sys_id, file_name, content_type, download_link)',
|
||||
},
|
||||
metadata: {
|
||||
type: 'json',
|
||||
description: 'Operation metadata',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Creates a Basic Authentication header from username and password
|
||||
* @param username ServiceNow username
|
||||
* @param password ServiceNow password
|
||||
* @returns Base64 encoded Basic Auth header value
|
||||
*/
|
||||
export function createBasicAuthHeader(username: string, password: string): string {
|
||||
const credentials = Buffer.from(`${username}:${password}`).toString('base64')
|
||||
return `Basic ${credentials}`
|
||||
}
|
||||
Reference in New Issue
Block a user