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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -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' },
},
}
+191
View File
@@ -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' },
},
}
+167
View File
@@ -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' },
},
}
+104
View File
@@ -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' },
},
}
+90
View File
@@ -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' },
},
}
+88
View File
@@ -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' },
},
}
+127
View File
@@ -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,
},
}
+97
View File
@@ -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' },
},
}
+197
View File
@@ -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,
},
}
+92
View File
@@ -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' },
},
}
+140
View File
@@ -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,
},
}
+26
View File
@@ -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'
+123
View File
@@ -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',
},
},
}
+144
View File
@@ -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,
},
}
+96
View File
@@ -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' },
},
}
+149
View File
@@ -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,
},
}
+531
View File
@@ -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' },
},
}
+189
View File
@@ -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' },
},
}
+174
View File
@@ -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' },
},
}
+104
View File
@@ -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' },
},
}