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,142 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import {
|
||||
buildZendeskUrl,
|
||||
handleZendeskError,
|
||||
METADATA_OUTPUT,
|
||||
ORGANIZATIONS_ARRAY_OUTPUT,
|
||||
PAGING_OUTPUT,
|
||||
} from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskAutocompleteOrganizationsParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
name: string
|
||||
perPage?: string
|
||||
page?: string
|
||||
}
|
||||
|
||||
export interface ZendeskAutocompleteOrganizationsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
organizations: any[]
|
||||
paging?: {
|
||||
after_cursor: string | null
|
||||
has_more: boolean
|
||||
next_page?: string | null
|
||||
}
|
||||
metadata: {
|
||||
total_returned: number
|
||||
has_more: boolean
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskAutocompleteOrganizationsTool: ToolConfig<
|
||||
ZendeskAutocompleteOrganizationsParams,
|
||||
ZendeskAutocompleteOrganizationsResponse
|
||||
> = {
|
||||
id: 'zendesk_autocomplete_organizations',
|
||||
name: 'Autocomplete Organizations in Zendesk',
|
||||
description:
|
||||
'Autocomplete organizations in Zendesk by name prefix (for name matching/autocomplete)',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization name prefix to search for (e.g., "Acme")',
|
||||
},
|
||||
perPage: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page as a number string (default: "100", max: "100")',
|
||||
},
|
||||
page: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number for pagination (1-based)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('name', params.name)
|
||||
if (params.perPage) queryParams.append('per_page', params.perPage)
|
||||
if (params.page) queryParams.append('page', params.page)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildZendeskUrl(params.subdomain, '/organizations/autocomplete')
|
||||
return `${url}?${query}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'autocomplete_organizations')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const organizations = data.organizations || []
|
||||
const hasMore = data.next_page !== null && data.next_page !== undefined
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
organizations,
|
||||
// /organizations/autocomplete uses offset pagination (page/per_page), not cursor pagination.
|
||||
// after_cursor is always null; use next_page URL or page param for subsequent pages.
|
||||
paging: {
|
||||
after_cursor: null,
|
||||
has_more: hasMore,
|
||||
next_page: data.next_page ?? null,
|
||||
},
|
||||
metadata: {
|
||||
total_returned: organizations.length,
|
||||
has_more: hasMore,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
organizations: ORGANIZATIONS_ARRAY_OUTPUT,
|
||||
paging: PAGING_OUTPUT,
|
||||
metadata: METADATA_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import {
|
||||
buildZendeskUrl,
|
||||
handleZendeskError,
|
||||
ORGANIZATION_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/zendesk/types'
|
||||
|
||||
const logger = createLogger('ZendeskCreateOrganization')
|
||||
|
||||
export interface ZendeskCreateOrganizationParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
name: string
|
||||
domainNames?: string
|
||||
details?: string
|
||||
notes?: string
|
||||
tags?: string
|
||||
customFields?: string
|
||||
}
|
||||
|
||||
export interface ZendeskCreateOrganizationResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
organization: any
|
||||
organization_id: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskCreateOrganizationTool: ToolConfig<
|
||||
ZendeskCreateOrganizationParams,
|
||||
ZendeskCreateOrganizationResponse
|
||||
> = {
|
||||
id: 'zendesk_create_organization',
|
||||
name: 'Create Organization in Zendesk',
|
||||
description: 'Create a new organization in Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization name (e.g., "Acme Corporation")',
|
||||
},
|
||||
domainNames: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated domain names (e.g., "acme.com, acme.org")',
|
||||
},
|
||||
details: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization details text',
|
||||
},
|
||||
notes: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization notes text',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated tags (e.g., "enterprise, priority")',
|
||||
},
|
||||
customFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Custom fields as JSON object (e.g., {"field_id": "value"})',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, '/organizations'),
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const organization: any = {
|
||||
name: params.name,
|
||||
}
|
||||
|
||||
if (params.domainNames)
|
||||
organization.domain_names = params.domainNames.split(',').map((d) => d.trim())
|
||||
if (params.details) organization.details = params.details
|
||||
if (params.notes) organization.notes = params.notes
|
||||
if (params.tags) organization.tags = params.tags.split(',').map((t) => t.trim())
|
||||
|
||||
if (params.customFields) {
|
||||
try {
|
||||
const customFields = JSON.parse(params.customFields)
|
||||
organization.organization_fields = customFields
|
||||
} catch (error) {
|
||||
logger.warn('Failed to parse custom fields', { error })
|
||||
}
|
||||
}
|
||||
|
||||
return { organization }
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'create_organization')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
organization: data.organization,
|
||||
organization_id: data.organization?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
organization: {
|
||||
type: 'object',
|
||||
description: 'Created organization object',
|
||||
properties: ORGANIZATION_OUTPUT_PROPERTIES,
|
||||
},
|
||||
organization_id: { type: 'number', description: 'The created organization ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError, JOB_STATUS_OUTPUT } from '@/tools/zendesk/types'
|
||||
|
||||
const logger = createLogger('ZendeskCreateOrganizationsBulk')
|
||||
|
||||
export interface ZendeskCreateOrganizationsBulkParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
organizations: string
|
||||
}
|
||||
|
||||
export interface ZendeskCreateOrganizationsBulkResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
job_status: any
|
||||
job_id: string
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskCreateOrganizationsBulkTool: ToolConfig<
|
||||
ZendeskCreateOrganizationsBulkParams,
|
||||
ZendeskCreateOrganizationsBulkResponse
|
||||
> = {
|
||||
id: 'zendesk_create_organizations_bulk',
|
||||
name: 'Bulk Create Organizations in Zendesk',
|
||||
description: 'Create multiple organizations in Zendesk using bulk import',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
organizations: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of organization objects to create (e.g., [{"name": "Org1"}, {"name": "Org2"}])',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, '/organizations/create_many'),
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
try {
|
||||
const organizations = JSON.parse(params.organizations)
|
||||
return { organizations }
|
||||
} catch (error) {
|
||||
logger.error('Failed to parse organizations array', { error })
|
||||
throw new Error('Invalid organizations JSON format')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'create_organizations_bulk')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
job_status: data.job_status,
|
||||
job_id: data.job_status?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
job_status: JOB_STATUS_OUTPUT,
|
||||
job_id: { type: 'string', description: 'The bulk operation job ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import {
|
||||
buildZendeskUrl,
|
||||
handleZendeskError,
|
||||
TICKET_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/zendesk/types'
|
||||
|
||||
const logger = createLogger('ZendeskCreateTicket')
|
||||
|
||||
export interface ZendeskCreateTicketParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
subject: string
|
||||
description: string
|
||||
priority?: string
|
||||
status?: string
|
||||
type?: string
|
||||
tags?: string
|
||||
assigneeId?: string
|
||||
groupId?: string
|
||||
requesterId?: string
|
||||
customFields?: string
|
||||
}
|
||||
|
||||
export interface ZendeskCreateTicketResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
ticket: any
|
||||
ticket_id: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskCreateTicketTool: ToolConfig<
|
||||
ZendeskCreateTicketParams,
|
||||
ZendeskCreateTicketResponse
|
||||
> = {
|
||||
id: 'zendesk_create_ticket',
|
||||
name: 'Create Ticket in Zendesk',
|
||||
description: 'Create a new ticket in Zendesk with support for custom fields',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Ticket subject (optional - will be auto-generated if not provided)',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Ticket description text (first comment)',
|
||||
},
|
||||
priority: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Priority: "low", "normal", "high", or "urgent"',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Status: "new", "open", "pending", "hold", "solved", or "closed"',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Type: "problem", "incident", "question", or "task"',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated tags (e.g., "billing, urgent")',
|
||||
},
|
||||
assigneeId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Assignee user ID as a numeric string (e.g., "12345")',
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group ID as a numeric string (e.g., "67890")',
|
||||
},
|
||||
requesterId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Requester user ID as a numeric string (e.g., "11111")',
|
||||
},
|
||||
customFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Custom fields as JSON object (e.g., {"field_id": "value"})',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, '/tickets'),
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
// Use Basic Authentication with email/token format for Zendesk API tokens
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const ticket: any = {
|
||||
subject: params.subject,
|
||||
comment: { body: params.description },
|
||||
}
|
||||
|
||||
if (params.priority) ticket.priority = params.priority
|
||||
if (params.status) ticket.status = params.status
|
||||
if (params.type) ticket.type = params.type
|
||||
if (params.assigneeId) ticket.assignee_id = params.assigneeId
|
||||
if (params.groupId) ticket.group_id = params.groupId
|
||||
if (params.requesterId) ticket.requester_id = params.requesterId
|
||||
if (params.tags) ticket.tags = params.tags.split(',').map((t) => t.trim())
|
||||
|
||||
if (params.customFields) {
|
||||
try {
|
||||
const customFields = JSON.parse(params.customFields)
|
||||
ticket.custom_fields = Object.entries(customFields).map(([id, value]) => ({ id, value }))
|
||||
} catch (error) {
|
||||
logger.warn('Failed to parse custom fields', { error })
|
||||
}
|
||||
}
|
||||
|
||||
return { ticket }
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'create_ticket')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
ticket: data.ticket,
|
||||
ticket_id: data.ticket?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ticket: {
|
||||
type: 'object',
|
||||
description: 'Created ticket object',
|
||||
properties: TICKET_OUTPUT_PROPERTIES,
|
||||
},
|
||||
ticket_id: { type: 'number', description: 'The created ticket ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError, JOB_STATUS_OUTPUT } from '@/tools/zendesk/types'
|
||||
|
||||
const logger = createLogger('ZendeskCreateTicketsBulk')
|
||||
|
||||
export interface ZendeskCreateTicketsBulkParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
tickets: string
|
||||
}
|
||||
|
||||
export interface ZendeskCreateTicketsBulkResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
job_status: any
|
||||
job_id?: string
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskCreateTicketsBulkTool: ToolConfig<
|
||||
ZendeskCreateTicketsBulkParams,
|
||||
ZendeskCreateTicketsBulkResponse
|
||||
> = {
|
||||
id: 'zendesk_create_tickets_bulk',
|
||||
name: 'Bulk Create Tickets in Zendesk',
|
||||
description: 'Create multiple tickets in Zendesk at once (max 100)',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
tickets: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of ticket objects to create (max 100). Each ticket should have subject and comment properties (e.g., [{"subject": "Issue 1", "comment": {"body": "Description"}}])',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, '/tickets/create_many'),
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
try {
|
||||
const tickets = JSON.parse(params.tickets)
|
||||
return { tickets }
|
||||
} catch (error) {
|
||||
logger.error('Failed to parse tickets JSON', { error })
|
||||
throw new Error('Invalid tickets JSON format')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'create_tickets_bulk')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
job_status: data.job_status,
|
||||
job_id: data.job_status?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
job_status: JOB_STATUS_OUTPUT,
|
||||
job_id: { type: 'string', description: 'The bulk operation job ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError, USER_OUTPUT_PROPERTIES } from '@/tools/zendesk/types'
|
||||
|
||||
const logger = createLogger('ZendeskCreateUser')
|
||||
|
||||
export interface ZendeskCreateUserParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
name: string
|
||||
userEmail?: string
|
||||
role?: string
|
||||
phone?: string
|
||||
organizationId?: string
|
||||
verified?: string
|
||||
tags?: string
|
||||
customFields?: string
|
||||
}
|
||||
|
||||
export interface ZendeskCreateUserResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
user: any
|
||||
user_id: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskCreateUserTool: ToolConfig<ZendeskCreateUserParams, ZendeskCreateUserResponse> =
|
||||
{
|
||||
id: 'zendesk_create_user',
|
||||
name: 'Create User in Zendesk',
|
||||
description: 'Create a new user in Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User full name (e.g., "John Smith")',
|
||||
},
|
||||
userEmail: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User email address (e.g., "john@example.com")',
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User role: "end-user", "agent", or "admin"',
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User phone number (e.g., "+1-555-123-4567")',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID as a numeric string (e.g., "12345")',
|
||||
},
|
||||
verified: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Set to "true" to skip email verification, or "false" otherwise',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated tags (e.g., "vip, enterprise")',
|
||||
},
|
||||
customFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Custom fields as JSON object (e.g., {"field_id": "value"})',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, '/users'),
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const user: any = {}
|
||||
|
||||
if (params.name) user.name = params.name
|
||||
if (params.userEmail) user.email = params.userEmail
|
||||
if (params.role) user.role = params.role
|
||||
if (params.phone) user.phone = params.phone
|
||||
if (params.organizationId) user.organization_id = params.organizationId
|
||||
if (params.verified) user.verified = params.verified === 'true'
|
||||
if (params.tags) user.tags = params.tags.split(',').map((t) => t.trim())
|
||||
|
||||
if (params.customFields) {
|
||||
try {
|
||||
const customFields = JSON.parse(params.customFields)
|
||||
user.user_fields = customFields
|
||||
} catch (error) {
|
||||
logger.warn('Failed to parse custom fields', { error })
|
||||
}
|
||||
}
|
||||
|
||||
return { user }
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'create_user')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
user: data.user,
|
||||
user_id: data.user?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
user: {
|
||||
type: 'object',
|
||||
description: 'Created user object',
|
||||
properties: USER_OUTPUT_PROPERTIES,
|
||||
},
|
||||
user_id: { type: 'number', description: 'The created user ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError, JOB_STATUS_OUTPUT } from '@/tools/zendesk/types'
|
||||
|
||||
const logger = createLogger('ZendeskCreateUsersBulk')
|
||||
|
||||
export interface ZendeskCreateUsersBulkParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
users: string
|
||||
}
|
||||
|
||||
export interface ZendeskCreateUsersBulkResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
job_status: any
|
||||
job_id: string
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskCreateUsersBulkTool: ToolConfig<
|
||||
ZendeskCreateUsersBulkParams,
|
||||
ZendeskCreateUsersBulkResponse
|
||||
> = {
|
||||
id: 'zendesk_create_users_bulk',
|
||||
name: 'Bulk Create Users in Zendesk',
|
||||
description: 'Create multiple users in Zendesk using bulk import',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
users: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of user objects to create (e.g., [{"name": "User1", "email": "user1@example.com"}])',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, '/users/create_many'),
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
try {
|
||||
const users = JSON.parse(params.users)
|
||||
return { users }
|
||||
} catch (error) {
|
||||
logger.error('Failed to parse users array', { error })
|
||||
throw new Error('Invalid users JSON format')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'create_users_bulk')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
job_status: data.job_status,
|
||||
job_id: data.job_status?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
job_status: JOB_STATUS_OUTPUT,
|
||||
job_id: { type: 'string', description: 'The bulk operation job ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError } from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskDeleteOrganizationParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
organizationId: string
|
||||
}
|
||||
|
||||
export interface ZendeskDeleteOrganizationResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
deleted: boolean
|
||||
organization_id: string
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskDeleteOrganizationTool: ToolConfig<
|
||||
ZendeskDeleteOrganizationParams,
|
||||
ZendeskDeleteOrganizationResponse
|
||||
> = {
|
||||
id: 'zendesk_delete_organization',
|
||||
name: 'Delete Organization from Zendesk',
|
||||
description: 'Delete an organization from Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID to delete as a numeric string (e.g., "12345")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, `/organizations/${params.organizationId}`),
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'delete_organization')
|
||||
}
|
||||
|
||||
// DELETE returns 204 No Content with empty body
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: true,
|
||||
organization_id: params?.organizationId || '',
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether the organization was successfully deleted' },
|
||||
organization_id: { type: 'string', description: 'The deleted organization ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError } from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskDeleteTicketParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
ticketId: string
|
||||
}
|
||||
|
||||
export interface ZendeskDeleteTicketResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
deleted: boolean
|
||||
ticket_id: string
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskDeleteTicketTool: ToolConfig<
|
||||
ZendeskDeleteTicketParams,
|
||||
ZendeskDeleteTicketResponse
|
||||
> = {
|
||||
id: 'zendesk_delete_ticket',
|
||||
name: 'Delete Ticket from Zendesk',
|
||||
description: 'Delete a ticket from Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
ticketId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Ticket ID to delete as a numeric string (e.g., "12345")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, `/tickets/${params.ticketId}`),
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
// Use Basic Authentication with email/token format for Zendesk API tokens
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'delete_ticket')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: true,
|
||||
ticket_id: params?.ticketId || '',
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Deletion success' },
|
||||
ticket_id: { type: 'string', description: 'The deleted ticket ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError } from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskDeleteUserParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface ZendeskDeleteUserResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
deleted: boolean
|
||||
user_id: string
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskDeleteUserTool: ToolConfig<ZendeskDeleteUserParams, ZendeskDeleteUserResponse> =
|
||||
{
|
||||
id: 'zendesk_delete_user',
|
||||
name: 'Delete User from Zendesk',
|
||||
description: 'Delete a user from Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID to delete as a numeric string (e.g., "12345")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, `/users/${params.userId}`),
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'delete_user')
|
||||
}
|
||||
|
||||
// DELETE returns 204 No Content with empty body
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: true,
|
||||
user_id: params?.userId || '',
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Deletion success' },
|
||||
user_id: { type: 'string', description: 'The deleted user ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError, USER_OUTPUT_PROPERTIES } from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskGetCurrentUserParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
}
|
||||
|
||||
export interface ZendeskGetCurrentUserResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
user: any
|
||||
user_id: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskGetCurrentUserTool: ToolConfig<
|
||||
ZendeskGetCurrentUserParams,
|
||||
ZendeskGetCurrentUserResponse
|
||||
> = {
|
||||
id: 'zendesk_get_current_user',
|
||||
name: 'Get Current User from Zendesk',
|
||||
description: 'Get the currently authenticated user from Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, '/users/me'),
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'get_current_user')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
user: data.user,
|
||||
user_id: data.user?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
user: {
|
||||
type: 'object',
|
||||
description: 'Current user object',
|
||||
properties: USER_OUTPUT_PROPERTIES,
|
||||
},
|
||||
user_id: { type: 'number', description: 'The current user ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import {
|
||||
buildZendeskUrl,
|
||||
handleZendeskError,
|
||||
ORGANIZATION_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskGetOrganizationParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
organizationId: string
|
||||
}
|
||||
|
||||
export interface ZendeskGetOrganizationResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
organization: any
|
||||
organization_id: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskGetOrganizationTool: ToolConfig<
|
||||
ZendeskGetOrganizationParams,
|
||||
ZendeskGetOrganizationResponse
|
||||
> = {
|
||||
id: 'zendesk_get_organization',
|
||||
name: 'Get Single Organization from Zendesk',
|
||||
description: 'Get a single organization by ID from Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID to retrieve as a numeric string (e.g., "12345")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, `/organizations/${params.organizationId}`),
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'get_organization')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
organization: data.organization,
|
||||
organization_id: data.organization?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
organization: {
|
||||
type: 'object',
|
||||
description: 'Organization object',
|
||||
properties: ORGANIZATION_OUTPUT_PROPERTIES,
|
||||
},
|
||||
organization_id: { type: 'number', description: 'The organization ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import {
|
||||
appendCursorPaginationParams,
|
||||
buildZendeskUrl,
|
||||
extractCursorPagingInfo,
|
||||
handleZendeskError,
|
||||
METADATA_OUTPUT,
|
||||
ORGANIZATIONS_ARRAY_OUTPUT,
|
||||
PAGING_OUTPUT,
|
||||
} from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskGetOrganizationsParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
perPage?: string
|
||||
pageAfter?: string
|
||||
}
|
||||
|
||||
export interface ZendeskGetOrganizationsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
organizations: any[]
|
||||
paging?: {
|
||||
after_cursor: string | null
|
||||
has_more: boolean
|
||||
}
|
||||
metadata: {
|
||||
total_returned: number
|
||||
has_more: boolean
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskGetOrganizationsTool: ToolConfig<
|
||||
ZendeskGetOrganizationsParams,
|
||||
ZendeskGetOrganizationsResponse
|
||||
> = {
|
||||
id: 'zendesk_get_organizations',
|
||||
name: 'Get Organizations from Zendesk',
|
||||
description: 'Retrieve a list of organizations from Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain (e.g., "mycompany" for mycompany.zendesk.com)',
|
||||
},
|
||||
perPage: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page as a number string (default: "100", max: "100")',
|
||||
},
|
||||
pageAfter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cursor from a previous response to fetch the next page of results',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
appendCursorPaginationParams(queryParams, params)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildZendeskUrl(params.subdomain, '/organizations')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'get_organizations')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const organizations = data.organizations || []
|
||||
const paging = extractCursorPagingInfo(data)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
organizations,
|
||||
paging,
|
||||
metadata: {
|
||||
total_returned: organizations.length,
|
||||
has_more: paging.has_more,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
organizations: ORGANIZATIONS_ARRAY_OUTPUT,
|
||||
paging: PAGING_OUTPUT,
|
||||
metadata: METADATA_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import {
|
||||
buildZendeskUrl,
|
||||
handleZendeskError,
|
||||
TICKET_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskGetTicketParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
ticketId: string
|
||||
}
|
||||
|
||||
export interface ZendeskGetTicketResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
ticket: any
|
||||
ticket_id: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskGetTicketTool: ToolConfig<ZendeskGetTicketParams, ZendeskGetTicketResponse> = {
|
||||
id: 'zendesk_get_ticket',
|
||||
name: 'Get Single Ticket from Zendesk',
|
||||
description: 'Get a single ticket by ID from Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
ticketId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Ticket ID to retrieve as a numeric string (e.g., "12345")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, `/tickets/${params.ticketId}`),
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
// Use Basic Authentication with email/token format for Zendesk API tokens
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'get_ticket')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
ticket: data.ticket,
|
||||
ticket_id: data.ticket?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ticket: {
|
||||
type: 'object',
|
||||
description: 'Ticket object',
|
||||
properties: TICKET_OUTPUT_PROPERTIES,
|
||||
},
|
||||
ticket_id: { type: 'number', description: 'The ticket ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import {
|
||||
appendCursorPaginationParams,
|
||||
buildZendeskUrl,
|
||||
extractCursorPagingInfo,
|
||||
handleZendeskError,
|
||||
METADATA_OUTPUT,
|
||||
PAGING_OUTPUT,
|
||||
TICKETS_ARRAY_OUTPUT,
|
||||
} from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskGetTicketsParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
status?: string
|
||||
priority?: string
|
||||
type?: string
|
||||
assigneeId?: string
|
||||
organizationId?: string
|
||||
sort?: string
|
||||
perPage?: string
|
||||
pageAfter?: string
|
||||
}
|
||||
|
||||
export interface ZendeskGetTicketsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
tickets: any[]
|
||||
paging?: {
|
||||
after_cursor: string | null
|
||||
has_more: boolean
|
||||
}
|
||||
metadata: {
|
||||
total_returned: number
|
||||
has_more: boolean
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskGetTicketsTool: ToolConfig<ZendeskGetTicketsParams, ZendeskGetTicketsResponse> =
|
||||
{
|
||||
id: 'zendesk_get_tickets',
|
||||
name: 'Get Tickets from Zendesk',
|
||||
description: 'Retrieve a list of tickets from Zendesk with optional filtering',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain (e.g., "mycompany" for mycompany.zendesk.com)',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by status: "new", "open", "pending", "hold", "solved", or "closed"',
|
||||
},
|
||||
priority: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by priority: "low", "normal", "high", or "urgent"',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by type: "problem", "incident", "question", or "task"',
|
||||
},
|
||||
assigneeId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by assignee user ID as a numeric string (e.g., "12345")',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by organization ID as a numeric string (e.g., "67890")',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Sort field for ticket listing (only applies without filters): "updated_at", "id", or "status". Prefix with "-" for descending (e.g., "-updated_at")',
|
||||
},
|
||||
perPage: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page as a number string (default: "100", max: "100")',
|
||||
},
|
||||
pageAfter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cursor from a previous response to fetch the next page of results',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const hasFilters =
|
||||
params.status ||
|
||||
params.priority ||
|
||||
params.type ||
|
||||
params.assigneeId ||
|
||||
params.organizationId
|
||||
|
||||
if (hasFilters) {
|
||||
// Use Search API for filtering - the /tickets endpoint doesn't support filter params
|
||||
// Build search query using Zendesk search syntax
|
||||
const searchTerms: string[] = ['type:ticket']
|
||||
if (params.status) searchTerms.push(`status:${params.status}`)
|
||||
if (params.priority) searchTerms.push(`priority:${params.priority}`)
|
||||
if (params.type) searchTerms.push(`ticket_type:${params.type}`)
|
||||
if (params.assigneeId) searchTerms.push(`assignee_id:${params.assigneeId}`)
|
||||
if (params.organizationId) searchTerms.push(`organization_id:${params.organizationId}`)
|
||||
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('query', searchTerms.join(' '))
|
||||
queryParams.append('filter[type]', 'ticket')
|
||||
appendCursorPaginationParams(queryParams, params)
|
||||
|
||||
return `${buildZendeskUrl(params.subdomain, '/search/export')}?${queryParams.toString()}`
|
||||
}
|
||||
|
||||
// No filters - use the simple /tickets endpoint with cursor-based pagination
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.sort) queryParams.append('sort', params.sort)
|
||||
appendCursorPaginationParams(queryParams, params)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildZendeskUrl(params.subdomain, '/tickets')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
// Use Basic Authentication with email/token format for Zendesk API tokens
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'get_tickets')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
// Handle both /tickets response (data.tickets) and /search response (data.results)
|
||||
const tickets = data.tickets || data.results || []
|
||||
const paging = extractCursorPagingInfo(data)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
tickets,
|
||||
paging,
|
||||
metadata: {
|
||||
total_returned: tickets.length,
|
||||
has_more: paging.has_more,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tickets: TICKETS_ARRAY_OUTPUT,
|
||||
paging: PAGING_OUTPUT,
|
||||
metadata: METADATA_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError, USER_OUTPUT_PROPERTIES } from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskGetUserParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface ZendeskGetUserResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
user: any
|
||||
user_id: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskGetUserTool: ToolConfig<ZendeskGetUserParams, ZendeskGetUserResponse> = {
|
||||
id: 'zendesk_get_user',
|
||||
name: 'Get Single User from Zendesk',
|
||||
description: 'Get a single user by ID from Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID to retrieve as a numeric string (e.g., "12345")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, `/users/${params.userId}`),
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'get_user')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
user: data.user,
|
||||
user_id: data.user?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
user: {
|
||||
type: 'object',
|
||||
description: 'User object',
|
||||
properties: USER_OUTPUT_PROPERTIES,
|
||||
},
|
||||
user_id: { type: 'number', description: 'The user ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import {
|
||||
appendCursorPaginationParams,
|
||||
buildZendeskUrl,
|
||||
extractCursorPagingInfo,
|
||||
handleZendeskError,
|
||||
METADATA_OUTPUT,
|
||||
PAGING_OUTPUT,
|
||||
USERS_ARRAY_OUTPUT,
|
||||
} from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskGetUsersParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
role?: string
|
||||
permissionSet?: string
|
||||
perPage?: string
|
||||
pageAfter?: string
|
||||
}
|
||||
|
||||
export interface ZendeskGetUsersResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
users: any[]
|
||||
paging?: {
|
||||
after_cursor: string | null
|
||||
has_more: boolean
|
||||
}
|
||||
metadata: {
|
||||
total_returned: number
|
||||
has_more: boolean
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskGetUsersTool: ToolConfig<ZendeskGetUsersParams, ZendeskGetUsersResponse> = {
|
||||
id: 'zendesk_get_users',
|
||||
name: 'Get Users from Zendesk',
|
||||
description: 'Retrieve a list of users from Zendesk with optional filtering',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain (e.g., "mycompany" for mycompany.zendesk.com)',
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by role: "end-user", "agent", or "admin"',
|
||||
},
|
||||
permissionSet: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by permission set ID as a numeric string (e.g., "12345")',
|
||||
},
|
||||
perPage: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page as a number string (default: "100", max: "100")',
|
||||
},
|
||||
pageAfter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cursor from a previous response to fetch the next page of results',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.role) queryParams.append('role', params.role)
|
||||
if (params.permissionSet) queryParams.append('permission_set', params.permissionSet)
|
||||
appendCursorPaginationParams(queryParams, params)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildZendeskUrl(params.subdomain, '/users')
|
||||
return query ? `${url}?${query}` : url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'get_users')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const users = data.users || []
|
||||
const paging = extractCursorPagingInfo(data)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
users,
|
||||
paging,
|
||||
metadata: {
|
||||
total_returned: users.length,
|
||||
has_more: paging.has_more,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
users: USERS_ARRAY_OUTPUT,
|
||||
paging: PAGING_OUTPUT,
|
||||
metadata: METADATA_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
export { zendeskAutocompleteOrganizationsTool } from './autocomplete_organizations'
|
||||
export { zendeskCreateOrganizationTool } from './create_organization'
|
||||
export { zendeskCreateOrganizationsBulkTool } from './create_organizations_bulk'
|
||||
export { zendeskCreateTicketTool } from './create_ticket'
|
||||
export { zendeskCreateTicketsBulkTool } from './create_tickets_bulk'
|
||||
export { zendeskCreateUserTool } from './create_user'
|
||||
export { zendeskCreateUsersBulkTool } from './create_users_bulk'
|
||||
export { zendeskDeleteOrganizationTool } from './delete_organization'
|
||||
export { zendeskDeleteTicketTool } from './delete_ticket'
|
||||
export { zendeskDeleteUserTool } from './delete_user'
|
||||
export { zendeskGetCurrentUserTool } from './get_current_user'
|
||||
export { zendeskGetOrganizationTool } from './get_organization'
|
||||
export { zendeskGetOrganizationsTool } from './get_organizations'
|
||||
export { zendeskGetTicketTool } from './get_ticket'
|
||||
export { zendeskGetTicketsTool } from './get_tickets'
|
||||
export { zendeskGetUserTool } from './get_user'
|
||||
export { zendeskGetUsersTool } from './get_users'
|
||||
export { zendeskMergeTicketsTool } from './merge_tickets'
|
||||
export { zendeskSearchTool } from './search'
|
||||
export { zendeskSearchCountTool } from './search_count'
|
||||
export { zendeskSearchUsersTool } from './search_users'
|
||||
export { zendeskUpdateOrganizationTool } from './update_organization'
|
||||
export { zendeskUpdateTicketTool } from './update_ticket'
|
||||
export { zendeskUpdateTicketsBulkTool } from './update_tickets_bulk'
|
||||
export { zendeskUpdateUserTool } from './update_user'
|
||||
export { zendeskUpdateUsersBulkTool } from './update_users_bulk'
|
||||
@@ -0,0 +1,123 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError, JOB_STATUS_OUTPUT } from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskMergeTicketsParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
targetTicketId: string
|
||||
sourceTicketIds: string
|
||||
targetComment?: string
|
||||
}
|
||||
|
||||
export interface ZendeskMergeTicketsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
job_status: any
|
||||
job_id?: string
|
||||
target_ticket_id: string
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskMergeTicketsTool: ToolConfig<
|
||||
ZendeskMergeTicketsParams,
|
||||
ZendeskMergeTicketsResponse
|
||||
> = {
|
||||
id: 'zendesk_merge_tickets',
|
||||
name: 'Merge Tickets in Zendesk',
|
||||
description: 'Merge multiple tickets into a target ticket',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
targetTicketId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Target ticket ID as a numeric string (tickets will be merged into this one, e.g., "12345")',
|
||||
},
|
||||
sourceTicketIds: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated source ticket IDs to merge (e.g., "111, 222, 333")',
|
||||
},
|
||||
targetComment: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comment text to add to target ticket after merge',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, `/tickets/${params.targetTicketId}/merge`),
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const ids = params.sourceTicketIds.split(',').map((id) => id.trim())
|
||||
const body: any = { ids }
|
||||
if (params.targetComment) {
|
||||
body.target_comment = {
|
||||
body: params.targetComment,
|
||||
public: true,
|
||||
}
|
||||
}
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'merge_tickets')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
job_status: data.job_status,
|
||||
job_id: data.job_status?.id,
|
||||
target_ticket_id: params?.targetTicketId || '',
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
job_status: JOB_STATUS_OUTPUT,
|
||||
job_id: { type: 'string', description: 'The merge job ID' },
|
||||
target_ticket_id: {
|
||||
type: 'string',
|
||||
description: 'The target ticket ID that tickets were merged into',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import {
|
||||
appendCursorPaginationParams,
|
||||
buildZendeskUrl,
|
||||
extractCursorPagingInfo,
|
||||
handleZendeskError,
|
||||
METADATA_OUTPUT,
|
||||
PAGING_OUTPUT,
|
||||
} from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskSearchParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
query: string
|
||||
filterType: string
|
||||
perPage?: string
|
||||
pageAfter?: string
|
||||
}
|
||||
|
||||
export interface ZendeskSearchResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
results: any[]
|
||||
paging?: {
|
||||
after_cursor: string | null
|
||||
has_more: boolean
|
||||
}
|
||||
metadata: {
|
||||
total_returned: number
|
||||
has_more: boolean
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskSearchTool: ToolConfig<ZendeskSearchParams, ZendeskSearchResponse> = {
|
||||
id: 'zendesk_search',
|
||||
name: 'Search Zendesk',
|
||||
description: 'Unified search across tickets, users, and organizations in Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Search query string using Zendesk search syntax (e.g., "type:ticket status:open")',
|
||||
},
|
||||
filterType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Resource type to search for: "ticket", "user", "organization", or "group"',
|
||||
},
|
||||
perPage: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page as a number string (default: "100", max: "100")',
|
||||
},
|
||||
pageAfter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cursor from a previous response to fetch the next page of results',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('query', params.query)
|
||||
queryParams.append('filter[type]', params.filterType)
|
||||
appendCursorPaginationParams(queryParams, params)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildZendeskUrl(params.subdomain, '/search/export')
|
||||
return `${url}?${query}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'search')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const results = data.results || []
|
||||
const paging = extractCursorPagingInfo(data)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
results,
|
||||
paging,
|
||||
metadata: {
|
||||
total_returned: results.length,
|
||||
has_more: paging.has_more,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
results: {
|
||||
type: 'array',
|
||||
description:
|
||||
'Array of result objects (tickets, users, or organizations depending on search query)',
|
||||
},
|
||||
paging: PAGING_OUTPUT,
|
||||
metadata: METADATA_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError } from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskSearchCountParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
query: string
|
||||
}
|
||||
|
||||
export interface ZendeskSearchCountResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
count: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskSearchCountTool: ToolConfig<
|
||||
ZendeskSearchCountParams,
|
||||
ZendeskSearchCountResponse
|
||||
> = {
|
||||
id: 'zendesk_search_count',
|
||||
name: 'Count Search Results in Zendesk',
|
||||
description: 'Count the number of search results matching a query in Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Search query string using Zendesk search syntax (e.g., "type:ticket status:open")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
queryParams.append('query', params.query)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildZendeskUrl(params.subdomain, '/search/count')
|
||||
return `${url}?${query}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'search_count')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
count: data.count || 0,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
count: { type: 'number', description: 'Number of matching results' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import {
|
||||
buildZendeskUrl,
|
||||
handleZendeskError,
|
||||
METADATA_OUTPUT,
|
||||
PAGING_OUTPUT,
|
||||
USERS_ARRAY_OUTPUT,
|
||||
} from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskSearchUsersParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
query?: string
|
||||
externalId?: string
|
||||
perPage?: string
|
||||
page?: string
|
||||
}
|
||||
|
||||
export interface ZendeskSearchUsersResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
users: any[]
|
||||
paging?: {
|
||||
after_cursor: string | null
|
||||
has_more: boolean
|
||||
next_page?: string | null
|
||||
}
|
||||
metadata: {
|
||||
total_returned: number
|
||||
has_more: boolean
|
||||
}
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskSearchUsersTool: ToolConfig<
|
||||
ZendeskSearchUsersParams,
|
||||
ZendeskSearchUsersResponse
|
||||
> = {
|
||||
id: 'zendesk_search_users',
|
||||
name: 'Search Users in Zendesk',
|
||||
description: 'Search for users in Zendesk using a query string',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search query string (e.g., user name or email)',
|
||||
},
|
||||
externalId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'External ID to search by (your system identifier)',
|
||||
},
|
||||
perPage: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page as a number string (default: "100", max: "100")',
|
||||
},
|
||||
page: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number for pagination (1-based)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.query) queryParams.append('query', params.query)
|
||||
if (params.externalId) queryParams.append('external_id', params.externalId)
|
||||
if (params.perPage) queryParams.append('per_page', params.perPage)
|
||||
if (params.page) queryParams.append('page', params.page)
|
||||
|
||||
const query = queryParams.toString()
|
||||
const url = buildZendeskUrl(params.subdomain, '/users/search')
|
||||
return `${url}?${query}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'search_users')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const users = data.users || []
|
||||
const hasMore = data.next_page !== null && data.next_page !== undefined
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
users,
|
||||
// /users/search uses offset pagination (page/per_page), not cursor pagination.
|
||||
// after_cursor is always null; use next_page URL or page param for subsequent pages.
|
||||
paging: {
|
||||
after_cursor: null,
|
||||
has_more: hasMore,
|
||||
next_page: data.next_page ?? null,
|
||||
},
|
||||
metadata: {
|
||||
total_returned: users.length,
|
||||
has_more: hasMore,
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
users: USERS_ARRAY_OUTPUT,
|
||||
paging: PAGING_OUTPUT,
|
||||
metadata: METADATA_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { OutputProperty } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('Zendesk')
|
||||
|
||||
// Base params - following Sentry pattern where subdomain is user-provided
|
||||
interface ZendeskBaseParams {
|
||||
email: string // Zendesk user email (required for API token authentication)
|
||||
apiToken: string // API token (hidden)
|
||||
subdomain: string // Zendesk subdomain (user-visible, required - e.g., "mycompany" for mycompany.zendesk.com)
|
||||
}
|
||||
|
||||
export interface ZendeskPaginationParams {
|
||||
perPage?: string
|
||||
pageAfter?: string
|
||||
}
|
||||
|
||||
export interface ZendeskPagingInfo {
|
||||
after_cursor: string | null
|
||||
has_more: boolean
|
||||
next_page?: string | null
|
||||
}
|
||||
|
||||
interface ZendeskListMetadata {
|
||||
total_returned: number
|
||||
has_more: boolean
|
||||
}
|
||||
|
||||
export interface ZendeskResponse<T> {
|
||||
success: boolean
|
||||
output: {
|
||||
data?: T
|
||||
paging?: ZendeskPagingInfo
|
||||
metadata?: ZendeskListMetadata
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to build Zendesk API URLs
|
||||
// Subdomain is always provided by user as a parameter
|
||||
export function buildZendeskUrl(subdomain: string, path: string): string {
|
||||
return `https://${subdomain}.zendesk.com/api/v2${path}`
|
||||
}
|
||||
|
||||
// Helper function for consistent error handling
|
||||
export function handleZendeskError(data: any, status: number, operation: string): never {
|
||||
logger.error(`Zendesk API request failed for ${operation}`, { data, status })
|
||||
|
||||
const errorMessage = data.error || data.description || data.message || 'Unknown error'
|
||||
throw new Error(`Zendesk ${operation} failed: ${errorMessage}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends cursor-based pagination query params.
|
||||
* Zendesk uses bracket notation: `page[size]` and `page[after]`.
|
||||
*/
|
||||
export function appendCursorPaginationParams(
|
||||
queryParams: URLSearchParams,
|
||||
params: ZendeskPaginationParams
|
||||
): void {
|
||||
if (params.perPage) queryParams.append('page[size]', params.perPage)
|
||||
if (params.pageAfter) queryParams.append('page[after]', params.pageAfter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts cursor-based pagination info from Zendesk API response.
|
||||
* Zendesk cursor-based responses include `meta.after_cursor`, `meta.has_more`, and `links.next`.
|
||||
*/
|
||||
export function extractCursorPagingInfo(data: Record<string, unknown>): ZendeskPagingInfo {
|
||||
const meta = (data.meta as Record<string, unknown>) || {}
|
||||
const links = (data.links as Record<string, unknown>) || {}
|
||||
return {
|
||||
after_cursor: (meta.after_cursor as string) ?? null,
|
||||
has_more: Boolean(meta.has_more),
|
||||
next_page: (links.next as string) ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output definition for the "via" object in ticket responses.
|
||||
* Contains information about how the ticket was created.
|
||||
*/
|
||||
export const VIA_OUTPUT_PROPERTIES = {
|
||||
channel: {
|
||||
type: 'string',
|
||||
description: 'Channel through which the ticket was created (e.g., email, web, api)',
|
||||
},
|
||||
source: {
|
||||
type: 'object',
|
||||
description: 'Source details for the channel',
|
||||
properties: {
|
||||
from: {
|
||||
type: 'object',
|
||||
description: 'Information about the source sender',
|
||||
optional: true,
|
||||
properties: {
|
||||
address: {
|
||||
type: 'string',
|
||||
description: 'Email address or other identifier',
|
||||
optional: true,
|
||||
},
|
||||
name: { type: 'string', description: 'Name of the sender', optional: true },
|
||||
},
|
||||
},
|
||||
to: {
|
||||
type: 'object',
|
||||
description: 'Information about the recipient',
|
||||
optional: true,
|
||||
properties: {
|
||||
address: {
|
||||
type: 'string',
|
||||
description: 'Email address or other identifier',
|
||||
optional: true,
|
||||
},
|
||||
name: { type: 'string', description: 'Name of the recipient', optional: true },
|
||||
},
|
||||
},
|
||||
rel: { type: 'string', description: 'Relationship type', optional: true },
|
||||
},
|
||||
},
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Output definition for custom field entries in ticket responses
|
||||
*/
|
||||
export const CUSTOM_FIELD_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'number', description: 'Custom field ID' },
|
||||
value: { type: 'string', description: 'Custom field value' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Output definition for satisfaction rating in ticket responses
|
||||
*/
|
||||
export const SATISFACTION_RATING_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'number', description: 'Satisfaction rating ID', optional: true },
|
||||
score: { type: 'string', description: 'Rating score (e.g., good, bad, offered, unoffered)' },
|
||||
comment: { type: 'string', description: 'Comment left with the rating', optional: true },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete ticket object output properties based on Zendesk API documentation.
|
||||
* @see https://developer.zendesk.com/api-reference/ticketing/tickets/tickets/
|
||||
*/
|
||||
export const TICKET_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'number', description: 'Automatically assigned ticket ID' },
|
||||
url: { type: 'string', description: 'API URL of the ticket' },
|
||||
external_id: {
|
||||
type: 'string',
|
||||
description: 'External ID for linking to external records',
|
||||
optional: true,
|
||||
},
|
||||
via: {
|
||||
type: 'object',
|
||||
description: 'How the ticket was created',
|
||||
properties: VIA_OUTPUT_PROPERTIES,
|
||||
},
|
||||
created_at: { type: 'string', description: 'When the ticket was created (ISO 8601 format)' },
|
||||
updated_at: { type: 'string', description: 'When the ticket was last updated (ISO 8601 format)' },
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'Ticket type (problem, incident, question, task)',
|
||||
optional: true,
|
||||
},
|
||||
subject: { type: 'string', description: 'Subject of the ticket' },
|
||||
raw_subject: { type: 'string', description: 'Subject of the ticket as entered by the requester' },
|
||||
description: { type: 'string', description: 'Read-only first comment on the ticket' },
|
||||
priority: {
|
||||
type: 'string',
|
||||
description: 'Priority level (low, normal, high, urgent)',
|
||||
optional: true,
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Ticket status (new, open, pending, hold, solved, closed)',
|
||||
},
|
||||
recipient: { type: 'string', description: 'Original recipient email address', optional: true },
|
||||
requester_id: { type: 'number', description: 'User ID of the ticket requester' },
|
||||
submitter_id: { type: 'number', description: 'User ID of the ticket submitter' },
|
||||
assignee_id: {
|
||||
type: 'number',
|
||||
description: 'User ID of the agent assigned to the ticket',
|
||||
optional: true,
|
||||
},
|
||||
organization_id: {
|
||||
type: 'number',
|
||||
description: 'Organization ID of the requester',
|
||||
optional: true,
|
||||
},
|
||||
group_id: { type: 'number', description: 'Group ID assigned to the ticket', optional: true },
|
||||
collaborator_ids: {
|
||||
type: 'array',
|
||||
description: 'User IDs of collaborators (CC)',
|
||||
items: { type: 'number', description: 'Collaborator user ID' },
|
||||
},
|
||||
follower_ids: {
|
||||
type: 'array',
|
||||
description: 'User IDs of followers',
|
||||
items: { type: 'number', description: 'Follower user ID' },
|
||||
},
|
||||
email_cc_ids: {
|
||||
type: 'array',
|
||||
description: 'User IDs of email CCs',
|
||||
items: { type: 'number', description: 'Email CC user ID' },
|
||||
},
|
||||
forum_topic_id: {
|
||||
type: 'number',
|
||||
description: 'Topic ID in the community forum',
|
||||
optional: true,
|
||||
},
|
||||
problem_id: {
|
||||
type: 'number',
|
||||
description: 'For incident tickets, the ID of the associated problem ticket',
|
||||
optional: true,
|
||||
},
|
||||
has_incidents: { type: 'boolean', description: 'Whether the ticket has incident tickets linked' },
|
||||
is_public: { type: 'boolean', description: 'Whether the first comment is public' },
|
||||
due_at: {
|
||||
type: 'string',
|
||||
description: 'Due date for task tickets (ISO 8601 format)',
|
||||
optional: true,
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'Tags associated with the ticket',
|
||||
items: { type: 'string', description: 'Tag name' },
|
||||
},
|
||||
custom_fields: {
|
||||
type: 'array',
|
||||
description: 'Custom ticket fields',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: CUSTOM_FIELD_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
custom_status_id: { type: 'number', description: 'Custom status ID', optional: true },
|
||||
satisfaction_rating: {
|
||||
type: 'object',
|
||||
description: 'Customer satisfaction rating',
|
||||
optional: true,
|
||||
properties: SATISFACTION_RATING_OUTPUT_PROPERTIES,
|
||||
},
|
||||
sharing_agreement_ids: {
|
||||
type: 'array',
|
||||
description: 'Sharing agreement IDs',
|
||||
items: { type: 'number', description: 'Sharing agreement ID' },
|
||||
},
|
||||
followup_ids: {
|
||||
type: 'array',
|
||||
description: 'IDs of follow-up tickets',
|
||||
items: { type: 'number', description: 'Follow-up ticket ID' },
|
||||
},
|
||||
brand_id: { type: 'number', description: 'Brand ID the ticket belongs to' },
|
||||
allow_attachments: { type: 'boolean', description: 'Whether attachments are allowed' },
|
||||
allow_channelback: { type: 'boolean', description: 'Whether channelback is enabled' },
|
||||
from_messaging_channel: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the ticket originated from a messaging channel',
|
||||
},
|
||||
ticket_form_id: { type: 'number', description: 'Ticket form ID', optional: true },
|
||||
generated_timestamp: { type: 'number', description: 'Unix timestamp of the ticket generation' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Output definition for user photo object
|
||||
*/
|
||||
export const USER_PHOTO_OUTPUT_PROPERTIES = {
|
||||
content_url: { type: 'string', description: 'URL to the photo' },
|
||||
file_name: { type: 'string', description: 'Photo file name' },
|
||||
size: { type: 'number', description: 'File size in bytes' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete user object output properties based on Zendesk API documentation.
|
||||
* @see https://developer.zendesk.com/api-reference/ticketing/users/users/
|
||||
*/
|
||||
export const USER_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'number', description: 'Automatically assigned user ID' },
|
||||
url: { type: 'string', description: 'API URL of the user' },
|
||||
name: { type: 'string', description: 'User name' },
|
||||
email: { type: 'string', description: 'Primary email address' },
|
||||
created_at: { type: 'string', description: 'When the user was created (ISO 8601 format)' },
|
||||
updated_at: { type: 'string', description: 'When the user was last updated (ISO 8601 format)' },
|
||||
time_zone: { type: 'string', description: 'Time zone (e.g., Eastern Time (US & Canada))' },
|
||||
iana_time_zone: { type: 'string', description: 'IANA time zone (e.g., America/New_York)' },
|
||||
phone: { type: 'string', description: 'Phone number', optional: true },
|
||||
shared_phone_number: { type: 'boolean', description: 'Whether the phone number is shared' },
|
||||
photo: {
|
||||
type: 'object',
|
||||
description: 'User photo details',
|
||||
optional: true,
|
||||
properties: USER_PHOTO_OUTPUT_PROPERTIES,
|
||||
},
|
||||
locale: { type: 'string', description: 'Locale (e.g., en-US)' },
|
||||
locale_id: { type: 'number', description: 'Locale ID' },
|
||||
organization_id: { type: 'number', description: 'Primary organization ID', optional: true },
|
||||
role: { type: 'string', description: 'User role (end-user, agent, admin)' },
|
||||
role_type: { type: 'number', description: 'Role type identifier', optional: true },
|
||||
custom_role_id: { type: 'number', description: 'Custom role ID', optional: true },
|
||||
active: { type: 'boolean', description: 'Whether the user is active (false if deleted)' },
|
||||
verified: { type: 'boolean', description: 'Whether any user identity has been verified' },
|
||||
alias: { type: 'string', description: 'Alias displayed to end users', optional: true },
|
||||
details: { type: 'string', description: 'Details about the user', optional: true },
|
||||
notes: { type: 'string', description: 'Notes about the user', optional: true },
|
||||
signature: { type: 'string', description: 'User signature for email replies', optional: true },
|
||||
default_group_id: {
|
||||
type: 'number',
|
||||
description: 'Default group ID for the user',
|
||||
optional: true,
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'Tags associated with the user',
|
||||
items: { type: 'string', description: 'Tag name' },
|
||||
},
|
||||
external_id: {
|
||||
type: 'string',
|
||||
description: 'External ID for linking to external records',
|
||||
optional: true,
|
||||
},
|
||||
restricted_agent: { type: 'boolean', description: 'Whether the agent has restrictions' },
|
||||
suspended: { type: 'boolean', description: 'Whether the user is suspended' },
|
||||
moderator: { type: 'boolean', description: 'Whether the user has moderator permissions' },
|
||||
chat_only: { type: 'boolean', description: 'Whether the user is a chat-only agent' },
|
||||
only_private_comments: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the user can only create private comments',
|
||||
},
|
||||
two_factor_auth_enabled: { type: 'boolean', description: 'Whether two-factor auth is enabled' },
|
||||
last_login_at: {
|
||||
type: 'string',
|
||||
description: 'Last login time (ISO 8601 format)',
|
||||
optional: true,
|
||||
},
|
||||
ticket_restriction: {
|
||||
type: 'string',
|
||||
description: 'Ticket access restriction (organization, groups, assigned, requested)',
|
||||
optional: true,
|
||||
},
|
||||
user_fields: {
|
||||
type: 'json',
|
||||
description: 'Custom user fields (dynamic key-value pairs)',
|
||||
optional: true,
|
||||
},
|
||||
shared: { type: 'boolean', description: 'Whether the user is shared from a different Zendesk' },
|
||||
shared_agent: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the agent is shared from a different Zendesk',
|
||||
},
|
||||
remote_photo_url: { type: 'string', description: 'URL to a remote photo', optional: true },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete organization object output properties based on Zendesk API documentation.
|
||||
* @see https://developer.zendesk.com/api-reference/ticketing/organizations/organizations/
|
||||
*/
|
||||
export const ORGANIZATION_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'number', description: 'Automatically assigned organization ID' },
|
||||
url: { type: 'string', description: 'API URL of the organization' },
|
||||
name: { type: 'string', description: 'Unique organization name' },
|
||||
domain_names: {
|
||||
type: 'array',
|
||||
description: 'Domain names for automatic user assignment',
|
||||
items: { type: 'string', description: 'Domain name' },
|
||||
},
|
||||
details: { type: 'string', description: 'Details about the organization', optional: true },
|
||||
notes: { type: 'string', description: 'Notes about the organization', optional: true },
|
||||
group_id: {
|
||||
type: 'number',
|
||||
description: 'Group ID for auto-routing new tickets',
|
||||
optional: true,
|
||||
},
|
||||
shared_tickets: { type: 'boolean', description: 'Whether end users can see each others tickets' },
|
||||
shared_comments: {
|
||||
type: 'boolean',
|
||||
description: 'Whether end users can see each others comments',
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'Tags associated with the organization',
|
||||
items: { type: 'string', description: 'Tag name' },
|
||||
},
|
||||
organization_fields: {
|
||||
type: 'json',
|
||||
description: 'Custom organization fields (dynamic key-value pairs)',
|
||||
optional: true,
|
||||
},
|
||||
created_at: {
|
||||
type: 'string',
|
||||
description: 'When the organization was created (ISO 8601 format)',
|
||||
},
|
||||
updated_at: {
|
||||
type: 'string',
|
||||
description: 'When the organization was last updated (ISO 8601 format)',
|
||||
},
|
||||
external_id: {
|
||||
type: 'string',
|
||||
description: 'External ID for linking to external records',
|
||||
optional: true,
|
||||
},
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Pagination output properties for list endpoints
|
||||
*/
|
||||
export const PAGING_OUTPUT_PROPERTIES = {
|
||||
after_cursor: {
|
||||
type: 'string',
|
||||
description: 'Cursor for fetching the next page of results',
|
||||
optional: true,
|
||||
},
|
||||
has_more: { type: 'boolean', description: 'Whether more results are available' },
|
||||
next_page: { type: 'string', description: 'URL for next page of results', optional: true },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete paging output definition
|
||||
*/
|
||||
export const PAGING_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Cursor-based pagination information',
|
||||
properties: PAGING_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata output properties for list responses
|
||||
*/
|
||||
export const METADATA_OUTPUT_PROPERTIES = {
|
||||
total_returned: { type: 'number', description: 'Number of items returned in this response' },
|
||||
has_more: { type: 'boolean', description: 'Whether more items are available' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete metadata output definition
|
||||
*/
|
||||
export const METADATA_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Response metadata',
|
||||
properties: METADATA_OUTPUT_PROPERTIES,
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete tickets array output definition with nested properties
|
||||
*/
|
||||
export const TICKETS_ARRAY_OUTPUT: OutputProperty = {
|
||||
type: 'array',
|
||||
description: 'Array of ticket objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: TICKET_OUTPUT_PROPERTIES,
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete users array output definition with nested properties
|
||||
*/
|
||||
export const USERS_ARRAY_OUTPUT: OutputProperty = {
|
||||
type: 'array',
|
||||
description: 'Array of user objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: USER_OUTPUT_PROPERTIES,
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete organizations array output definition with nested properties
|
||||
*/
|
||||
export const ORGANIZATIONS_ARRAY_OUTPUT: OutputProperty = {
|
||||
type: 'array',
|
||||
description: 'Array of organization objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: ORGANIZATION_OUTPUT_PROPERTIES,
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Job status result item output properties for bulk operations.
|
||||
* @see https://developer.zendesk.com/api-reference/ticketing/ticket-management/job_statuses/
|
||||
*/
|
||||
export const JOB_RESULT_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'number', description: 'ID of the created or updated resource' },
|
||||
index: { type: 'number', description: 'Position of the result in the batch', optional: true },
|
||||
action: {
|
||||
type: 'string',
|
||||
description: 'Action performed (e.g., create, update)',
|
||||
optional: true,
|
||||
},
|
||||
success: { type: 'boolean', description: 'Whether the operation succeeded' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Status message (e.g., Updated, Created)',
|
||||
optional: true,
|
||||
},
|
||||
error: { type: 'string', description: 'Error message if operation failed', optional: true },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Job status output properties for bulk operation responses.
|
||||
* @see https://developer.zendesk.com/api-reference/ticketing/ticket-management/job_statuses/
|
||||
*/
|
||||
export const JOB_STATUS_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'string', description: 'Automatically assigned job ID' },
|
||||
url: { type: 'string', description: 'URL to poll for status updates' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Current job status (queued, working, failed, completed)',
|
||||
},
|
||||
job_type: { type: 'string', description: 'Category of background task' },
|
||||
total: { type: 'number', description: 'Total number of tasks in this job' },
|
||||
progress: { type: 'number', description: 'Number of tasks already completed' },
|
||||
message: { type: 'string', description: 'Message from the job worker', optional: true },
|
||||
results: {
|
||||
type: 'array',
|
||||
description: 'Array of result objects from the job',
|
||||
optional: true,
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: JOB_RESULT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Complete job status output definition
|
||||
*/
|
||||
export const JOB_STATUS_OUTPUT: OutputProperty = {
|
||||
type: 'object',
|
||||
description: 'Job status object for bulk operations',
|
||||
properties: JOB_STATUS_OUTPUT_PROPERTIES,
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import {
|
||||
buildZendeskUrl,
|
||||
handleZendeskError,
|
||||
ORGANIZATION_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/zendesk/types'
|
||||
|
||||
const logger = createLogger('ZendeskUpdateOrganization')
|
||||
|
||||
export interface ZendeskUpdateOrganizationParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
organizationId: string
|
||||
name?: string
|
||||
domainNames?: string
|
||||
details?: string
|
||||
notes?: string
|
||||
tags?: string
|
||||
customFields?: string
|
||||
}
|
||||
|
||||
export interface ZendeskUpdateOrganizationResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
organization: any
|
||||
organization_id: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskUpdateOrganizationTool: ToolConfig<
|
||||
ZendeskUpdateOrganizationParams,
|
||||
ZendeskUpdateOrganizationResponse
|
||||
> = {
|
||||
id: 'zendesk_update_organization',
|
||||
name: 'Update Organization in Zendesk',
|
||||
description: 'Update an existing organization in Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID to update as a numeric string (e.g., "12345")',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New organization name (e.g., "Acme Corporation")',
|
||||
},
|
||||
domainNames: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated domain names (e.g., "acme.com, acme.org")',
|
||||
},
|
||||
details: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization details text',
|
||||
},
|
||||
notes: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization notes text',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated tags (e.g., "enterprise, priority")',
|
||||
},
|
||||
customFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Custom fields as JSON object (e.g., {"field_id": "value"})',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, `/organizations/${params.organizationId}`),
|
||||
method: 'PUT',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const organization: any = {}
|
||||
|
||||
if (params.name) organization.name = params.name
|
||||
if (params.domainNames)
|
||||
organization.domain_names = params.domainNames.split(',').map((d) => d.trim())
|
||||
if (params.details) organization.details = params.details
|
||||
if (params.notes) organization.notes = params.notes
|
||||
if (params.tags) organization.tags = params.tags.split(',').map((t) => t.trim())
|
||||
|
||||
if (params.customFields) {
|
||||
try {
|
||||
const customFields = JSON.parse(params.customFields)
|
||||
organization.organization_fields = customFields
|
||||
} catch (error) {
|
||||
logger.warn('Failed to parse custom fields', { error })
|
||||
}
|
||||
}
|
||||
|
||||
return { organization }
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'update_organization')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
organization: data.organization,
|
||||
organization_id: data.organization?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
organization: {
|
||||
type: 'object',
|
||||
description: 'Updated organization object',
|
||||
properties: ORGANIZATION_OUTPUT_PROPERTIES,
|
||||
},
|
||||
organization_id: { type: 'number', description: 'The updated organization ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import {
|
||||
buildZendeskUrl,
|
||||
handleZendeskError,
|
||||
TICKET_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/zendesk/types'
|
||||
|
||||
const logger = createLogger('ZendeskUpdateTicket')
|
||||
|
||||
export interface ZendeskUpdateTicketParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
ticketId: string
|
||||
subject?: string
|
||||
comment?: string
|
||||
priority?: string
|
||||
status?: string
|
||||
type?: string
|
||||
tags?: string
|
||||
assigneeId?: string
|
||||
groupId?: string
|
||||
customFields?: string
|
||||
}
|
||||
|
||||
export interface ZendeskUpdateTicketResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
ticket: any
|
||||
ticket_id: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskUpdateTicketTool: ToolConfig<
|
||||
ZendeskUpdateTicketParams,
|
||||
ZendeskUpdateTicketResponse
|
||||
> = {
|
||||
id: 'zendesk_update_ticket',
|
||||
name: 'Update Ticket in Zendesk',
|
||||
description: 'Update an existing ticket in Zendesk with support for custom fields',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
ticketId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Ticket ID to update as a numeric string (e.g., "12345")',
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New ticket subject text',
|
||||
},
|
||||
comment: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comment text to add to the ticket',
|
||||
},
|
||||
priority: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Priority: "low", "normal", "high", or "urgent"',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Status: "new", "open", "pending", "hold", "solved", or "closed"',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Type: "problem", "incident", "question", or "task"',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated tags (e.g., "billing, urgent")',
|
||||
},
|
||||
assigneeId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Assignee user ID as a numeric string (e.g., "12345")',
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group ID as a numeric string (e.g., "67890")',
|
||||
},
|
||||
customFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Custom fields as JSON object (e.g., {"field_id": "value"})',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, `/tickets/${params.ticketId}`),
|
||||
method: 'PUT',
|
||||
headers: (params) => {
|
||||
// Use Basic Authentication with email/token format for Zendesk API tokens
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const ticket: any = {}
|
||||
|
||||
if (params.subject) ticket.subject = params.subject
|
||||
if (params.priority) ticket.priority = params.priority
|
||||
if (params.status) ticket.status = params.status
|
||||
if (params.type) ticket.type = params.type
|
||||
if (params.assigneeId) ticket.assignee_id = params.assigneeId
|
||||
if (params.groupId) ticket.group_id = params.groupId
|
||||
if (params.tags) ticket.tags = params.tags.split(',').map((t) => t.trim())
|
||||
if (params.comment) ticket.comment = { body: params.comment }
|
||||
|
||||
if (params.customFields) {
|
||||
try {
|
||||
const customFields = JSON.parse(params.customFields)
|
||||
ticket.custom_fields = Object.entries(customFields).map(([id, value]) => ({ id, value }))
|
||||
} catch (error) {
|
||||
logger.warn('Failed to parse custom fields', { error })
|
||||
}
|
||||
}
|
||||
|
||||
return { ticket }
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'update_ticket')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
ticket: data.ticket,
|
||||
ticket_id: data.ticket?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ticket: {
|
||||
type: 'object',
|
||||
description: 'Updated ticket object',
|
||||
properties: TICKET_OUTPUT_PROPERTIES,
|
||||
},
|
||||
ticket_id: { type: 'number', description: 'The updated ticket ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError, JOB_STATUS_OUTPUT } from '@/tools/zendesk/types'
|
||||
|
||||
export interface ZendeskUpdateTicketsBulkParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
ticketIds: string
|
||||
status?: string
|
||||
priority?: string
|
||||
assigneeId?: string
|
||||
groupId?: string
|
||||
tags?: string
|
||||
}
|
||||
|
||||
export interface ZendeskUpdateTicketsBulkResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
job_status: any
|
||||
job_id?: string
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskUpdateTicketsBulkTool: ToolConfig<
|
||||
ZendeskUpdateTicketsBulkParams,
|
||||
ZendeskUpdateTicketsBulkResponse
|
||||
> = {
|
||||
id: 'zendesk_update_tickets_bulk',
|
||||
name: 'Bulk Update Tickets in Zendesk',
|
||||
description: 'Update multiple tickets in Zendesk at once (max 100)',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
ticketIds: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated ticket IDs to update (max 100, e.g., "111, 222, 333")',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'New status for all tickets: "new", "open", "pending", "hold", "solved", or "closed"',
|
||||
},
|
||||
priority: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New priority for all tickets: "low", "normal", "high", or "urgent"',
|
||||
},
|
||||
assigneeId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New assignee ID for all tickets as a numeric string (e.g., "12345")',
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New group ID for all tickets as a numeric string (e.g., "67890")',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated tags to add to all tickets (e.g., "bulk-update, processed")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const ids = params.ticketIds.split(',').map((id) => id.trim())
|
||||
return buildZendeskUrl(params.subdomain, `/tickets/update_many?ids=${ids.join(',')}`)
|
||||
},
|
||||
method: 'PUT',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const ticket: any = {}
|
||||
if (params.status) ticket.status = params.status
|
||||
if (params.priority) ticket.priority = params.priority
|
||||
if (params.assigneeId) ticket.assignee_id = params.assigneeId
|
||||
if (params.groupId) ticket.group_id = params.groupId
|
||||
if (params.tags) ticket.tags = params.tags.split(',').map((t) => t.trim())
|
||||
return { ticket }
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'update_tickets_bulk')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
job_status: data.job_status,
|
||||
job_id: data.job_status?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
job_status: JOB_STATUS_OUTPUT,
|
||||
job_id: { type: 'string', description: 'The bulk operation job ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError, USER_OUTPUT_PROPERTIES } from '@/tools/zendesk/types'
|
||||
|
||||
const logger = createLogger('ZendeskUpdateUser')
|
||||
|
||||
export interface ZendeskUpdateUserParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
userId: string
|
||||
name?: string
|
||||
userEmail?: string
|
||||
role?: string
|
||||
phone?: string
|
||||
organizationId?: string
|
||||
verified?: string
|
||||
tags?: string
|
||||
customFields?: string
|
||||
}
|
||||
|
||||
export interface ZendeskUpdateUserResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
user: any
|
||||
user_id: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskUpdateUserTool: ToolConfig<ZendeskUpdateUserParams, ZendeskUpdateUserResponse> =
|
||||
{
|
||||
id: 'zendesk_update_user',
|
||||
name: 'Update User in Zendesk',
|
||||
description: 'Update an existing user in Zendesk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID to update as a numeric string (e.g., "12345")',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New user full name (e.g., "John Smith")',
|
||||
},
|
||||
userEmail: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New user email address (e.g., "john@example.com")',
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User role: "end-user", "agent", or "admin"',
|
||||
},
|
||||
phone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User phone number (e.g., "+1-555-123-4567")',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization ID as a numeric string (e.g., "67890")',
|
||||
},
|
||||
verified: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Set to "true" to mark user as verified, or "false" otherwise',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated tags (e.g., "vip, enterprise")',
|
||||
},
|
||||
customFields: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Custom fields as JSON object (e.g., {"field_id": "value"})',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, `/users/${params.userId}`),
|
||||
method: 'PUT',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const user: any = {}
|
||||
|
||||
if (params.name) user.name = params.name
|
||||
if (params.userEmail) user.email = params.userEmail
|
||||
if (params.role) user.role = params.role
|
||||
if (params.phone) user.phone = params.phone
|
||||
if (params.organizationId) user.organization_id = params.organizationId
|
||||
if (params.verified) user.verified = params.verified === 'true'
|
||||
if (params.tags) user.tags = params.tags.split(',').map((t) => t.trim())
|
||||
|
||||
if (params.customFields) {
|
||||
try {
|
||||
const customFields = JSON.parse(params.customFields)
|
||||
user.user_fields = customFields
|
||||
} catch (error) {
|
||||
logger.warn('Failed to parse custom fields', { error })
|
||||
}
|
||||
}
|
||||
|
||||
return { user }
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'update_user')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
user: data.user,
|
||||
user_id: data.user?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
user: {
|
||||
type: 'object',
|
||||
description: 'Updated user object',
|
||||
properties: USER_OUTPUT_PROPERTIES,
|
||||
},
|
||||
user_id: { type: 'number', description: 'The updated user ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import { buildZendeskUrl, handleZendeskError, JOB_STATUS_OUTPUT } from '@/tools/zendesk/types'
|
||||
|
||||
const logger = createLogger('ZendeskUpdateUsersBulk')
|
||||
|
||||
export interface ZendeskUpdateUsersBulkParams {
|
||||
email: string
|
||||
apiToken: string
|
||||
subdomain: string
|
||||
users: string
|
||||
}
|
||||
|
||||
export interface ZendeskUpdateUsersBulkResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
job_status: any
|
||||
job_id: string
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const zendeskUpdateUsersBulkTool: ToolConfig<
|
||||
ZendeskUpdateUsersBulkParams,
|
||||
ZendeskUpdateUsersBulkResponse
|
||||
> = {
|
||||
id: 'zendesk_update_users_bulk',
|
||||
name: 'Bulk Update Users in Zendesk',
|
||||
description: 'Update multiple users in Zendesk using bulk update',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk email address',
|
||||
},
|
||||
apiToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'Zendesk API token',
|
||||
},
|
||||
subdomain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Your Zendesk subdomain',
|
||||
},
|
||||
users: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of user objects to update, each must include id field (e.g., [{"id": "123", "name": "New Name"}])',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => buildZendeskUrl(params.subdomain, '/users/update_many'),
|
||||
method: 'PUT',
|
||||
headers: (params) => {
|
||||
const credentials = `${params.email}/token:${params.apiToken}`
|
||||
const base64Credentials = Buffer.from(credentials).toString('base64')
|
||||
return {
|
||||
Authorization: `Basic ${base64Credentials}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
try {
|
||||
const users = JSON.parse(params.users)
|
||||
return { users }
|
||||
} catch (error) {
|
||||
logger.error('Failed to parse users array', { error })
|
||||
throw new Error('Invalid users JSON format')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
handleZendeskError(data, response.status, 'update_users_bulk')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
job_status: data.job_status,
|
||||
job_id: data.job_status?.id,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
job_status: JOB_STATUS_OUTPUT,
|
||||
job_id: { type: 'string', description: 'The bulk operation job ID' },
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user