diff --git a/.github/workflows/deploy-integrations-production.yml b/.github/workflows/deploy-integrations-production.yml index b5fbe7656a1..5413793c15c 100644 --- a/.github/workflows/deploy-integrations-production.yml +++ b/.github/workflows/deploy-integrations-production.yml @@ -31,7 +31,7 @@ jobs: uses: ./.github/actions/deploy-integrations with: environment: 'production' - extra_filter: "-F '!docusign' -F '!whatsapp'" + extra_filter: "-F '!docusign'" force: ${{ github.event.inputs.force == 'true' }} sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }} token_cloud_ops_account: ${{ secrets.PRODUCTION_TOKEN_CLOUD_OPS_ACCOUNT }} diff --git a/integrations/anthropic/integration.definition.ts b/integrations/anthropic/integration.definition.ts index ff4257263b7..25666f54e06 100644 --- a/integrations/anthropic/integration.definition.ts +++ b/integrations/anthropic/integration.definition.ts @@ -1,4 +1,3 @@ -/* bplint-disable */ import { z, IntegrationDefinition } from '@botpress/sdk' import { ModelId } from 'src/schemas' import llm from './bp_modules/llm' @@ -7,7 +6,7 @@ export default new IntegrationDefinition({ name: 'anthropic', title: 'Anthropic', description: 'Access a curated list of Claude models to set as your chosen LLM.', - version: '12.0.0', + version: '12.0.1', readme: 'hub.md', icon: 'icon.svg', entities: { @@ -22,9 +21,6 @@ export default new IntegrationDefinition({ description: 'Anthropic API key', }, }, - __advanced: { - useLegacyZuiTransformer: true, - }, }).extend(llm, ({ entities }) => ({ entities: { modelRef: entities.modelRef }, })) diff --git a/integrations/instagram/integration.definition.ts b/integrations/instagram/integration.definition.ts index 1a921e96d5d..6c9e5e7811d 100644 --- a/integrations/instagram/integration.definition.ts +++ b/integrations/instagram/integration.definition.ts @@ -16,7 +16,7 @@ const commonConfigSchema = z.object({ export default new IntegrationDefinition({ name: INTEGRATION_NAME, - version: '4.1.0', + version: '4.1.1', title: 'Instagram', description: 'Automate interactions, manage comments, and send/receive messages all in real-time.', icon: 'icon.svg', @@ -187,6 +187,9 @@ export default new IntegrationDefinition({ SANDBOX_INSTAGRAM_ID: { description: 'Instagram ID for the Sandbox Instagram profile', }, + POSTHOG_KEY: { + description: 'The PostHog key for the Instagram integration', + }, }, user: { tags: { diff --git a/integrations/instagram/package.json b/integrations/instagram/package.json index 445e567f896..4b68ff932c5 100644 --- a/integrations/instagram/package.json +++ b/integrations/instagram/package.json @@ -14,7 +14,8 @@ "@botpress/client": "workspace:*", "@botpress/sdk": "workspace:*", "@botpress/sdk-addons": "workspace:*", - "axios": "^1.6.2" + "axios": "^1.6.2", + "posthog-node": "^5.10.4" }, "devDependencies": { "@botpress/cli": "workspace:*", diff --git a/integrations/instagram/src/misc/posthog-client.ts b/integrations/instagram/src/misc/posthog-client.ts new file mode 100644 index 00000000000..a9c8fb09641 --- /dev/null +++ b/integrations/instagram/src/misc/posthog-client.ts @@ -0,0 +1,49 @@ +import { EventMessage, PostHog } from 'posthog-node' +import * as bp from '.botpress' + +type BotpressEventMessage = Omit & { + event: BotpressEvent +} + +type PostHogErrorOptions = { + from: string + integrationName: string + errorType?: BotpressEvent +} + +export const botpressEvents = { + UNHANDLED_ERROR: 'unhandled_error', + UNHANDLED_MESSAGE: 'unhandled_message', + INVALID_MESSAGE_FORMAT: 'invalid_message_format', +} as const +type BotpressEvent = (typeof botpressEvents)[keyof typeof botpressEvents] + +const sendPosthogEvent = async (props: BotpressEventMessage): Promise => { + const client = new PostHog(bp.secrets.POSTHOG_KEY, { + host: 'https://us.i.posthog.com', + }) + try { + await client.captureImmediate(props) + await client.shutdown() + console.info('PostHog event sent') + } catch (thrown: any) { + const errMsg = thrown instanceof Error ? thrown.message : String(thrown) + console.error(`The server for posthog could not be reached - Error: ${errMsg}`) + } +} + +export const sendPosthogError = async ( + distinctId: string, + errorMessage: string, + { from, integrationName, errorType = botpressEvents.UNHANDLED_ERROR }: PostHogErrorOptions +): Promise => { + await sendPosthogEvent({ + distinctId, + event: errorType, + properties: { + from, + integrationName, + message: errorMessage, + }, + }) +} diff --git a/integrations/instagram/src/webhook/handler.ts b/integrations/instagram/src/webhook/handler.ts index 0c85ab1daf1..8a9bbf65e89 100644 --- a/integrations/instagram/src/webhook/handler.ts +++ b/integrations/instagram/src/webhook/handler.ts @@ -1,7 +1,9 @@ import { isSandboxCommand } from '@botpress/common' import { Request } from '@botpress/sdk' import * as crypto from 'crypto' +import { INTEGRATION_NAME } from 'integration.definition' import { getClientSecret } from 'src/misc/client' +import { sendPosthogError } from 'src/misc/posthog-client' import { instagramPayloadSchema, InstagramLegacyCommentEntry, @@ -48,13 +50,15 @@ const _handler: bp.IntegrationProps['handler'] = async (props: bp.HandlerProps) } const { data, success } = safeJsonParse(req.body) if (!success) { - return { status: 400, body: 'Invalid payload body' } + const errorMsg = 'Unable to parse request payload as JSON' + return { status: 400, body: errorMsg } } // Parse payload once with entry-level union schema const payloadResult = instagramPayloadSchema.safeParse(data) if (!payloadResult.success) { - props.logger.warn('Received invalid Instagram payload:', payloadResult.error.message) + const errorMsg = `Received invalid Instagram payload: ${payloadResult.error.message}` + props.logger.warn(errorMsg) return { status: 400, body: 'Invalid payload' } } @@ -113,14 +117,23 @@ const _handlerWrapper: typeof _handler = async (props: bp.HandlerProps) => { try { const response = await _handler(props) if (response && response.status !== 200) { - props.logger.error(`Instagram handler failed with status ${response.status}: ${response.body}`) + const errorMessage = `Instagram handler failed with status ${response.status}: ${response.body}` + props.logger.error(errorMessage) + await sendPosthogError(props.ctx.integrationId, errorMessage, { + from: `${INTEGRATION_NAME}:handler`, + integrationName: INTEGRATION_NAME, + }) } return response - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : String(error) - const logMessage = `Instagram handler failed with error: ${errorMessage ?? 'Unknown error thrown'}` - props.logger.error(logMessage) - return { status: 500, body: logMessage } + } catch (thrown: unknown) { + const errorMsg = thrown instanceof Error ? thrown.message : String(thrown) + const errorMessage = `Instagram handler failed with error: ${errorMsg}` + await sendPosthogError(props.ctx.integrationId, errorMessage, { + from: `${INTEGRATION_NAME}:handler`, + integrationName: INTEGRATION_NAME, + }) + props.logger.error(errorMessage) + return { status: 500, body: errorMessage } } } diff --git a/integrations/line/integration.definition.ts b/integrations/line/integration.definition.ts index 42a104b71dc..5707a38dcf2 100644 --- a/integrations/line/integration.definition.ts +++ b/integrations/line/integration.definition.ts @@ -6,7 +6,7 @@ import typingIndicator from 'bp_modules/typing-indicator' export default new IntegrationDefinition({ name: 'line', - version: '2.0.0', + version: '2.0.2', title: 'Line', description: 'Interact with customers using a rich set of features.', icon: 'icon.svg', @@ -49,7 +49,6 @@ export default new IntegrationDefinition({ description: 'Line user ID of the bot', }, }, - creation: { enabled: true, requiredTags: ['usrId', 'destId'] }, }, }, }, @@ -84,7 +83,6 @@ export default new IntegrationDefinition({ description: 'Line user ID', }, }, - creation: { enabled: true, requiredTags: ['usrId'] }, }, entities: { user: { diff --git a/integrations/line/src/index.ts b/integrations/line/src/index.ts index 10c810f453d..fa137614a05 100644 --- a/integrations/line/src/index.ts +++ b/integrations/line/src/index.ts @@ -1,8 +1,8 @@ import { RuntimeError } from '@botpress/client' +import { transformMarkdown } from '@botpress/common' import { sentry as sentryHelpers } from '@botpress/sdk-addons' import { messagingApi as lineMessagingApi } from '@line/bot-sdk' import crypto from 'crypto' -import { parseMarkdown } from './markdown-parser' import getOrCreateConversation from './proactive-conversation' import getOrCreateUser from './proactive-user' import * as bp from '.botpress' @@ -61,11 +61,11 @@ const replyOrSendLineMessage = async (props: SendOrReplyLineProps, message: line } } -const tryParseMarkdown = (text: string) => { +const tryTransformMarkdown = (text: string) => { try { - return parseMarkdown(text) + return transformMarkdown(text) } catch { - console.error('Failed to parse the markdown. The message will be sent as text without parsing markdown.') + console.error('Failed to transform the markdown. The message will be sent as text without transfoming markdown.') return text } } @@ -108,7 +108,7 @@ const integration = new bp.Integration({ { ctx, conversation, client, ack }, { type: 'text', - text: tryParseMarkdown(payload.text), + text: tryTransformMarkdown(payload.text), } ) }, diff --git a/integrations/line/src/markdown-parser.ts b/integrations/line/src/markdown-parser.ts deleted file mode 100644 index 58231e50894..00000000000 --- a/integrations/line/src/markdown-parser.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { - Blockquote, - Break, - Code, - Delete, - Emphasis, - FootnoteDefinition, - FootnoteReference, - Heading, - Html, - Image, - InlineCode, - Link, - List, - ListItem, - Node, - Paragraph, - Root, - Strong, - Table, - TableCell, - Text, - ThematicBreak, -} from 'mdast' -import { remark } from 'remark' -import remarkGfm from 'remark-gfm' - -const FIXED_SIZE_SPACE_CHAR = '\u2002' // 'En space' yields better results for identation in WhatsApp messages -type NodeHandler = ( - node: N, - visit: (node: RootNodes) => string, - parents: RootNodes[], - handlers: MarkdownHandlers -) => string - -type MarkdownHandlers = { - blockquote: NodeHandler
- break: NodeHandler - code: NodeHandler - delete: NodeHandler - emphasis: NodeHandler - footnoteDefinition: NodeHandler - footnoteReference: NodeHandler - heading: NodeHandler - html: NodeHandler - image: NodeHandler - inlineCode: NodeHandler - link: NodeHandler - list: NodeHandler - paragraph: NodeHandler - strong: NodeHandler - table: NodeHandler - text: NodeHandler - thematicBreak: NodeHandler -} - -const stripAllHandlers: MarkdownHandlers = { - blockquote: (node, visit) => `Quote: “${visit(node as Blockquote)}”\n`, - break: (_node, _visit) => '\n', - code: (node, _visit) => `${(node as Code).value}\n`, - delete: (node, visit) => `${visit(node as Delete)}`, - emphasis: (node, visit) => visit(node as Emphasis), - footnoteDefinition: (node, visit) => - `[${(node as FootnoteDefinition).identifier}] ${visit(node as FootnoteDefinition)}\n`, - footnoteReference: (node, _visit) => `[${(node as FootnoteReference).identifier}]`, - heading: (node, visit) => `${visit(node as Heading)}\n`, - html: (_node, _visit) => '', - image: (node, _visit) => (node as Image).url, - inlineCode: (node, _visit) => (node as InlineCode).value, - link: (node, _visit) => (node as Link).url, - list: (node, _visit, parents, handlers) => _handleList(node as List, handlers, parents), - paragraph: (node, visit, parents) => `${visit(node as Paragraph)}${parents.at(-1)?.type === 'root' ? '\n' : ''}`, - strong: (node, visit) => visit(node as Strong), - table: (node, _visit, parents, handlers) => _handleTable(node as Table, handlers, parents), - text: (node, _visit) => (node as Text).value, - thematicBreak: (_node, _visit) => '---\n', -} - -type RootNodes = - | Blockquote - | Delete - | Emphasis - | FootnoteDefinition - | Heading - | List - | ListItem - | Paragraph - | Root - | Strong - | Table - | TableCell - -const isNodeType = (s: string, handlers: MarkdownHandlers): s is keyof MarkdownHandlers => s in handlers - -const _visitTree = (tree: RootNodes, handlers: MarkdownHandlers, parents: RootNodes[]): string => { - let tmp = '' - let footnoteTmp = '' - parents.push(tree) - for (const node of tree.children) { - if (!isNodeType(node.type, handlers)) { - throw new Error(`The Markdown node type [${node.type}] is not supported`) - } - - const handler = handlers[node.type] as NodeHandler - - if (node.type === 'footnoteDefinition') { - footnoteTmp += handler(node, (n) => _visitTree(n, handlers, parents), parents, handlers) - continue - } - tmp += handler(node, (n) => _visitTree(n, handlers, parents), parents, handlers) - } - parents.pop() - return `${tmp}${footnoteTmp}` -} - -const _handleList = (listNode: List, handlers: MarkdownHandlers, parents: RootNodes[]): string => { - parents.push(listNode) - const listLevel = parents.filter((parent) => parent.type === 'list').length - let listTmp = listLevel !== 1 ? '\n' : '' - let itemCount = 0 - for (const listItemNode of listNode.children) { - itemCount++ - let prefix = FIXED_SIZE_SPACE_CHAR.repeat(listLevel - 1) - if (listItemNode.checked !== undefined && listItemNode.checked !== null) { - prefix += listItemNode.checked ? '☑︎ ' : '☐ ' - } else { - prefix += listNode.ordered ? `${itemCount}. ` : '- ' - } - const shouldBreak = listLevel === 1 || itemCount !== listNode.children.length - listTmp = `${listTmp}${prefix}${_visitTree(listItemNode, handlers, parents)}${shouldBreak ? '\n' : ''}` - } - parents.pop() - return listTmp -} - -const _handleTable = (tableNode: Table, handlers: MarkdownHandlers, parents: RootNodes[]): string => { - parents.push(tableNode) - let tmpTable = '' - for (const tableRow of tableNode.children) { - let childrenCount = 0 - let tmpRow = '| ' - for (const tableCell of tableRow.children) { - tmpRow = `${tmpRow}${_visitTree(tableCell, handlers, parents)}${childrenCount + 1 === tableRow.children.length ? ' |' : ' | '}` - childrenCount++ - } - tmpTable = `${tmpTable}${tmpRow}\n` - } - parents.pop() - return tmpTable -} - -export const parseMarkdown = (markdown: string): string => { - const tree = remark().use(remarkGfm).parse(markdown) - return _visitTree(tree, stripAllHandlers, []) -} diff --git a/integrations/messenger/integration.definition.ts b/integrations/messenger/integration.definition.ts index 9de3ddccdd4..51dfe536d9e 100644 --- a/integrations/messenger/integration.definition.ts +++ b/integrations/messenger/integration.definition.ts @@ -4,6 +4,8 @@ import proactiveConversation from 'bp_modules/proactive-conversation' import proactiveUser from 'bp_modules/proactive-user' import typingIndicator from 'bp_modules/typing-indicator' +export const INTEGRATION_NAME = 'messenger' + const commonConfigSchema = z.object({ downloadMedia: z .boolean() @@ -23,8 +25,8 @@ const commonConfigSchema = z.object({ }) export default new IntegrationDefinition({ - name: 'messenger', - version: '4.1.0', + name: INTEGRATION_NAME, + version: '4.1.1', title: 'Messenger', description: 'Give your bot access to one of the world’s largest messaging platform.', icon: 'icon.svg', @@ -158,6 +160,9 @@ export default new IntegrationDefinition({ SANDBOX_SHOULD_GET_USER_PROFILE: { description: "Whether to get the user profile infos from Messenger when creating a new user ('true' or 'false')", }, + POSTHOG_KEY: { + description: 'The PostHog API key', + }, }, user: { tags: { id: { title: 'User ID', description: 'The Messenger ID of the user' } }, diff --git a/integrations/messenger/package.json b/integrations/messenger/package.json index a324cdd6aa8..dbc5095efc0 100644 --- a/integrations/messenger/package.json +++ b/integrations/messenger/package.json @@ -14,7 +14,8 @@ "@botpress/sdk": "workspace:*", "@botpress/sdk-addons": "workspace:*", "axios": "^1.6.2", - "messaging-api-messenger": "^1.1.0" + "messaging-api-messenger": "^1.1.0", + "posthog-node": "^5.10.4" }, "devDependencies": { "@botpress/cli": "workspace:*", diff --git a/integrations/messenger/src/misc/posthog-client.ts b/integrations/messenger/src/misc/posthog-client.ts new file mode 100644 index 00000000000..bc3bba5dfc1 --- /dev/null +++ b/integrations/messenger/src/misc/posthog-client.ts @@ -0,0 +1,49 @@ +import { EventMessage, PostHog } from 'posthog-node' +import * as bp from '.botpress' + +type BotpressEventMessage = Omit & { + event: BotpressEvent +} + +type PostHogErrorOptions = { + from: string + integrationName: string + errorType?: BotpressEvent +} + +export const botpressEvents = { + UNHANDLED_ERROR: 'unhandled_error', + UNHANDLED_MESSAGE: 'unhandled_message', + INVALID_MESSAGE_FORMAT: 'invalid_message_format', +} as const +type BotpressEvent = (typeof botpressEvents)[keyof typeof botpressEvents] + +const sendPosthogEvent = async (props: BotpressEventMessage): Promise => { + const client = new PostHog(bp.secrets.POSTHOG_KEY, { + host: 'https://us.i.posthog.com', + }) + try { + await client.captureImmediate(props) + await client.shutdown() + console.info('PostHog event sent') + } catch (thrown: unknown) { + const errMsg = thrown instanceof Error ? thrown.message : String(thrown) + console.error(`The server for posthog could not be reached - Error: ${errMsg}`) + } +} + +export const sendPosthogError = async ( + distinctId: string, + errorMessage: string, + { from, integrationName, errorType }: PostHogErrorOptions +): Promise => { + await sendPosthogEvent({ + distinctId, + event: errorType ?? botpressEvents.UNHANDLED_ERROR, + properties: { + from, + integrationName, + message: errorMessage, + }, + }) +} diff --git a/integrations/messenger/src/webhook/handler.ts b/integrations/messenger/src/webhook/handler.ts index f9064475499..8a89302278b 100644 --- a/integrations/messenger/src/webhook/handler.ts +++ b/integrations/messenger/src/webhook/handler.ts @@ -1,7 +1,9 @@ import { isSandboxCommand, meta } from '@botpress/common' +import { INTEGRATION_NAME } from 'integration.definition' +import { sendPosthogError } from 'src/misc/posthog-client' import { getClientSecret, getVerifyToken } from '../misc/auth' import { messengerPayloadSchema } from '../misc/types' -import { getErrorFromUnknown, safeJsonParse } from '../misc/utils' +import { safeJsonParse } from '../misc/utils' import { oauthHandler, messageHandler, sandboxHandler } from './handlers' import * as bp from '.botpress' @@ -39,8 +41,9 @@ const _handler: bp.IntegrationProps['handler'] = async (props) => { const parseResult = messengerPayloadSchema.safeParse(jsonParseResult.data) if (!parseResult.success) { - logger.forBot().warn('Error while parsing body as Messenger payload:', parseResult.error.message) - return + const errorMessage = `Error while parsing body as Messenger payload: ${parseResult.error.message}` + logger.forBot().warn(errorMessage) + return { status: 400, body: errorMessage } } const data = parseResult.data @@ -56,11 +59,23 @@ const _handlerWrapper: typeof _handler = async (props: bp.HandlerProps) => { try { const response = await _handler(props) if (response?.status && response.status >= 400) { - props.logger.error(`Messenger handler failed with status ${response.status}: ${response.body}`) + const errorMessage = `Messenger handler failed with status ${response.status}: ${response.body}` + props.logger.error(errorMessage) + await sendPosthogError(props.ctx.integrationId, errorMessage, { + from: `${INTEGRATION_NAME}:handler`, + integrationName: INTEGRATION_NAME, + }) } return response - } catch (error) { - return { status: 500, body: getErrorFromUnknown(error).message } + } catch (thrown: unknown) { + const errorMsg = thrown instanceof Error ? thrown.message : String(thrown) + const errorMessage = `Messenger handler failed with error: ${errorMsg}` + props.logger.error(errorMessage) + await sendPosthogError(props.ctx.integrationId, errorMessage, { + from: `${INTEGRATION_NAME}:handler`, + integrationName: INTEGRATION_NAME, + }) + return { status: 500, body: errorMessage } } } diff --git a/integrations/slack/integration.definition.ts b/integrations/slack/integration.definition.ts index 4bf25c3f152..cdd609a62c1 100644 --- a/integrations/slack/integration.definition.ts +++ b/integrations/slack/integration.definition.ts @@ -17,7 +17,7 @@ export default new IntegrationDefinition({ name: 'slack', title: 'Slack', description: 'Automate interactions with your team.', - version: '3.1.0', + version: '3.1.1', icon: 'icon.svg', readme: 'hub.md', configuration, diff --git a/integrations/slack/src/actions/start-channel-conversation.ts b/integrations/slack/src/actions/start-channel-conversation.ts index fd1f5fe991e..f6186380cdf 100644 --- a/integrations/slack/src/actions/start-channel-conversation.ts +++ b/integrations/slack/src/actions/start-channel-conversation.ts @@ -6,7 +6,7 @@ export const startChannelConversation = wrapActionAndInjectSlackClient( async ({ client, logger, slackClient }, { channelName }) => { const slackChannelInfo = await slackClient.getChannelInfo({ channelName }) if (slackChannelInfo === undefined) { - const errorMessage = 'The channel id provided does not exist' + const errorMessage = `The channel "${channelName}" does not exist or your bot does not have access to it` logger.forBot().error(errorMessage) throw new RuntimeError(errorMessage) } diff --git a/integrations/slack/src/slack-api/slack-client.ts b/integrations/slack/src/slack-api/slack-client.ts index 7c2445b8673..5f9d696f3bb 100644 --- a/integrations/slack/src/slack-api/slack-client.ts +++ b/integrations/slack/src/slack-api/slack-client.ts @@ -327,7 +327,7 @@ export class SlackClient { public async getChannelInfo({ channelName }: { channelName: string }) { const allChannels: SlackWebApi.ConversationsListResponse['channels'] = [] for await (const page of this._slackWebClient.paginate('conversations.list', { - types: 'public_channel', + types: 'public_channel,private_channel', exclude_archived: true, limit: 200, }) as AsyncIterable) { diff --git a/integrations/twilio/integration.definition.ts b/integrations/twilio/integration.definition.ts index de8c8fdfab0..153f5b9d319 100644 --- a/integrations/twilio/integration.definition.ts +++ b/integrations/twilio/integration.definition.ts @@ -3,9 +3,10 @@ import { sentry as sentryHelpers } from '@botpress/sdk-addons' import proactiveConversation from 'bp_modules/proactive-conversation' import proactiveUser from 'bp_modules/proactive-user' +export const INTEGRATION_NAME = 'twilio' export default new IntegrationDefinition({ - name: 'twilio', - version: '1.0.0', + name: INTEGRATION_NAME, + version: '1.0.1', title: 'Twilio', description: 'Send and receive messages, voice calls, emails, SMS, and more.', icon: 'icon.svg', @@ -60,7 +61,12 @@ export default new IntegrationDefinition({ }, actions: {}, events: {}, - secrets: sentryHelpers.COMMON_SECRET_NAMES, + secrets: { + ...sentryHelpers.COMMON_SECRET_NAMES, + POSTHOG_KEY: { + description: 'Posthog key for error dashboards', + }, + }, user: { tags: { userPhone: { diff --git a/integrations/twilio/package.json b/integrations/twilio/package.json index de44aabdd35..27c7a0a99ac 100644 --- a/integrations/twilio/package.json +++ b/integrations/twilio/package.json @@ -10,12 +10,12 @@ }, "dependencies": { "@botpress/client": "workspace:*", + "@botpress/common": "workspace:*", "@botpress/sdk": "workspace:*", "@botpress/sdk-addons": "workspace:*", "axios": "^1.6.0", + "posthog-node": "^5.10.4", "query-string": "^6.14.1", - "remark": "^15.0.1", - "remark-gfm": "^4.0.1", "twilio": "^3.84.0" }, "devDependencies": { @@ -23,8 +23,7 @@ "@botpress/common": "workspace:*", "@botpresshub/proactive-conversation": "workspace:*", "@botpresshub/proactive-user": "workspace:*", - "@sentry/cli": "^2.39.1", - "@types/mdast": "^4.0.4" + "@sentry/cli": "^2.39.1" }, "bpDependencies": { "proactive-conversation": "../../interfaces/proactive-conversation", diff --git a/integrations/twilio/src/index.ts b/integrations/twilio/src/index.ts index 4b617724989..bce20acc7e7 100644 --- a/integrations/twilio/src/index.ts +++ b/integrations/twilio/src/index.ts @@ -5,7 +5,8 @@ import axios from 'axios' import * as crypto from 'crypto' import queryString from 'query-string' import { Twilio } from 'twilio' -import { parseMarkdown } from './markdown-to-twilio' +import { transformMarkdownForTwilio } from './markdown-to-twilio' +import { botpressEvents, sendPosthogEvent } from './posthogClient' import * as types from './types' import * as bp from '.botpress' @@ -399,10 +400,14 @@ async function sendMessage({ ctx, conversation, ack, mediaUrl, text, logger }: S let body = text if (body !== undefined) { try { - body = parseMarkdown(body, twilioChannel) + body = transformMarkdownForTwilio(body, twilioChannel) } catch (thrown) { const errMsg = thrown instanceof Error ? thrown.message : String(thrown) - logger.forBot().debug('Failed to parse markdown - Error:', errMsg) + logger.forBot().debug('Failed to transform markdown - Error:', errMsg) + await sendPosthogEvent({ + distinctId: errMsg, + event: botpressEvents.UNHANDLED_MARKDOWN, + }) } } const { sid } = await twilioClient.messages.create({ to, from, mediaUrl, body }) diff --git a/integrations/twilio/src/markdown-to-twilio.test.ts b/integrations/twilio/src/markdown-to-twilio.test.ts index f1d05ecb6ec..776e462369a 100644 --- a/integrations/twilio/src/markdown-to-twilio.test.ts +++ b/integrations/twilio/src/markdown-to-twilio.test.ts @@ -1,85 +1,11 @@ import { test, expect } from 'vitest' -import { parseMarkdown } from './markdown-to-twilio' +import { transformMarkdownForTwilio } from './markdown-to-twilio' const FIXED_SIZE_SPACE_CHAR = '\u2002' // 'En space' yields better results for identation in WhatsApp messages type Test = Record -const stripAllTests: Test = { - empty: { input: '', expected: '' }, - Text: { input: 'test', expected: 'test\n' }, - Paragraph: { input: 'first Paragraph\n\nSecond Paragraph', expected: 'first Paragraph\nSecond Paragraph\n' }, - Hard_line_break: { input: 'line one \nline two', expected: 'line one\nline two\n' }, - Title_1: { input: '# H1', expected: 'H1\n' }, - Title_2: { input: '## H2', expected: 'H2\n' }, - Title_3: { input: '### H3', expected: 'H3\n' }, - Ordered_list: { input: '1. orderedListItem1\n2. item2', expected: '1. orderedListItem1\n2. item2\n' }, - Unordered_list: { input: '- unorderedListItem1\n- item2', expected: '- unorderedListItem1\n- item2\n' }, - ThematicBreak: { input: 'horizontal\n\n---\n\nrule', expected: 'horizontal\n---\nrule\n' }, - Image: { input: '![image](https://tinyurl.com/mrv4bmyk)', expected: 'https://tinyurl.com/mrv4bmyk\n' }, - Image_with_fallback: { input: '![test](https://tinyurl.com/mrv4bmyk)', expected: 'https://tinyurl.com/mrv4bmyk\n' }, - Table: { input: '| 1 | 2 |\n| - | - |\n| a | b |', expected: '| 1 | 2 |\n| a | b |\n' }, - Footnote: { input: 'footnote[^1]\n\n[^1]: the footnote', expected: 'footnote[1]\n[1] the footnote\n' }, - Definition: { input: 'term\n: definition', expected: 'term\n: definition\n' }, - Task_list: { input: '- [x] taskListItem1\n- [ ] item2', expected: '☑︎ taskListItem1\n☐ item2\n' }, - Emoji_direct: { input: 'emoji direct 😂', expected: 'emoji direct 😂\n' }, - Link: { input: '[title](https://www.example.com)', expected: 'https://www.example.com\n' }, - Link_only: { input: 'https://www.example.com', expected: 'https://www.example.com\n' }, - Blockquote: { input: '> blockquote', expected: 'Quote: “blockquote”\n' }, - Code: { input: '`code`', expected: 'code\n' }, - Code_snippet: { input: '```\n{\n\ta: null\n}\n```', expected: '{\n\ta: null\n}\n' }, - Strong_with_asterisk: { input: '**bold-asterisk**', expected: 'bold-asterisk\n' }, - Strong_with_underscore: { input: '__bold-underscore__', expected: 'bold-underscore\n' }, - Emphasis_with_asterisk: { input: '*italic-asterisk*', expected: 'italic-asterisk\n' }, - Emphasis_with_underscore: { input: '_italic-underscore_', expected: 'italic-underscore\n' }, - Strong_emphasis: { input: '**_strong-emphasis_**', expected: 'strong-emphasis\n' }, - Strong_delete: { input: '**~strong-delete~**', expected: 'strong-delete\n' }, - Emphasis_delete: { input: '**_emphasis-delete_**', expected: 'emphasis-delete\n' }, - Strong_emphasis_delete: { input: '**_~strong-emphasis-delete~_**', expected: 'strong-emphasis-delete\n' }, - Delete: { input: '~~strikethrough~~', expected: 'strikethrough\n' }, - Link_in_list: { - input: '- first\n- [title](https://www.example.com)', - expected: '- first\n- https://www.example.com\n', - }, - Image_in_list: { - input: '- first\n- ![image](https://tinyurl.com/mrv4bmyk)', - expected: '- first\n- https://tinyurl.com/mrv4bmyk\n', - }, - Strong_in_list: { - input: '- first\n- **strong_second**', - expected: '- first\n- strong_second\n', - }, - Unordered_sub_list: { - input: '- first\n- second\n\t- sub-first\n\t- sub-second\n- third', - expected: `- first\n- second\n${FIXED_SIZE_SPACE_CHAR}- sub-first\n${FIXED_SIZE_SPACE_CHAR}- sub-second\n- third\n`, - }, - Ordered_sub_sub_list: { - input: '1. root\n\t1. sub-one\n\t\t1. sub-two\n', - expected: `1. root\n${FIXED_SIZE_SPACE_CHAR}1. sub-one\n${FIXED_SIZE_SPACE_CHAR.repeat(2)}1. sub-two\n`, - }, - Mixed_sub_lists: { - input: '- unordered\n\t1. ordered\n\t\t- [ ] task', - expected: `- unordered\n${FIXED_SIZE_SPACE_CHAR}1. ordered\n${FIXED_SIZE_SPACE_CHAR.repeat(2)}☐ task\n`, - }, - Complicated_lists: { - input: '- unordered\n\t1. ordered\n- unordered\n\t1. ordered\n\t\t- [ ] task\n- unordered\n', - expected: `- unordered\n${FIXED_SIZE_SPACE_CHAR}1. ordered\n- unordered\n${FIXED_SIZE_SPACE_CHAR}1. ordered\n${FIXED_SIZE_SPACE_CHAR.repeat(2)}☐ task\n- unordered\n`, - }, - Emphasize_in_table: { - input: '| 1 | 2 |\n| - | - |\n| a | _b_ |', - expected: '| 1 | 2 |\n| a | b |\n', - }, - Image_in_table: { - input: '| 1 | 2 |\n| - | - |\n| a | ![image](https://tinyurl.com/mrv4bmyk) |', - expected: '| 1 | 2 |\n| a | https://tinyurl.com/mrv4bmyk |\n', - }, - Link_in_table: { - input: '| 1 | 2 |\n| - | - |\n| a | [title](https://www.example.com) |', - expected: '| 1 | 2 |\n| a | https://www.example.com |\n', - }, -} const messengerTests: Test = { - ...stripAllTests, Code: { input: '`code`', expected: '`code`\n' }, Code_snippet: { input: '```\n{\n\ta: null\n}\n```', expected: '```\n{\n\ta: null\n}\n```\n' }, Strong_with_asterisk: { input: '**bold-asterisk**', expected: '*bold-asterisk*\n' }, @@ -106,26 +32,10 @@ const whatsappTests: Test = { Code_snippet: { input: '```\n{\n\ta: null\n}\n```', expected: '```{\n\ta: null\n}```\n' }, } -test.each(Object.entries(stripAllTests))( - '[SMS, MMS] Test %s', - (_testName: string, testValues: { input: string; expected: string }): void => { - const actual = parseMarkdown(testValues.input, 'sms/mms') - expect(actual).toBe(testValues.expected) - } -) - -test.each(Object.entries(stripAllTests))( - '[RCS] Test %s', - (_testName: string, testValues: { input: string; expected: string }): void => { - const actualRcs = parseMarkdown(testValues.input, 'rcs') - expect(actualRcs).toBe(testValues.expected) - } -) - test.each(Object.entries(messengerTests))( '[Messenger] Test %s', (_testName: string, testValues: { input: string; expected: string }): void => { - const actual = parseMarkdown(testValues.input, 'messenger') + const actual = transformMarkdownForTwilio(testValues.input, 'messenger') expect(actual).toBe(testValues.expected) } ) @@ -133,7 +43,7 @@ test.each(Object.entries(messengerTests))( test.each(Object.entries(whatsappTests))( '[WhatsApp] Test %s', (_testName: string, testValues: { input: string; expected: string }): void => { - const actual = parseMarkdown(testValues.input, 'whatsapp') + const actual = transformMarkdownForTwilio(testValues.input, 'whatsapp') expect(actual).toBe(testValues.expected) } ) @@ -181,41 +91,6 @@ term emoji direct 😂 ` -const expectedForBigInputSMS = `H1 -H2 -H3 -bold-asterisk -italic-asterisk -bold-underscore -italic-underscore -Quote: “blockquote” -1. orderedListItem1 -2. item2 -- unorderedListItem1 -- item2 -code -horizontal ---- -rule -https://www.example.com -https://tinyurl.com/mrv4bmyk -https://tinyurl.com/mrv4bmyk -| 1 | 2 | -| a | b | -{ - a: null -} -footnote[1] -term -: definition -strikethrough -☑︎ taskListItem1 -☐ item2 -emoji direct 😂 -[1] the footnote -` -const expectedForBigInputRCS = expectedForBigInputSMS - const expectedForBigInputMessenger = `H1 H2 H3 @@ -285,22 +160,12 @@ emoji direct 😂 [1] the footnote ` -test('[SMS, MMS] Multi-line multi markup test', () => { - const actual = parseMarkdown(bigInput, 'sms/mms') - expect(actual).toBe(expectedForBigInputSMS) -}) - -test('[RCS] Multi-line multi markup test', () => { - const actualRcs = parseMarkdown(bigInput, 'rcs') - expect(actualRcs).toBe(expectedForBigInputRCS) -}) - test('[Messenger] Multi-line multi markup test', () => { - const actual = parseMarkdown(bigInput, 'messenger') + const actual = transformMarkdownForTwilio(bigInput, 'messenger') expect(actual).toBe(expectedForBigInputMessenger) }) test('[WhatsApp] Multi-line multi markup test', () => { - const actual = parseMarkdown(bigInput, 'whatsapp') + const actual = transformMarkdownForTwilio(bigInput, 'whatsapp') expect(actual).toBe(expectedForBigInputWhatsApp) }) diff --git a/integrations/twilio/src/markdown-to-twilio.ts b/integrations/twilio/src/markdown-to-twilio.ts index f64487cbbeb..f3f9a040f92 100644 --- a/integrations/twilio/src/markdown-to-twilio.ts +++ b/integrations/twilio/src/markdown-to-twilio.ts @@ -1,186 +1,31 @@ -import { - Blockquote, - Break, - Code, - Delete, - Emphasis, - FootnoteDefinition, - FootnoteReference, - Heading, - Html, - Image, - InlineCode, - Link, - List, - ListItem, - Node, - Paragraph, - Root, - Strong, - Table, - TableCell, - Text, - ThematicBreak, -} from 'mdast' -import { remark } from 'remark' -import remarkGfm from 'remark-gfm' +import { transformMarkdown, MarkdownHandlers, stripAllHandlers } from '@botpress/common' import { TwilioChannel } from './types' -const FIXED_SIZE_SPACE_CHAR = '\u2002' // 'En space' yields better results for identation in WhatsApp messages -type NodeHandler = ( - node: N, - visit: (node: RootNodes) => string, - parents: RootNodes[], - handlers: MarkdownHandlers -) => string - -type MarkdownHandlers = { - blockquote: NodeHandler
- break: NodeHandler - code: NodeHandler - delete: NodeHandler - emphasis: NodeHandler - footnoteDefinition: NodeHandler - footnoteReference: NodeHandler - heading: NodeHandler - html: NodeHandler - image: NodeHandler - inlineCode: NodeHandler - link: NodeHandler - list: NodeHandler - paragraph: NodeHandler - strong: NodeHandler - table: NodeHandler
- text: NodeHandler - thematicBreak: NodeHandler -} - -const stripAllHandlers: MarkdownHandlers = { - blockquote: (node, visit) => `Quote: “${visit(node as Blockquote)}”\n`, - break: (_node, _visit) => '\n', - code: (node, _visit) => `${(node as Code).value}\n`, - delete: (node, visit) => `${visit(node as Delete)}`, - emphasis: (node, visit) => visit(node as Emphasis), - footnoteDefinition: (node, visit) => - `[${(node as FootnoteDefinition).identifier}] ${visit(node as FootnoteDefinition)}\n`, - footnoteReference: (node, _visit) => `[${(node as FootnoteReference).identifier}]`, - heading: (node, visit) => `${visit(node as Heading)}\n`, - html: (_node, _visit) => '', - image: (node, _visit) => (node as Image).url, - inlineCode: (node, _visit) => (node as InlineCode).value, - link: (node, _visit) => (node as Link).url, - list: (node, _visit, parents, handlers) => _handleList(node as List, handlers, parents), - paragraph: (node, visit, parents) => `${visit(node as Paragraph)}${parents.at(-1)?.type === 'root' ? '\n' : ''}`, - strong: (node, visit) => visit(node as Strong), - table: (node, _visit, parents, handlers) => _handleTable(node as Table, handlers, parents), - text: (node, _visit) => (node as Text).value, - thematicBreak: (_node, _visit) => '---\n', -} - const messengerHandlers: MarkdownHandlers = { ...stripAllHandlers, - code: (node, _visit) => `\`\`\`\n${(node as Code).value}\n\`\`\`\n`, - delete: (node, visit) => `~${visit(node as Delete)}~`, - emphasis: (node, visit) => `_${visit(node as Emphasis)}_`, - inlineCode: (node, _visit) => `\`${(node as InlineCode).value}\``, - strong: (node, visit) => `*${visit(node as Strong)}*`, + code: (node, _visit) => `\`\`\`\n${node.value}\n\`\`\`\n`, + delete: (node, visit) => `~${visit(node)}~`, + emphasis: (node, visit) => `_${visit(node)}_`, + inlineCode: (node, _visit) => `\`${node.value}\``, + strong: (node, visit) => `*${visit(node)}*`, } const whatsappHandlers: MarkdownHandlers = { ...stripAllHandlers, - code: (node, _visit) => `\`\`\`${(node as Code).value}\`\`\`\n`, - delete: (node, visit) => `~${visit(node as Delete)}~`, - emphasis: (node, visit) => `_${visit(node as Emphasis)}_`, - inlineCode: (node, _visit) => `\`${(node as InlineCode).value}\``, - strong: (node, visit) => `*${visit(node as Strong)}*`, -} - -type RootNodes = - | Blockquote - | Delete - | Emphasis - | FootnoteDefinition - | Heading - | List - | ListItem - | Paragraph - | Root - | Strong - | Table - | TableCell - -const isNodeType = (s: string, handlers: MarkdownHandlers): s is keyof MarkdownHandlers => s in handlers - -const _visitTree = (tree: RootNodes, handlers: MarkdownHandlers, parents: RootNodes[]): string => { - let tmp = '' - let footnoteTmp = '' - parents.push(tree) - for (const node of tree.children) { - if (!isNodeType(node.type, handlers)) { - throw new Error(`The Markdown node type [${node.type}] is not supported`) - } - - const handler = handlers[node.type] as NodeHandler - - if (node.type === 'footnoteDefinition') { - footnoteTmp += handler(node, (n) => _visitTree(n, handlers, parents), parents, handlers) - continue - } - tmp += handler(node, (n) => _visitTree(n, handlers, parents), parents, handlers) - } - parents.pop() - return `${tmp}${footnoteTmp}` -} - -const _handleList = (listNode: List, handlers: MarkdownHandlers, parents: RootNodes[]): string => { - parents.push(listNode) - const listLevel = parents.filter((parent) => parent.type === 'list').length - let listTmp = listLevel !== 1 ? '\n' : '' - let itemCount = 0 - for (const listItemNode of listNode.children) { - itemCount++ - let prefix = FIXED_SIZE_SPACE_CHAR.repeat(listLevel - 1) - if (listItemNode.checked !== undefined && listItemNode.checked !== null) { - prefix += listItemNode.checked ? '☑︎ ' : '☐ ' - } else { - prefix += listNode.ordered ? `${itemCount}. ` : '- ' - } - const shouldBreak = listLevel === 1 || itemCount !== listNode.children.length - listTmp = `${listTmp}${prefix}${_visitTree(listItemNode, handlers, parents)}${shouldBreak ? '\n' : ''}` - } - parents.pop() - return listTmp -} - -const _handleTable = (tableNode: Table, handlers: MarkdownHandlers, parents: RootNodes[]): string => { - parents.push(tableNode) - let tmpTable = '' - for (const tableRow of tableNode.children) { - let childrenCount = 0 - let tmpRow = '| ' - for (const tableCell of tableRow.children) { - tmpRow = `${tmpRow}${_visitTree(tableCell, handlers, parents)}${childrenCount + 1 === tableRow.children.length ? ' |' : ' | '}` - childrenCount++ - } - tmpTable = `${tmpTable}${tmpRow}\n` - } - parents.pop() - return tmpTable -} - -export const parseMarkdown = (markdown: string, channel: TwilioChannel): string => { - const tree = remark().use(remarkGfm).parse(markdown) - switch (channel) { - case 'messenger': - return _visitTree(tree, messengerHandlers, []) - case 'whatsapp': - return _visitTree(tree, whatsappHandlers, []) - case 'rcs': - return _visitTree(tree, stripAllHandlers, []) - case 'sms/mms': - return _visitTree(tree, stripAllHandlers, []) - default: - channel satisfies never - return _visitTree(tree, stripAllHandlers, []) - } + code: (node, _visit) => `\`\`\`${node.value}\`\`\`\n`, + delete: (node, visit) => `~${visit(node)}~`, + emphasis: (node, visit) => `_${visit(node)}_`, + inlineCode: (node, _visit) => `\`${node.value}\``, + strong: (node, visit) => `*${visit(node)}*`, +} + +const markdownHandlersByChannelType: Map = new Map([ + ['rcs', stripAllHandlers], + ['sms/mms', stripAllHandlers], + ['messenger', messengerHandlers], + ['whatsapp', whatsappHandlers], +]) + +export function transformMarkdownForTwilio(text: string, channel: TwilioChannel) { + return transformMarkdown(text, markdownHandlersByChannelType.get(channel)) } diff --git a/integrations/twilio/src/posthogClient.ts b/integrations/twilio/src/posthogClient.ts new file mode 100644 index 00000000000..1ed9e5168a3 --- /dev/null +++ b/integrations/twilio/src/posthogClient.ts @@ -0,0 +1,51 @@ +import { INTEGRATION_NAME } from 'integration.definition' +import { EventMessage, PostHog } from 'posthog-node' +import * as bp from '.botpress' + +type BotpressEventMessage = Omit & { + event: BotpressEvent +} + +type PostHogErrorOptions = { + from: string + integrationName: string +} + +export const botpressEvents = { + UNHANDLED_MARKDOWN: 'unhandled_markdown', + UNHANDLED_ERROR: 'unhandled_error', +} as const +type BotpressEvent = (typeof botpressEvents)[keyof typeof botpressEvents] + +export const sendPosthogEvent = async (props: BotpressEventMessage): Promise => { + const client = new PostHog(bp.secrets.POSTHOG_KEY, { + host: 'https://us.i.posthog.com', + }) + try { + const signedProps: BotpressEventMessage = { + ...props, + properties: { + ...props.properties, + integrationName: INTEGRATION_NAME, + }, + } + await client.captureImmediate(signedProps) + await client.shutdown() + console.info('PostHog event sent') + } catch (thrown: any) { + const errMsg = thrown instanceof Error ? thrown.message : String(thrown) + console.error(`The server for posthog could not be reached - Error: ${errMsg}`) + } +} + +export const sendPosthogError = async (thrown: unknown, { from }: Partial): Promise => { + const errMsg = thrown instanceof Error ? thrown.message : String(thrown) + await sendPosthogEvent({ + distinctId: errMsg, + event: botpressEvents.UNHANDLED_ERROR, + properties: { + from, + integrationName: INTEGRATION_NAME, + }, + }) +} diff --git a/integrations/viber/integration.definition.ts b/integrations/viber/integration.definition.ts index cb22c343bf6..b4421f9f00e 100644 --- a/integrations/viber/integration.definition.ts +++ b/integrations/viber/integration.definition.ts @@ -3,7 +3,7 @@ import { sentry as sentryHelpers } from '@botpress/sdk-addons' export default new IntegrationDefinition({ name: 'viber', - version: '1.0.0', + version: '1.0.2', title: 'Viber', description: 'Send and receive SMS messages.', icon: 'icon.svg', @@ -29,7 +29,6 @@ export default new IntegrationDefinition({ tags: { id: { title: 'User ID', description: 'Viber user ID taking part in the conversation' }, }, - creation: { enabled: true, requiredTags: ['id'] }, }, }, }, @@ -40,6 +39,5 @@ export default new IntegrationDefinition({ tags: { id: { title: 'User ID', description: 'Viber user ID' }, }, - creation: { enabled: true, requiredTags: ['id'] }, }, }) diff --git a/integrations/viber/src/index.ts b/integrations/viber/src/index.ts index e0370746fdc..7dfb2cf41dc 100644 --- a/integrations/viber/src/index.ts +++ b/integrations/viber/src/index.ts @@ -1,4 +1,5 @@ import { RuntimeError } from '@botpress/client' +import { transformMarkdown } from '@botpress/common' import { sentry as sentryHelpers } from '@botpress/sdk-addons' import axios from 'axios' import * as bp from '.botpress' @@ -19,7 +20,7 @@ const integration = new bp.Integration({ ...props, payload: { type: 'text', - text: props.payload.text, + text: tryTransformMarkdown(props.payload.text, props.logger.forBot()), }, }) }, @@ -411,3 +412,12 @@ function renderChoice(payload: Choice) { }) return choice } + +const tryTransformMarkdown = (text: string, logger: bp.Logger) => { + try { + return transformMarkdown(text) + } catch { + logger.error('Failed to transform the markdown. The message will be sent as text without transforming markdown.') + return text + } +} diff --git a/package.json b/package.json index 9ef2e828d14..b8ff87d506b 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "devDependencies": { "@aws-sdk/client-dynamodb": "^3.564.0", - "@botpress/api": "1.48.2", + "@botpress/api": "1.52.1", "@botpress/cli": "workspace:*", "@botpress/client": "workspace:*", "@botpress/sdk": "workspace:*", diff --git a/packages/cli/package.json b/packages/cli/package.json index 854c7219e41..6bc2c9da508 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/cli", - "version": "4.23.0", + "version": "4.23.1", "description": "Botpress CLI", "scripts": { "build": "pnpm run build:types && pnpm run bundle && pnpm run template:gen", @@ -26,8 +26,8 @@ "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.7.0", "@botpress/chat": "0.5.3", - "@botpress/client": "1.27.0", - "@botpress/sdk": "4.18.0", + "@botpress/client": "1.27.1", + "@botpress/sdk": "4.18.1", "@bpinternal/const": "^0.1.0", "@bpinternal/tunnel": "^0.1.1", "@bpinternal/verel": "^0.2.0", diff --git a/packages/cli/templates/empty-bot/package.json b/packages/cli/templates/empty-bot/package.json index 982172bd155..20fb96be3dc 100644 --- a/packages/cli/templates/empty-bot/package.json +++ b/packages/cli/templates/empty-bot/package.json @@ -5,8 +5,8 @@ }, "private": true, "dependencies": { - "@botpress/client": "1.27.0", - "@botpress/sdk": "4.18.0" + "@botpress/client": "1.27.1", + "@botpress/sdk": "4.18.1" }, "devDependencies": { "@types/node": "^22.16.4", diff --git a/packages/cli/templates/empty-integration/package.json b/packages/cli/templates/empty-integration/package.json index 57eaad73f74..02a3f52fd37 100644 --- a/packages/cli/templates/empty-integration/package.json +++ b/packages/cli/templates/empty-integration/package.json @@ -6,8 +6,8 @@ }, "private": true, "dependencies": { - "@botpress/client": "1.27.0", - "@botpress/sdk": "4.18.0" + "@botpress/client": "1.27.1", + "@botpress/sdk": "4.18.1" }, "devDependencies": { "@types/node": "^22.16.4", diff --git a/packages/cli/templates/empty-plugin/package.json b/packages/cli/templates/empty-plugin/package.json index f4ee1ffac5c..ab465a7c7e4 100644 --- a/packages/cli/templates/empty-plugin/package.json +++ b/packages/cli/templates/empty-plugin/package.json @@ -6,7 +6,7 @@ }, "private": true, "dependencies": { - "@botpress/sdk": "4.18.0" + "@botpress/sdk": "4.18.1" }, "devDependencies": { "@types/node": "^22.16.4", diff --git a/packages/cli/templates/hello-world/package.json b/packages/cli/templates/hello-world/package.json index b2ab219edd7..7128a7e1993 100644 --- a/packages/cli/templates/hello-world/package.json +++ b/packages/cli/templates/hello-world/package.json @@ -6,8 +6,8 @@ }, "private": true, "dependencies": { - "@botpress/client": "1.27.0", - "@botpress/sdk": "4.18.0" + "@botpress/client": "1.27.1", + "@botpress/sdk": "4.18.1" }, "devDependencies": { "@types/node": "^22.16.4", diff --git a/packages/cli/templates/webhook-message/package.json b/packages/cli/templates/webhook-message/package.json index 97b8f5c51af..0555e2bf69b 100644 --- a/packages/cli/templates/webhook-message/package.json +++ b/packages/cli/templates/webhook-message/package.json @@ -6,8 +6,8 @@ }, "private": true, "dependencies": { - "@botpress/client": "1.27.0", - "@botpress/sdk": "4.18.0", + "@botpress/client": "1.27.1", + "@botpress/sdk": "4.18.1", "axios": "^1.6.8" }, "devDependencies": { diff --git a/packages/client/openapi.ts b/packages/client/openapi.ts index b5c22bdb778..3326c563d28 100644 --- a/packages/client/openapi.ts +++ b/packages/client/openapi.ts @@ -1,6 +1,13 @@ import { runtimeApi, adminApi, filesApi, tablesApi, api as publicApi } from '@botpress/api' + +const options = { + generator: 'opapi', + ignoreDefaultParameters: true, + ignoreSecurity: true, +} as const + void publicApi.exportClient('./src/gen/public', { generator: 'opapi' }) -void runtimeApi.exportClient('./src/gen/runtime', { generator: 'opapi' }) -void adminApi.exportClient('./src/gen/admin', { generator: 'opapi' }) -void filesApi.exportClient('./src/gen/files', { generator: 'opapi' }) -void tablesApi.exportClient('./src/gen/tables', { generator: 'opapi' }) +void runtimeApi.exportClient('./src/gen/runtime', options) +void adminApi.exportClient('./src/gen/admin', options) +void filesApi.exportClient('./src/gen/files', options) +void tablesApi.exportClient('./src/gen/tables', options) diff --git a/packages/client/package.json b/packages/client/package.json index 4bfece1dcb4..d5b7ce7d024 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/client", - "version": "1.27.0", + "version": "1.27.1", "description": "Botpress Client", "main": "./dist/index.cjs", "module": "./dist/index.mjs", diff --git a/packages/cognitive/package.json b/packages/cognitive/package.json index f2f9711d762..008aef1eb47 100644 --- a/packages/cognitive/package.json +++ b/packages/cognitive/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/cognitive", - "version": "0.2.0", + "version": "0.2.1", "description": "Wrapper around the Botpress Client to call LLMs", "main": "./dist/index.cjs", "module": "./dist/index.mjs", diff --git a/packages/common/package.json b/packages/common/package.json index edfd6c7e1c7..3c58f6b97f3 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -12,6 +12,11 @@ "marked": "^15.0.1", "openai": "^5.12.1", "preact": "^10.26.6", - "preact-render-to-string": "^6.5.13" + "preact-render-to-string": "^6.5.13", + "remark": "^15.0.1", + "remark-gfm": "^4.0.1" + }, + "devDependencies": { + "@types/mdast": "^4.0.4" } } diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index bfb7cb35ad6..87213d3f36b 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -10,3 +10,4 @@ export * from './conversation-transcript' export * from './user-resolver' export * from './sandbox' export * as meta from './meta' +export * from './markdown-transformer' diff --git a/packages/common/src/markdown-transformer/index.ts b/packages/common/src/markdown-transformer/index.ts new file mode 100644 index 00000000000..e1c40c64caa --- /dev/null +++ b/packages/common/src/markdown-transformer/index.ts @@ -0,0 +1,2 @@ +export { transformMarkdown, stripAllHandlers } from './markdown-transformer' +export { MarkdownHandlers } from './types' diff --git a/integrations/line/src/markdown-parser.test.ts b/packages/common/src/markdown-transformer/markdown-transformer.test.ts similarity index 78% rename from integrations/line/src/markdown-parser.test.ts rename to packages/common/src/markdown-transformer/markdown-transformer.test.ts index 63535b936cd..d1c859fd5ff 100644 --- a/integrations/line/src/markdown-parser.test.ts +++ b/packages/common/src/markdown-transformer/markdown-transformer.test.ts @@ -1,5 +1,6 @@ import { test, expect } from 'vitest' -import { parseMarkdown } from './markdown-parser' +import { transformMarkdown, stripAllHandlers } from './markdown-transformer' +import { MarkdownHandlers } from './types' const FIXED_SIZE_SPACE_CHAR = '\u2002' // 'En space' yields better results for identation in WhatsApp messages @@ -79,9 +80,9 @@ const stripAllTests: Test = { } test.each(Object.entries(stripAllTests))( - '[SMS, MMS] Test %s', + 'Single line test %s', (_testName: string, testValues: { input: string; expected: string }): void => { - const actual = parseMarkdown(testValues.input) + const actual = transformMarkdown(testValues.input) expect(actual).toBe(testValues.expected) } ) @@ -164,6 +165,60 @@ emoji direct 😂 ` test('Multi-line multi markup test', () => { - const actual = parseMarkdown(bigInput) + const actual = transformMarkdown(bigInput) expect(actual).toBe(expectedForBigInput) }) + +test('Custom heading handler markup test', () => { + const expected = 'heading handled' + const customHandlers: MarkdownHandlers = { + ...stripAllHandlers, + heading: (_node, _visit) => { + return expected + }, + } + + const actual = transformMarkdown('# any heading`', customHandlers) + + expect(actual).toBe(expected) +}) + +test('Custom blockQuote handler markup test', () => { + const expected = 'quote handled' + const customHandlers: MarkdownHandlers = { + ...stripAllHandlers, + blockquote: (_node, _visit) => { + return expected + }, + } + + const actual = transformMarkdown('> any quote\n> 1\n> 2\n>3', customHandlers) + + expect(actual).toBe(expected) +}) + +test('Custom paragraph handler markup test', () => { + const expected = 'paragraph handled' + const customHandlers: MarkdownHandlers = { + ...stripAllHandlers, + paragraph: (_node, _visit) => expected, + } + + const actual = transformMarkdown('any paragraph', customHandlers) + + expect(actual).toBe(expected) +}) + +test('Escape character markup test', () => { + const input = '\\# not handled' + const expected = '# not handled' + const customHandlers: MarkdownHandlers = { + ...stripAllHandlers, + heading: (_node, _visit) => 'title handled', + paragraph: (_node, _visit) => _visit(_node), + } + + const actual = transformMarkdown(input, customHandlers) + + expect(actual).toBe(expected) +}) diff --git a/packages/common/src/markdown-transformer/markdown-transformer.ts b/packages/common/src/markdown-transformer/markdown-transformer.ts new file mode 100644 index 00000000000..f2879b803a0 --- /dev/null +++ b/packages/common/src/markdown-transformer/markdown-transformer.ts @@ -0,0 +1,91 @@ +import { List, Node, Table } from 'mdast' +import { remark } from 'remark' +import remarkGfm from 'remark-gfm' +import { MarkdownHandlers, NodeHandler, RootNodes } from './types' + +export const stripAllHandlers: MarkdownHandlers = { + blockquote: (node, visit) => `Quote: “${visit(node)}”\n`, + break: (_node, _visit) => '\n', + code: (node, _visit) => `${node.value}\n`, + delete: (node, visit) => `${visit(node)}`, + emphasis: (node, visit) => visit(node), + footnoteDefinition: (node, visit) => `[${node.identifier}] ${visit(node)}\n`, + footnoteReference: (node, _visit) => `[${node.identifier}]`, + heading: (node, visit) => `${visit(node)}\n`, + html: (_node, _visit) => '', + image: (node, _visit) => node.url, + inlineCode: (node, _visit) => node.value, + link: (node, _visit) => node.url, + list: (node, _visit, parents, handlers) => handleList(node, handlers, parents), + paragraph: (node, visit, parents) => `${visit(node)}${parents.at(-1)?.type === 'root' ? '\n' : ''}`, + strong: (node, visit) => visit(node), + table: (node, _visit, parents, handlers) => handleTable(node, handlers, parents), + text: (node, _visit) => node.value, + thematicBreak: (_node, _visit) => '---\n', +} + +const FIXED_SIZE_SPACE_CHAR = '\u2002' // 'En space' yields better results for identation in WhatsApp messages + +export const handleList = (listNode: List, handlers: MarkdownHandlers, parents: RootNodes[]): string => { + parents.push(listNode) + const listLevel = parents.filter((parent) => parent.type === 'list').length + let listTmp = listLevel !== 1 ? '\n' : '' + let itemCount = 0 + for (const listItemNode of listNode.children) { + itemCount++ + let prefix = FIXED_SIZE_SPACE_CHAR.repeat(listLevel - 1) + if (listItemNode.checked !== undefined && listItemNode.checked !== null) { + prefix += listItemNode.checked ? '☑︎ ' : '☐ ' + } else { + prefix += listNode.ordered ? `${itemCount}. ` : '- ' + } + const shouldBreak = listLevel === 1 || itemCount !== listNode.children.length + listTmp = `${listTmp}${prefix}${visitTree(listItemNode, handlers, parents)}${shouldBreak ? '\n' : ''}` + } + parents.pop() + return listTmp +} + +export const handleTable = (tableNode: Table, handlers: MarkdownHandlers, parents: RootNodes[]): string => { + parents.push(tableNode) + let tmpTable = '' + for (const tableRow of tableNode.children) { + let childrenCount = 0 + let tmpRow = '| ' + for (const tableCell of tableRow.children) { + tmpRow = `${tmpRow}${visitTree(tableCell, handlers, parents)}${childrenCount + 1 === tableRow.children.length ? ' |' : ' | '}` + childrenCount++ + } + tmpTable = `${tmpTable}${tmpRow}\n` + } + parents.pop() + return tmpTable +} + +const isNodeType = (s: string, handlers: MarkdownHandlers): s is keyof MarkdownHandlers => s in handlers + +export const visitTree = (tree: RootNodes, handlers: MarkdownHandlers, parents: RootNodes[]): string => { + let tmp = '' + let footnoteTmp = '' + parents.push(tree) + for (const node of tree.children) { + if (!isNodeType(node.type, handlers)) { + throw new Error(`The Markdown node type [${node.type}] is not supported`) + } + + const handler = handlers[node.type] as NodeHandler + + if (node.type === 'footnoteDefinition') { + footnoteTmp += handler(node, (n) => visitTree(n, handlers, parents), parents, handlers) + continue + } + tmp += handler(node, (n) => visitTree(n, handlers, parents), parents, handlers) + } + parents.pop() + return `${tmp}${footnoteTmp}` +} + +export const transformMarkdown = (markdown: string, handlers: MarkdownHandlers = stripAllHandlers): string => { + const tree = remark().use(remarkGfm).parse(markdown) + return visitTree(tree, handlers, []) +} diff --git a/packages/common/src/markdown-transformer/types.ts b/packages/common/src/markdown-transformer/types.ts new file mode 100644 index 00000000000..d95426bf83d --- /dev/null +++ b/packages/common/src/markdown-transformer/types.ts @@ -0,0 +1,66 @@ +import { + Blockquote, + Break, + Code, + Delete, + Emphasis, + FootnoteDefinition, + FootnoteReference, + Heading, + Html, + Image, + InlineCode, + Link, + List, + ListItem, + Node, + Paragraph, + Root, + Strong, + Table, + TableCell, + Text, + ThematicBreak, +} from 'mdast' + +export type NodeHandler = ( + node: N, + visit: (node: RootNodes) => string, + parents: RootNodes[], + handlers: MarkdownHandlers +) => string + +export type MarkdownHandlers = { + blockquote: NodeHandler
+ break: NodeHandler + code: NodeHandler + delete: NodeHandler + emphasis: NodeHandler + footnoteDefinition: NodeHandler + footnoteReference: NodeHandler + heading: NodeHandler + html: NodeHandler + image: NodeHandler + inlineCode: NodeHandler + link: NodeHandler + list: NodeHandler + paragraph: NodeHandler + strong: NodeHandler + table: NodeHandler
+ text: NodeHandler + thematicBreak: NodeHandler +} + +export type RootNodes = + | Blockquote + | Delete + | Emphasis + | FootnoteDefinition + | Heading + | List + | ListItem + | Paragraph + | Root + | Strong + | Table + | TableCell diff --git a/packages/llmz/package.json b/packages/llmz/package.json index 258085762bd..42e6e53c1f8 100644 --- a/packages/llmz/package.json +++ b/packages/llmz/package.json @@ -2,7 +2,7 @@ "name": "llmz", "type": "module", "description": "LLMz – An LLM-native Typescript VM built on top of Zui", - "version": "0.0.27", + "version": "0.0.28", "types": "./dist/index.d.ts", "main": "./dist/index.cjs", "module": "./dist/index.js", @@ -69,8 +69,8 @@ "tsx": "^4.19.2" }, "peerDependencies": { - "@botpress/client": "1.27.0", - "@botpress/cognitive": "0.2.0", + "@botpress/client": "1.27.1", + "@botpress/cognitive": "0.2.1", "@bpinternal/thicktoken": "^1.0.5", "@bpinternal/zui": "1.2.1" }, diff --git a/packages/sdk/package.json b/packages/sdk/package.json index db7620fd3db..151228d6152 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/sdk", - "version": "4.18.0", + "version": "4.18.1", "description": "Botpress SDK", "main": "./dist/index.cjs", "module": "./dist/index.mjs", @@ -19,7 +19,7 @@ "author": "", "license": "MIT", "dependencies": { - "@botpress/client": "1.27.0", + "@botpress/client": "1.27.1", "browser-or-node": "^2.1.1" }, "devDependencies": { diff --git a/packages/vai/package.json b/packages/vai/package.json index c23e017fe60..05c45801f37 100644 --- a/packages/vai/package.json +++ b/packages/vai/package.json @@ -39,7 +39,7 @@ "tsup": "^8.0.2" }, "peerDependencies": { - "@botpress/client": "1.27.0", + "@botpress/client": "1.27.1", "@bpinternal/thicktoken": "^1.0.1", "@bpinternal/zui": "1.2.1", "lodash": "^4.17.21", diff --git a/packages/zai/package.json b/packages/zai/package.json index 7c0869fbb29..c196f05ffa0 100644 --- a/packages/zai/package.json +++ b/packages/zai/package.json @@ -1,7 +1,7 @@ { "name": "@botpress/zai", "description": "Zui AI (zai) – An LLM utility library written on top of Zui and the Botpress API", - "version": "2.5.0", + "version": "2.5.1", "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { @@ -32,7 +32,7 @@ "author": "", "license": "ISC", "dependencies": { - "@botpress/cognitive": "0.2.0", + "@botpress/cognitive": "0.2.1", "json5": "^2.2.3", "jsonrepair": "^3.10.0", "lodash-es": "^4.17.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5cbebc78725..a8aefd64587 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,8 +17,8 @@ importers: specifier: ^3.564.0 version: 3.709.0 '@botpress/api': - specifier: 1.48.2 - version: 1.48.2 + specifier: 1.52.1 + version: 1.52.1(openapi-types@12.1.3) '@botpress/cli': specifier: workspace:* version: link:packages/cli @@ -1164,6 +1164,9 @@ importers: axios: specifier: ^1.6.2 version: 1.7.4 + posthog-node: + specifier: ^5.10.4 + version: 5.11.1 devDependencies: '@botpress/common': specifier: workspace:* @@ -1357,6 +1360,9 @@ importers: messaging-api-messenger: specifier: ^1.1.0 version: 1.1.0 + posthog-node: + specifier: ^5.10.4 + version: 5.11.1 devDependencies: '@botpress/common': specifier: workspace:* @@ -1771,6 +1777,9 @@ importers: '@botpress/client': specifier: workspace:* version: link:../../packages/client + '@botpress/common': + specifier: workspace:* + version: link:../../packages/common '@botpress/sdk': specifier: workspace:* version: link:../../packages/sdk @@ -1780,15 +1789,12 @@ importers: axios: specifier: ^1.6.0 version: 1.8.4 + posthog-node: + specifier: ^5.10.4 + version: 5.11.1 query-string: specifier: ^6.14.1 version: 6.14.1 - remark: - specifier: ^15.0.1 - version: 15.0.1 - remark-gfm: - specifier: ^4.0.1 - version: 4.0.1 twilio: specifier: ^3.84.0 version: 3.84.1 @@ -1796,9 +1802,6 @@ importers: '@botpress/cli': specifier: workspace:* version: link:../../packages/cli - '@botpress/common': - specifier: workspace:* - version: link:../../packages/common '@botpresshub/proactive-conversation': specifier: workspace:* version: link:../../interfaces/proactive-conversation @@ -1808,9 +1811,6 @@ importers: '@sentry/cli': specifier: ^2.39.1 version: 2.39.1 - '@types/mdast': - specifier: ^4.0.4 - version: 4.0.4 integrations/viber: dependencies: @@ -1924,7 +1924,7 @@ importers: version: 15.0.1 posthog-node: specifier: ^5.10.4 - version: 5.10.4 + version: 5.11.1 preact: specifier: ^10.26.6 version: 10.26.6 @@ -2297,10 +2297,10 @@ importers: specifier: 0.5.3 version: link:../chat-client '@botpress/client': - specifier: 1.27.0 + specifier: 1.27.1 version: link:../client '@botpress/sdk': - specifier: 4.18.0 + specifier: 4.18.1 version: link:../sdk '@bpinternal/const': specifier: ^0.1.0 @@ -2415,10 +2415,10 @@ importers: packages/cli/templates/empty-bot: dependencies: '@botpress/client': - specifier: 1.27.0 + specifier: 1.27.1 version: link:../../../client '@botpress/sdk': - specifier: 4.18.0 + specifier: 4.18.1 version: link:../../../sdk devDependencies: '@types/node': @@ -2431,10 +2431,10 @@ importers: packages/cli/templates/empty-integration: dependencies: '@botpress/client': - specifier: 1.27.0 + specifier: 1.27.1 version: link:../../../client '@botpress/sdk': - specifier: 4.18.0 + specifier: 4.18.1 version: link:../../../sdk devDependencies: '@types/node': @@ -2447,7 +2447,7 @@ importers: packages/cli/templates/empty-plugin: dependencies: '@botpress/sdk': - specifier: 4.18.0 + specifier: 4.18.1 version: link:../../../sdk devDependencies: '@types/node': @@ -2460,10 +2460,10 @@ importers: packages/cli/templates/hello-world: dependencies: '@botpress/client': - specifier: 1.27.0 + specifier: 1.27.1 version: link:../../../client '@botpress/sdk': - specifier: 4.18.0 + specifier: 4.18.1 version: link:../../../sdk devDependencies: '@types/node': @@ -2476,10 +2476,10 @@ importers: packages/cli/templates/webhook-message: dependencies: '@botpress/client': - specifier: 1.27.0 + specifier: 1.27.1 version: link:../../../client '@botpress/sdk': - specifier: 4.18.0 + specifier: 4.18.1 version: link:../../../sdk axios: specifier: ^1.6.8 @@ -2586,6 +2586,16 @@ importers: preact-render-to-string: specifier: ^6.5.13 version: 6.5.13(preact@10.26.6) + remark: + specifier: ^15.0.1 + version: 15.0.1 + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + devDependencies: + '@types/mdast': + specifier: ^4.0.4 + version: 4.0.4 packages/llmz: dependencies: @@ -2614,10 +2624,10 @@ importers: specifier: ^7.26.3 version: 7.26.9 '@botpress/client': - specifier: 1.27.0 + specifier: 1.27.1 version: link:../client '@botpress/cognitive': - specifier: 0.2.0 + specifier: 0.2.1 version: link:../cognitive '@bpinternal/thicktoken': specifier: ^1.0.5 @@ -2717,7 +2727,7 @@ importers: packages/sdk: dependencies: '@botpress/client': - specifier: 1.27.0 + specifier: 1.27.1 version: link:../client '@bpinternal/zui': specifier: 1.2.1 @@ -2748,7 +2758,7 @@ importers: packages/vai: dependencies: '@botpress/client': - specifier: 1.27.0 + specifier: 1.27.1 version: link:../client '@bpinternal/thicktoken': specifier: ^1.0.1 @@ -2791,7 +2801,7 @@ importers: packages/zai: dependencies: '@botpress/cognitive': - specifier: 0.2.0 + specifier: 0.2.1 version: link:../cognitive '@bpinternal/thicktoken': specifier: ^1.0.0 @@ -3723,8 +3733,8 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@botpress/api@1.48.2': - resolution: {integrity: sha512-jKiZcipzYq03D/uajqWIfxnFP1KLQu7YztdJEx9R9Rq7NvbwfIx3e9IMArBtEIcqO36yMsWr/E/rgHI+JPG5Rg==} + '@botpress/api@1.52.1': + resolution: {integrity: sha512-Ymf6/qrYCdRoicECp5sNPaj+76b8C43Sf+4psI1RcpNjyyPa1khGDkVW4K6C2fzaB+AhQRcVj+2P9gBrjrj4mg==} '@bpinternal/const@0.1.0': resolution: {integrity: sha512-iIQg9oYYXOt+LSK34oNhJVQTcgRdtLmLZirEUaE+R9hnmbKONA5reR2kTewxZmekGyxej+5RtDK9xrC/0hmeAw==} @@ -3750,6 +3760,10 @@ packages: resolution: {integrity: sha512-FicqYzV5+BnDO1oTyAgNut5MIqy7B3OLvAboo2o9ROQ9s6SI+kMx8rZP8OyFKJXEsEOSglZNPPIkMrmB4EvaoA==} engines: {node: '>=16.0.0', pnpm: 8.6.2} + '@bpinternal/opapi@1.0.0': + resolution: {integrity: sha512-r38k8SZSyu0s4YKukTrHaBeb8tlL8vgJiNdawnN2cmxbqkSdsss2pWnZQU2fsSLijhcfV0tUvGCf8GfgZviYBA==} + engines: {node: '>=16.0.0', pnpm: 8.6.2} + '@bpinternal/readiness@0.0.16': resolution: {integrity: sha512-VkxUYCblFVm0S39kNT/ycFmHXN/OGPuMnbOpPWPgYf/dOLJgtM0oWsW8TOs8JcO+Pt928r9vonbMFYRGYDwQpw==} engines: {node: '>=16.0.0', pnpm: 8.6.2} @@ -4904,8 +4918,8 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@posthog/core@1.4.0': - resolution: {integrity: sha512-jmW8/I//YOHAfjzokqas+Qtc2T57Ux8d2uIJu7FLcMGxywckHsl6od59CD18jtUzKToQdjQhV6Y3429qj+KeNw==} + '@posthog/core@1.5.1': + resolution: {integrity: sha512-8fdEzfvdStr45iIncTD+gnqp45UBTUpRK/bwB4shP5usCKytnPIeilU8rIpNBOVjJPwfW+2N8yWhQ0l14x191Q==} '@react-email/body@0.0.10': resolution: {integrity: sha512-dMJyL9aU25ieatdPtVjCyQ/WHZYHwNc+Hy/XpF8Cc18gu21cUynVEeYQzFSeigDRMeBQ3PGAyjVDPIob7YlGwA==} @@ -9774,8 +9788,8 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} - posthog-node@5.10.4: - resolution: {integrity: sha512-sy020/Q4mt18eCkVo/cGEJD+wgdxeg5DTgNUaA85awBCbuEP43BOdCTOOw/K2Tvgw2oZfag3PFyhkVuQ6nJfBg==} + posthog-node@5.11.1: + resolution: {integrity: sha512-P6rtzdCVvS718r011x0W0cwmJo7gfP5YWXiWh0/S3OL+pnHtcqbWHDjrtRxN/IrMkjZWzMU4xDze5vRK/cZ23w==} engines: {node: '>=20'} preact-render-to-string@6.5.13: @@ -12778,9 +12792,9 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@botpress/api@1.48.2': + '@botpress/api@1.52.1(openapi-types@12.1.3)': dependencies: - '@bpinternal/opapi': 0.16.1(openapi-types@12.1.3) + '@bpinternal/opapi': 1.0.0(openapi-types@12.1.3) transitivePeerDependencies: - debug - openapi-types @@ -12839,6 +12853,29 @@ snapshots: - openapi-types - supports-color + '@bpinternal/opapi@1.0.0(openapi-types@12.1.3)': + dependencies: + '@anatine/zod-openapi': 1.12.1(openapi3-ts@2.0.2)(zod@3.22.4) + '@readme/openapi-parser': 2.6.0(openapi-types@12.1.3) + axios: 1.7.8 + chalk: 4.1.2 + decompress: 4.2.1 + execa: 8.0.1 + json-schema-to-typescript: 13.1.2 + json-schema-to-zod: 1.1.1 + lodash: 4.17.21 + openapi-typescript: 6.7.6 + openapi3-ts: 2.0.2 + radash: 12.1.0 + tsconfig-paths: 4.2.0 + verror: 1.10.1 + winston: 3.17.0 + zod: 3.22.4 + transitivePeerDependencies: + - debug + - openapi-types + - supports-color + '@bpinternal/readiness@0.0.16': dependencies: '@aws-sdk/client-dynamodb': 3.709.0 @@ -13943,7 +13980,9 @@ snapshots: '@pkgr/core@0.2.9': {} - '@posthog/core@1.4.0': {} + '@posthog/core@1.5.1': + dependencies: + cross-spawn: 7.0.6 '@react-email/body@0.0.10(react@18.3.1)': dependencies: @@ -20128,9 +20167,9 @@ snapshots: dependencies: xtend: 4.0.2 - posthog-node@5.10.4: + posthog-node@5.11.1: dependencies: - '@posthog/core': 1.4.0 + '@posthog/core': 1.5.1 preact-render-to-string@6.5.13(preact@10.26.6): dependencies: