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
2 changes: 1 addition & 1 deletion integrations/whatsapp/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const defaultBotPhoneNumberId = {

export default new IntegrationDefinition({
name: INTEGRATION_NAME,
version: '4.5.1',
version: '4.5.2',
title: 'WhatsApp',
description: 'Send and receive messages through WhatsApp.',
icon: 'icon.svg',
Expand Down
1 change: 1 addition & 0 deletions integrations/whatsapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@botpress/sdk": "workspace:*",
"@botpress/sdk-addons": "workspace:*",
"@segment/analytics-node": "^2.2.0",
"awesome-phonenumber": "^7.5.0",
"axios": "^1.6.2",
"marked": "^15.0.1",
"preact": "^10.26.6",
Expand Down
3 changes: 2 additions & 1 deletion integrations/whatsapp/src/actions/start-conversation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RuntimeError, z } from '@botpress/sdk'
import { formatPhoneNumber } from 'src/misc/phone-number-to-whatsapp'
import { hasAtleastOne } from 'src/misc/util'
import { BodyComponent, BodyParameter, Language, Template } from 'whatsapp-api-js/messages'
import { getDefaultBotPhoneNumberId, getAuthenticatedWhatsappClient } from '../auth'
Expand Down Expand Up @@ -41,7 +42,7 @@ export const startConversation: bp.IntegrationProps['actions']['startConversatio
channel: 'channel',
tags: {
botPhoneNumberId,
userPhone,
userPhone: formatPhoneNumber(userPhone),
},
})

Expand Down
46 changes: 46 additions & 0 deletions integrations/whatsapp/src/misc/phone-number-to-whatsapp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { test, expect } from 'vitest'
import { formatPhoneNumber } from './phone-number-to-whatsapp'

test('throws on invalid phone number', () => {
expect(() => formatPhoneNumber('garbage', 'US')).toThrow('Invalid phone number')
})

test('Argentina: removes "15" prefix, trims leading zeros, and inserts +549', () => {
expect(formatPhoneNumber('(11) 15 2345-6789', 'AR')).toBe('+5491123456789')
})

test('Argentina: also strips national leading zeros even without "15"', () => {
expect(formatPhoneNumber('(011) 2345 6789', 'AR')).toBe('+5491123456789')
})

test('Mexico: ensures +521 prefix and strips leading zeros', () => {
expect(formatPhoneNumber('55 1234 5678', 'MX')).toBe('+5215512345678')
})

test('Generic leading-zero cleanup for countries other than AR/MX', () => {
expect(formatPhoneNumber('(0151 12) 34 567', 'GB')).toBe('+441511234567')
})

test('Leaves already-clean e164 untouched (non-AR/MX)', () => {
expect(formatPhoneNumber('(415) 555-2671', 'US')).toBe('+14155552671')
})

test('Simple case: adds leading + for e164 format', () => {
expect(formatPhoneNumber('416-555-0123', 'CA')).toBe('+14165550123')
})

test('Insure this phone number typing works +1 (xxx) xxx-xxxx', () => {
expect(formatPhoneNumber('+1 (581) 849-1511')).toBe('+15818491511')
})

test('Insure this phone number typing works +1 xxx xxx-xxxx', () => {
expect(formatPhoneNumber('+1 581 849-1511')).toBe('+15818491511')
})

test('Insure this phone number typing works +1 xxx xxx xxxx', () => {
expect(formatPhoneNumber('+1 581 849 1511')).toBe('+15818491511')
})

test('Insure this phone number typing works +1xxxxxxxxxx', () => {
expect(formatPhoneNumber('+15818491511')).toBe('+15818491511')
})
72 changes: 72 additions & 0 deletions integrations/whatsapp/src/misc/phone-number-to-whatsapp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { RuntimeError } from '@botpress/client'
import { parsePhoneNumber, ParsedPhoneNumber } from 'awesome-phonenumber'

const ARGENTINA_COUNTRY_CODE = 54
const ARGENTINA_COUNTRY_CODE_AFTER_PREFIX = 9
const MEXICO_COUNTRY_CODE = 52
const MEXICO_COUNTRY_CODE_AFTER_PREFIX = 1

export function formatPhoneNumber(rawPhoneNumber: string, defaultRegion: string = 'CA') {
let parsed: ParsedPhoneNumber
if (rawPhoneNumber.startsWith('+')) {
parsed = parsePhoneNumber(rawPhoneNumber)
} else {
parsed = parsePhoneNumber(rawPhoneNumber, { regionCode: defaultRegion })
}
if (!parsed?.valid) {
throw new RuntimeError('Invalid phone number')
}

let phone = parsed.number.e164
phone = _handleWhatsAppEdgeCases(phone, parsed)

return phone
}

function _handleWhatsAppEdgeCases(phone: string, parsed: ParsedPhoneNumber): string {
if (!parsed.countryCode) {
return phone
}

if (parsed.countryCode === ARGENTINA_COUNTRY_CODE) {
phone = _handleArgentinaEdgeCases(phone, parsed.countryCode)
} else if (parsed.countryCode === MEXICO_COUNTRY_CODE) {
phone = _handleMexicoEdgeCases(phone, parsed.countryCode)
} else {
let nationalNumber = _stripCountryCode(phone, parsed.countryCode)
nationalNumber = _stripLeadingZeros(nationalNumber)
phone = `+${parsed.countryCode}${nationalNumber}`
}

return phone
}

// This needs to remove leading zeros and make sure the number starts with +549
const _handleArgentinaEdgeCases = (phone: string, countryCode: number): string => {
let nationalNumber = _stripCountryCode(phone, countryCode)
nationalNumber = _stripLeadingZeros(nationalNumber)

if (nationalNumber.startsWith(ARGENTINA_COUNTRY_CODE_AFTER_PREFIX.toString())) {
return `+${ARGENTINA_COUNTRY_CODE}${nationalNumber}`
}

return `+${ARGENTINA_COUNTRY_CODE}${ARGENTINA_COUNTRY_CODE_AFTER_PREFIX}${nationalNumber}`
}

// This needs to remove leading zeros and make sure the number starts with +521
const _handleMexicoEdgeCases = (phone: string, countryCode: number): string => {
let nationalNumber = _stripCountryCode(phone, countryCode)
nationalNumber = _stripLeadingZeros(nationalNumber)
return `+${MEXICO_COUNTRY_CODE}${MEXICO_COUNTRY_CODE_AFTER_PREFIX}${nationalNumber}`
}

const _stripLeadingZeros = (phone: string): string => {
return phone.replace(/^0+/, '')
}

const _stripCountryCode = (phone: string, countryCode: number): string => {
if (phone.startsWith('+')) {
phone = phone.slice('+'.length)
}
return phone.slice(countryCode.toString().length)
}
3 changes: 2 additions & 1 deletion integrations/whatsapp/src/webhook/handlers/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { RuntimeError } from '@botpress/client'
import { ValueOf } from '@botpress/sdk/dist/utils/type-utils'
import axios from 'axios'
import { getAccessToken, getAuthenticatedWhatsappClient } from 'src/auth'
import { formatPhoneNumber } from 'src/misc/phone-number-to-whatsapp'
import { getMessageFromWhatsappMessageId } from 'src/misc/util'
import { WhatsAppMessage, WhatsAppMessageValue } from '../../misc/types'
import { getMediaInfos } from '../../misc/whatsapp-utils'
Expand Down Expand Up @@ -39,7 +40,7 @@ async function _handleIncomingMessage(
const { conversation } = await client.getOrCreateConversation({
channel: 'channel',
tags: {
userPhone: message.from,
userPhone: formatPhoneNumber(message.from),
botPhoneNumberId: value.metadata.phone_number_id,
},
})
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@botpress/cli",
"version": "4.17.19",
"version": "4.17.20",
"description": "Botpress CLI",
"scripts": {
"build": "pnpm run build:types && pnpm run bundle && pnpm run template:gen",
Expand All @@ -24,7 +24,7 @@
"@apidevtools/json-schema-ref-parser": "^11.7.0",
"@botpress/chat": "0.5.1",
"@botpress/client": "1.25.0",
"@botpress/sdk": "4.15.10",
"@botpress/sdk": "4.15.11",
"@bpinternal/const": "^0.1.0",
"@bpinternal/tunnel": "^0.1.1",
"@bpinternal/yargs-extra": "^0.0.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/chat/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as chat from '@botpress/chat'
import * as chalk from 'chalk'
import chalk from 'chalk'
import * as readline from 'readline'
import * as uuid from 'uuid'
import * as utils from '../utils'
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/empty-bot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"private": true,
"dependencies": {
"@botpress/client": "1.25.0",
"@botpress/sdk": "4.15.10"
"@botpress/sdk": "4.15.11"
},
"devDependencies": {
"@types/node": "^22.16.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/empty-integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"private": true,
"dependencies": {
"@botpress/client": "1.25.0",
"@botpress/sdk": "4.15.10"
"@botpress/sdk": "4.15.11"
},
"devDependencies": {
"@types/node": "^22.16.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/empty-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"private": true,
"dependencies": {
"@botpress/sdk": "4.15.10"
"@botpress/sdk": "4.15.11"
},
"devDependencies": {
"@types/node": "^22.16.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/hello-world/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"private": true,
"dependencies": {
"@botpress/client": "1.25.0",
"@botpress/sdk": "4.15.10"
"@botpress/sdk": "4.15.11"
},
"devDependencies": {
"@types/node": "^22.16.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/templates/webhook-message/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"private": true,
"dependencies": {
"@botpress/client": "1.25.0",
"@botpress/sdk": "4.15.10",
"@botpress/sdk": "4.15.11",
"axios": "^1.6.8"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cognitive/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@botpress/cognitive",
"version": "0.1.45",
"version": "0.1.46",
"description": "Wrapper around the Botpress Client to call LLMs",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
Expand Down
25 changes: 9 additions & 16 deletions packages/cognitive/refresh-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ import * as fs from 'fs'
import * as path from 'path'
import { Model } from 'src/schemas.gen'

const builtInModels = ['auto', 'best', 'fast', 'reasoning', 'cheapest', 'balance']
const builtInModels = ['auto', 'best', 'fast']
const filteredLifecycles = ['deprecated', 'discontinued']
const filteredTags = ['deprecated']

const modelsListPath = path.resolve(__dirname, 'src/cognitive-v2', 'models.ts')
const typesPath = path.resolve(__dirname, 'src/cognitive-v2', 'types.ts')

type RemoteModel = Model & { lifecycle?: string }
type RemoteModel = Model & { lifecycle?: string; aliases?: string[] }

const toRef = (m: RemoteModel | string | null | undefined): string | null => {
if (!m) return null
Expand All @@ -36,17 +35,6 @@ async function main(): Promise<void> {
},
})

const uniqueTags = [
...new Set(
models
.map((m) => m.tags)
.flat()
.filter((t) => !filteredTags.includes(t))
),
]

console.log(`Unique tags: ${uniqueTags.join(', ')}`)

const modelsObj = models.reduce((acc, m) => ((acc[m.id] = m), acc), {} as Record<string, RemoteModel>)

const defaultModel: RemoteModel = {
Expand All @@ -62,7 +50,6 @@ async function main(): Promise<void> {
const newFile = `import { Model } from 'src/schemas.gen'\n
export type RemoteModel = Model & { aliases?: string[]; lifecycle: 'live' | 'beta' | 'deprecated' | 'discontinued' }\n
export const models: Record<string, RemoteModel> = ${JSON.stringify(modelsObj, null, 2)}\n
export const knownTags = [${[...builtInModels, ...uniqueTags].map((t) => `'${t}'`).join(', ')}]\n
export const defaultModel: RemoteModel = ${JSON.stringify(defaultModel, undefined, 2)}
`

Expand All @@ -72,6 +59,12 @@ export const defaultModel: RemoteModel = ${JSON.stringify(defaultModel, undefine

const withoutDeprecated = models.filter((m) => !filteredLifecycles.includes(m.lifecycle))
const refs = Array.from(new Set(withoutDeprecated.map(toRef).filter(Boolean))).sort((a, b) => a.localeCompare(b))
const aliases = models.flatMap((m) =>
(m.aliases || []).map((a) => {
const [provider] = m.id.split(':')
return `${provider}:${a}`
})
)

const content = fs.readFileSync(typesPath, 'utf8')

Expand All @@ -85,7 +78,7 @@ export const defaultModel: RemoteModel = ${JSON.stringify(defaultModel, undefine
throw new Error('Could not locate Models union block in models.ts')
}

const items = [...builtInModels, ...uniqueTags, ...refs].map((r) => ` | '${r}'`)
const items = [...builtInModels, ...refs, ...aliases].map((r) => ` | '${r}'`)
const unionBlock = ['type Models =', ...items, ' | ({} & string)', ''].join('\n')

const nextContent = content.slice(0, startIdx) + unionBlock + content.slice(endIdx)
Expand Down
6 changes: 3 additions & 3 deletions packages/cognitive/src/cognitive-v2/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios, { AxiosInstance } from 'axios'
import { backOff } from 'exponential-backoff'
import { defaultModel, knownTags, models } from './models'
import { defaultModel, models } from './models'
import { CognitiveRequest, CognitiveResponse, CognitiveStreamChunk, Model } from './types'

export { CognitiveRequest, CognitiveResponse, CognitiveStreamChunk }
Expand Down Expand Up @@ -236,8 +236,8 @@ export const getCognitiveV2Model = (model: string): Model | undefined => {
return alias
}

// Special tags like auto, fast, coding don't have explicit limits so we give a default model
if (knownTags.includes(model)) {
// Special tags like auto, fast dont have explicit limits so we give a default model
if (['auto', 'fast', 'best'].includes(model)) {
return { ...defaultModel, id: model, name: model }
}
return undefined
Expand Down
Loading
Loading