Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 30 additions & 30 deletions apps/docs/content/docs/integrations/helpscout.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ Move a Help Scout conversation to another inbox.

### Help Scout Set Conversation Tags

Replace the full tag list on a Help Scout conversation. Tags left out are removed, an empty list removes every tag, and unknown tags are created.
Replace the full tag list on a Help Scout conversation. Tags left out are removed, [] removes every tag, and unknown tags are created.

<ReferenceDetails>

Expand All @@ -441,7 +441,7 @@ Replace the full tag list on a Help Scout conversation. Tags left out are remove
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | Yes | Conversation ID |
| `tags` | string | No | Comma-separated tags the conversation should have, including any existing tags to keep. Leave empty to remove every tag. |
| `tags` | string | Yes | Comma-separated tags the conversation should have, including any existing tags to keep. Pass \[\] to remove every tag. |

#### Output

Expand Down Expand Up @@ -1345,7 +1345,7 @@ A trigger starts a workflow when a supported event occurs in this service.

### Help Scout Agent Replied

Trigger workflow when a user replies to a Help Scout conversation (auto replies are not included)
Trigger workflow when an agent replies to a Help Scout conversation (auto replies are not included)

<ReferenceDetails label="Configuration and outputs">

Expand Down Expand Up @@ -1373,9 +1373,9 @@ Trigger workflow when a user replies to a Help Scout conversation (auto replies
| ↳ `subject` | string | Subject |
| ↳ `preview` | string | Preview text from the most recent thread |
| ↳ `mailboxId` | number | Inbox ID |
| ↳ `assignee` | json | User or team the conversation is assigned to |
| ↳ `createdBy` | json | User or customer who created the conversation |
| ↳ `primaryCustomer` | json | Primary customer of the conversation |
| ↳ `assignee` | json | User or team the conversation is assigned to \(id, type, first, last, name, email\) |
| ↳ `createdBy` | json | User or customer who created the conversation \(id, type, first, last, name, email\) |
| ↳ `primaryCustomer` | json | Primary customer of the conversation \(id, type, first, last, name, email\) |
| ↳ `createdAt` | string | When the conversation was created |
| ↳ `closedAt` | string | When the conversation was closed |
| ↳ `closedBy` | number | ID of the user who closed it |
Expand Down Expand Up @@ -1423,9 +1423,9 @@ Trigger workflow when a Help Scout conversation is assigned
| ↳ `subject` | string | Subject |
| ↳ `preview` | string | Preview text from the most recent thread |
| ↳ `mailboxId` | number | Inbox ID |
| ↳ `assignee` | json | User or team the conversation is assigned to |
| ↳ `createdBy` | json | User or customer who created the conversation |
| ↳ `primaryCustomer` | json | Primary customer of the conversation |
| ↳ `assignee` | json | User or team the conversation is assigned to \(id, type, first, last, name, email\) |
| ↳ `createdBy` | json | User or customer who created the conversation \(id, type, first, last, name, email\) |
| ↳ `primaryCustomer` | json | Primary customer of the conversation \(id, type, first, last, name, email\) |
| ↳ `createdAt` | string | When the conversation was created |
| ↳ `closedAt` | string | When the conversation was closed |
| ↳ `closedBy` | number | ID of the user who closed it |
Expand Down Expand Up @@ -1473,9 +1473,9 @@ Trigger workflow when a conversation is created in Help Scout
| ↳ `subject` | string | Subject |
| ↳ `preview` | string | Preview text from the most recent thread |
| ↳ `mailboxId` | number | Inbox ID |
| ↳ `assignee` | json | User or team the conversation is assigned to |
| ↳ `createdBy` | json | User or customer who created the conversation |
| ↳ `primaryCustomer` | json | Primary customer of the conversation |
| ↳ `assignee` | json | User or team the conversation is assigned to \(id, type, first, last, name, email\) |
| ↳ `createdBy` | json | User or customer who created the conversation \(id, type, first, last, name, email\) |
| ↳ `primaryCustomer` | json | Primary customer of the conversation \(id, type, first, last, name, email\) |
| ↳ `createdAt` | string | When the conversation was created |
| ↳ `closedAt` | string | When the conversation was closed |
| ↳ `closedBy` | number | ID of the user who closed it |
Expand Down Expand Up @@ -1547,9 +1547,9 @@ Trigger workflow when Help Scout conversations are merged
| ↳ `subject` | string | Subject |
| ↳ `preview` | string | Preview text from the most recent thread |
| ↳ `mailboxId` | number | Inbox ID |
| ↳ `assignee` | json | User or team the conversation is assigned to |
| ↳ `createdBy` | json | User or customer who created the conversation |
| ↳ `primaryCustomer` | json | Primary customer of the conversation |
| ↳ `assignee` | json | User or team the conversation is assigned to \(id, type, first, last, name, email\) |
| ↳ `createdBy` | json | User or customer who created the conversation \(id, type, first, last, name, email\) |
| ↳ `primaryCustomer` | json | Primary customer of the conversation \(id, type, first, last, name, email\) |
| ↳ `createdAt` | string | When the conversation was created |
| ↳ `closedAt` | string | When the conversation was closed |
| ↳ `closedBy` | number | ID of the user who closed it |
Expand Down Expand Up @@ -1597,9 +1597,9 @@ Trigger workflow when a Help Scout conversation is moved to another inbox
| ↳ `subject` | string | Subject |
| ↳ `preview` | string | Preview text from the most recent thread |
| ↳ `mailboxId` | number | Inbox ID |
| ↳ `assignee` | json | User or team the conversation is assigned to |
| ↳ `createdBy` | json | User or customer who created the conversation |
| ↳ `primaryCustomer` | json | Primary customer of the conversation |
| ↳ `assignee` | json | User or team the conversation is assigned to \(id, type, first, last, name, email\) |
| ↳ `createdBy` | json | User or customer who created the conversation \(id, type, first, last, name, email\) |
| ↳ `primaryCustomer` | json | Primary customer of the conversation \(id, type, first, last, name, email\) |
| ↳ `createdAt` | string | When the conversation was created |
| ↳ `closedAt` | string | When the conversation was closed |
| ↳ `closedBy` | number | ID of the user who closed it |
Expand Down Expand Up @@ -1647,9 +1647,9 @@ Trigger workflow when the status of a Help Scout conversation changes
| ↳ `subject` | string | Subject |
| ↳ `preview` | string | Preview text from the most recent thread |
| ↳ `mailboxId` | number | Inbox ID |
| ↳ `assignee` | json | User or team the conversation is assigned to |
| ↳ `createdBy` | json | User or customer who created the conversation |
| ↳ `primaryCustomer` | json | Primary customer of the conversation |
| ↳ `assignee` | json | User or team the conversation is assigned to \(id, type, first, last, name, email\) |
| ↳ `createdBy` | json | User or customer who created the conversation \(id, type, first, last, name, email\) |
| ↳ `primaryCustomer` | json | Primary customer of the conversation \(id, type, first, last, name, email\) |
| ↳ `createdAt` | string | When the conversation was created |
| ↳ `closedAt` | string | When the conversation was closed |
| ↳ `closedBy` | number | ID of the user who closed it |
Expand Down Expand Up @@ -1697,9 +1697,9 @@ Trigger workflow when the tags on a Help Scout conversation change
| ↳ `subject` | string | Subject |
| ↳ `preview` | string | Preview text from the most recent thread |
| ↳ `mailboxId` | number | Inbox ID |
| ↳ `assignee` | json | User or team the conversation is assigned to |
| ↳ `createdBy` | json | User or customer who created the conversation |
| ↳ `primaryCustomer` | json | Primary customer of the conversation |
| ↳ `assignee` | json | User or team the conversation is assigned to \(id, type, first, last, name, email\) |
| ↳ `createdBy` | json | User or customer who created the conversation \(id, type, first, last, name, email\) |
| ↳ `primaryCustomer` | json | Primary customer of the conversation \(id, type, first, last, name, email\) |
| ↳ `createdAt` | string | When the conversation was created |
| ↳ `closedAt` | string | When the conversation was closed |
| ↳ `closedBy` | number | ID of the user who closed it |
Expand Down Expand Up @@ -1791,9 +1791,9 @@ Trigger workflow when a customer replies to a Help Scout conversation
| ↳ `subject` | string | Subject |
| ↳ `preview` | string | Preview text from the most recent thread |
| ↳ `mailboxId` | number | Inbox ID |
| ↳ `assignee` | json | User or team the conversation is assigned to |
| ↳ `createdBy` | json | User or customer who created the conversation |
| ↳ `primaryCustomer` | json | Primary customer of the conversation |
| ↳ `assignee` | json | User or team the conversation is assigned to \(id, type, first, last, name, email\) |
| ↳ `createdBy` | json | User or customer who created the conversation \(id, type, first, last, name, email\) |
| ↳ `primaryCustomer` | json | Primary customer of the conversation \(id, type, first, last, name, email\) |
| ↳ `createdAt` | string | When the conversation was created |
| ↳ `closedAt` | string | When the conversation was closed |
| ↳ `closedBy` | number | ID of the user who closed it |
Expand Down Expand Up @@ -1885,9 +1885,9 @@ Trigger workflow when a note is added to a Help Scout conversation
| ↳ `subject` | string | Subject |
| ↳ `preview` | string | Preview text from the most recent thread |
| ↳ `mailboxId` | number | Inbox ID |
| ↳ `assignee` | json | User or team the conversation is assigned to |
| ↳ `createdBy` | json | User or customer who created the conversation |
| ↳ `primaryCustomer` | json | Primary customer of the conversation |
| ↳ `assignee` | json | User or team the conversation is assigned to \(id, type, first, last, name, email\) |
| ↳ `createdBy` | json | User or customer who created the conversation \(id, type, first, last, name, email\) |
| ↳ `primaryCustomer` | json | Primary customer of the conversation \(id, type, first, last, name, email\) |
| ↳ `createdAt` | string | When the conversation was created |
| ↳ `closedAt` | string | When the conversation was closed |
| ↳ `closedBy` | number | ID of the user who closed it |
Expand Down
1 change: 1 addition & 0 deletions apps/sim/blocks/blocks/helpscout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ export const HelpScoutBlock: BlockConfig = {
type: 'short-input',
placeholder: 'vip, billing',
condition: { field: 'operation', value: ['create_conversation', 'set_conversation_tags'] },
required: { field: 'operation', value: 'set_conversation_tags' },
},
{
id: 'createStatus',
Expand Down
9 changes: 4 additions & 5 deletions apps/sim/connectors/helpscout/helpscout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import { type RetryOptions, VALIDATE_RETRY_OPTIONS } from '@/lib/knowledge/docum
import { DEFAULT_MAX_CONVERSATIONS, helpScoutConnectorMeta } from '@/connectors/helpscout/meta'
import type { ConnectorConfig, ExternalDocument, ExternalDocumentList } from '@/connectors/types'
import { computeContentHash, htmlToPlainText, joinTagArray, parseTagDate } from '@/connectors/utils'
import { HELPSCOUT_API_BASE, parseIdText } from '@/tools/helpscout/utils'

const logger = createLogger('HelpScoutConnector')

const API_BASE = 'https://api.helpscout.net/v2'
const HELPSCOUT_ID_PATTERN = /^\d{1,16}$/
const STATUSES = new Set(['all', 'active', 'pending', 'closed'])
/** Help Scout caps a conversation at 100 threads, 50 per page, so two pages cover every thread. */
const MAX_THREAD_PAGES = 4
Expand Down Expand Up @@ -119,7 +118,7 @@ async function helpScoutGet(
query: Record<string, string> = {},
retryOptions?: RetryOptions
): Promise<unknown> {
const url = new URL(`${API_BASE}${path}`)
const url = new URL(`${HELPSCOUT_API_BASE}${path}`)
for (const [key, value] of Object.entries(query)) url.searchParams.set(key, value)
const response = await fetchWithRetry(
url.toString(),
Expand All @@ -140,7 +139,7 @@ function optionalId(value: unknown, label: string): string | undefined {
const text =
typeof value === 'string' ? value.trim() : typeof value === 'number' ? String(value) : ''
if (!text) return undefined
if (!HELPSCOUT_ID_PATTERN.test(text)) throw new Error(`${label} must be a numeric Help Scout ID`)
if (parseIdText(text) === null) throw new Error(`${label} must be a numeric Help Scout ID`)
return text
}

Expand Down Expand Up @@ -404,7 +403,7 @@ export const helpScoutConnector: ConnectorConfig = {
sourceConfig: Record<string, unknown>,
externalId: string
): Promise<ExternalDocument | null> => {
if (!HELPSCOUT_ID_PATTERN.test(externalId)) return null
if (parseIdText(externalId) === null) return null
const includeNotes = sourceConfig.includeNotes !== 'exclude'
try {
const conversation = ConversationSchema.parse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ export interface ClientCredentialAccountDescriptor {
defaultAuthMethod?: string
/** Sim setup guide, docked bottom-left of the connect modal. */
docsUrl: string
/** Optional one-line caveat rendered in the connect modal. */
/**
* Optional one-line caveat, rendered under the `orgId` field of the connect modal. A descriptor
* without an `orgId` field puts its guidance in a field `hint` instead.
*/
helpText?: string
}

Expand Down Expand Up @@ -434,6 +437,7 @@ export const CLIENT_CREDENTIAL_ACCOUNT_DESCRIPTORS: Record<
label: 'App ID',
placeholder: 'Paste the App ID',
secret: false,
hint: 'Create the app in Help Scout under Your Profile > My Apps.',
},
{
id: 'clientSecret',
Expand All @@ -443,7 +447,6 @@ export const CLIENT_CREDENTIAL_ACCOUNT_DESCRIPTORS: Record<
},
],
docsUrl: 'https://docs.sim.ai/integrations/helpscout',
helpText: 'Create the app in Help Scout under Your Profile > My Apps.',
},
[VANTA_SERVICE_ACCOUNT_PROVIDER_ID]: {
providerId: VANTA_SERVICE_ACCOUNT_PROVIDER_ID,
Expand Down
6 changes: 2 additions & 4 deletions apps/sim/lib/selectors/server/providers/helpscout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
requireListRequest,
type ServerSelectorAttachmentMap,
} from '@/lib/selectors/server/types'
import { HELPSCOUT_API_BASE, HELPSCOUT_ID_PATTERN } from '@/tools/helpscout/utils'
import { HELPSCOUT_API_BASE, parseIdText } from '@/tools/helpscout/utils'

type HelpScoutSelectorKey = Extract<
ServerSelectorKey,
Expand Down Expand Up @@ -91,9 +91,7 @@ async function helpScoutHeaders(args: ExecuteServerSelectorArgs): Promise<Record

function requireHelpScoutId(value: string): string {
const id = value.trim()
if (!HELPSCOUT_ID_PATTERN.test(id) || !Number.isSafeInteger(Number(id))) {
throw new SelectorContextUnavailableError()
}
if (parseIdText(id) === null) throw new SelectorContextUnavailableError()
return id
}

Expand Down
25 changes: 14 additions & 11 deletions apps/sim/lib/webhooks/providers/helpscout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ import type {
} from '@/lib/webhooks/providers/types'
import { createHmacVerifier } from '@/lib/webhooks/providers/utils'
import { ErrorExtractorId, extractErrorMessage } from '@/tools/error-extractors'
import { HELPSCOUT_API_BASE, projectConversation, projectCustomer } from '@/tools/helpscout/utils'
import {
HELPSCOUT_API_BASE,
parseIdText,
projectConversation,
projectCustomer,
readResourceId,
} from '@/tools/helpscout/utils'
import {
getHelpScoutSubscriptionEvents,
HELPSCOUT_GENERIC_TRIGGER_ID,
Expand All @@ -45,7 +51,6 @@ function validateHelpScoutSignature(secret: string, signature: string, rawBody:
return safeCompare(computed, signature.trim())
}

const HELPSCOUT_ID_PATTERN = /^\d{1,16}$/
/** Help Scout accepts a signing secret of at most 40 characters. */
const WEBHOOK_SECRET_LENGTH = 40

Expand All @@ -60,8 +65,8 @@ function readEventType(headers: Record<string, string>): string {
function resolveInboxFilter(config: Record<string, unknown>): number | undefined {
const raw = toStringOrNull(config.triggerMailboxId)?.trim()
if (!raw) return undefined
const id = Number(raw)
if (!HELPSCOUT_ID_PATTERN.test(raw) || !Number.isSafeInteger(id) || id <= 0) {
const id = parseIdText(raw)
if (id === null) {
throw new WebhookDeploymentConfigurationError('The Help Scout inbox ID must be numeric.')
}
return id
Expand Down Expand Up @@ -94,12 +99,10 @@ async function readHelpScoutErrorDetail(response: Response): Promise<string> {

/** The new webhook ID comes from `Resource-ID`, with the `Location` URL as a fallback. */
function readCreatedWebhookId(response: Response): string | null {
const resourceId = toStringOrNull(response.headers.get('resource-id'))?.trim()
if (resourceId && HELPSCOUT_ID_PATTERN.test(resourceId)) return resourceId
const fromLocation = toStringOrNull(response.headers.get('location'))?.match(
/\/webhooks\/(\d{1,16})\/?$/
)?.[1]
return fromLocation ?? null
const id =
readResourceId(response) ??
parseIdText(response.headers.get('location')?.match(/\/webhooks\/(\d+)\/?$/)?.[1] ?? null)
return id === null ? null : String(id)
}

async function requireAccessToken(config: Record<string, unknown>, requestId: string) {
Expand Down Expand Up @@ -176,7 +179,7 @@ export const helpScoutHandler: WebhookProviderHandler = {
logger.info(`[${requestId}] No managed Help Scout webhook to delete`)
return
}
if (!HELPSCOUT_ID_PATTERN.test(externalId)) {
if (parseIdText(externalId) === null) {
if (strict) throw new Error('Invalid Help Scout webhook ID for deletion')
return
}
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/generated/tool-metadata.ts

Large diffs are not rendered by default.

17 changes: 13 additions & 4 deletions apps/sim/tools/helpscout/set-conversation-tags.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { toStringOrNull } from '@sim/utils/coerce'
import { ErrorExtractorId } from '@/tools/error-extractors'
import type {
HelpScoutConversationChangeResponse,
Expand All @@ -14,14 +15,22 @@ import {
} from '@/tools/helpscout/utils'
import type { ToolConfig } from '@/tools/types'

/** An empty list removes every tag, so clearing takes an explicit `[]` and never an omitted value. */
function parseTags(value: unknown): string[] {
if (Array.isArray(value) ? value.length === 0 : toStringOrNull(value)?.trim() === '[]') return []
const tags = toStringList(value)
if (tags.length === 0) throw new Error('tags is empty; pass [] to remove every tag')
return tags
}

export const helpScoutSetConversationTagsTool: ToolConfig<
HelpScoutSetConversationTagsParams,
HelpScoutConversationChangeResponse
> = {
id: 'helpscout_set_conversation_tags',
name: 'Help Scout Set Conversation Tags',
description:
'Replace the full tag list on a Help Scout conversation. Tags left out are removed, an empty list removes every tag, and unknown tags are created.',
'Replace the full tag list on a Help Scout conversation. Tags left out are removed, [] removes every tag, and unknown tags are created.',
version: '1.0.0',
oauth: { required: true, provider: 'helpscout' },
errorExtractor: ErrorExtractorId.HELPSCOUT_ERRORS,
Expand All @@ -30,17 +39,17 @@ export const helpScoutSetConversationTagsTool: ToolConfig<
conversationId: CONVERSATION_ID_PARAM,
tags: {
type: 'string',
required: false,
required: true,
visibility: 'user-or-llm',
description:
'Comma-separated tags the conversation should have, including any existing tags to keep. Leave empty to remove every tag.',
'Comma-separated tags the conversation should have, including any existing tags to keep. Pass [] to remove every tag.',
},
},
request: {
url: (params) => conversationUrl(params.conversationId, '/tags'),
method: 'PUT',
headers: (params) => buildHelpScoutHeaders(params.accessToken),
body: (params) => ({ tags: toStringList(params.tags) }),
body: (params) => ({ tags: parseTags(params.tags) }),
},
transformResponse: async (_response, params) => conversationChangeResult(params),
outputs: CONVERSATION_CHANGE_OUTPUTS,
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/helpscout/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ export interface HelpScoutMoveConversationParams extends HelpScoutConversationPa
export type HelpScoutDeleteConversationParams = HelpScoutConversationParams

export interface HelpScoutSetConversationTagsParams extends HelpScoutConversationParams {
tags?: string | string[]
tags: string | string[]
}

export interface HelpScoutConversationChangeResponse extends ToolResponse {
Expand Down
Loading
Loading