diff --git a/integrations/whatsapp/integration.definition.ts b/integrations/whatsapp/integration.definition.ts index df68803de82..d56911013ad 100644 --- a/integrations/whatsapp/integration.definition.ts +++ b/integrations/whatsapp/integration.definition.ts @@ -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', diff --git a/integrations/whatsapp/package.json b/integrations/whatsapp/package.json index adcea53d224..8431abb4864 100644 --- a/integrations/whatsapp/package.json +++ b/integrations/whatsapp/package.json @@ -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", diff --git a/integrations/whatsapp/src/actions/start-conversation.ts b/integrations/whatsapp/src/actions/start-conversation.ts index af2500e143a..f26d5398a7b 100644 --- a/integrations/whatsapp/src/actions/start-conversation.ts +++ b/integrations/whatsapp/src/actions/start-conversation.ts @@ -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' @@ -41,7 +42,7 @@ export const startConversation: bp.IntegrationProps['actions']['startConversatio channel: 'channel', tags: { botPhoneNumberId, - userPhone, + userPhone: formatPhoneNumber(userPhone), }, }) diff --git a/integrations/whatsapp/src/misc/phone-number-to-whatsapp.test.ts b/integrations/whatsapp/src/misc/phone-number-to-whatsapp.test.ts new file mode 100644 index 00000000000..53767273967 --- /dev/null +++ b/integrations/whatsapp/src/misc/phone-number-to-whatsapp.test.ts @@ -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') +}) diff --git a/integrations/whatsapp/src/misc/phone-number-to-whatsapp.ts b/integrations/whatsapp/src/misc/phone-number-to-whatsapp.ts new file mode 100644 index 00000000000..559ae70d5c8 --- /dev/null +++ b/integrations/whatsapp/src/misc/phone-number-to-whatsapp.ts @@ -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) +} diff --git a/integrations/whatsapp/src/webhook/handlers/messages.ts b/integrations/whatsapp/src/webhook/handlers/messages.ts index 86f1b7492f4..c0f1ac1ce9d 100644 --- a/integrations/whatsapp/src/webhook/handlers/messages.ts +++ b/integrations/whatsapp/src/webhook/handlers/messages.ts @@ -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' @@ -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, }, }) diff --git a/packages/cli/package.json b/packages/cli/package.json index a559d082d72..a8ef0f14240 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -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", @@ -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", diff --git a/packages/cli/src/chat/index.ts b/packages/cli/src/chat/index.ts index ce45393b801..cdbdb65b459 100644 --- a/packages/cli/src/chat/index.ts +++ b/packages/cli/src/chat/index.ts @@ -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' diff --git a/packages/cli/templates/empty-bot/package.json b/packages/cli/templates/empty-bot/package.json index e5369d32acc..332ec790001 100644 --- a/packages/cli/templates/empty-bot/package.json +++ b/packages/cli/templates/empty-bot/package.json @@ -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", diff --git a/packages/cli/templates/empty-integration/package.json b/packages/cli/templates/empty-integration/package.json index b47a76b5810..5b4b26912e2 100644 --- a/packages/cli/templates/empty-integration/package.json +++ b/packages/cli/templates/empty-integration/package.json @@ -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", diff --git a/packages/cli/templates/empty-plugin/package.json b/packages/cli/templates/empty-plugin/package.json index 26df9e617aa..719ce82607e 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.15.10" + "@botpress/sdk": "4.15.11" }, "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 5cb05366633..16ac7169332 100644 --- a/packages/cli/templates/hello-world/package.json +++ b/packages/cli/templates/hello-world/package.json @@ -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", diff --git a/packages/cli/templates/webhook-message/package.json b/packages/cli/templates/webhook-message/package.json index 13f325f52eb..94122c0c418 100644 --- a/packages/cli/templates/webhook-message/package.json +++ b/packages/cli/templates/webhook-message/package.json @@ -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": { diff --git a/packages/cognitive/package.json b/packages/cognitive/package.json index 7fa83c8374f..6b381ff0196 100644 --- a/packages/cognitive/package.json +++ b/packages/cognitive/package.json @@ -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", diff --git a/packages/cognitive/refresh-models.ts b/packages/cognitive/refresh-models.ts index 2e68df20f3b..513d9b80a74 100644 --- a/packages/cognitive/refresh-models.ts +++ b/packages/cognitive/refresh-models.ts @@ -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 @@ -36,17 +35,6 @@ async function main(): Promise { }, }) - 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) const defaultModel: RemoteModel = { @@ -62,7 +50,6 @@ async function main(): Promise { 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 = ${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)} ` @@ -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') @@ -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) diff --git a/packages/cognitive/src/cognitive-v2/index.ts b/packages/cognitive/src/cognitive-v2/index.ts index f1ea57f44cb..791fd08137d 100644 --- a/packages/cognitive/src/cognitive-v2/index.ts +++ b/packages/cognitive/src/cognitive-v2/index.ts @@ -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 } @@ -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 diff --git a/packages/cognitive/src/cognitive-v2/models.ts b/packages/cognitive/src/cognitive-v2/models.ts index 4f1d6987665..ff300b03f24 100644 --- a/packages/cognitive/src/cognitive-v2/models.ts +++ b/packages/cognitive/src/cognitive-v2/models.ts @@ -18,6 +18,7 @@ export const models: Record = { }, tags: ['recommended', 'reasoning', 'general-purpose'], lifecycle: 'live', + aliases: ['gpt-5'], }, 'openai:gpt-5-mini-2025-08-07': { id: 'openai:gpt-5-mini-2025-08-07', @@ -34,6 +35,7 @@ export const models: Record = { }, tags: ['recommended', 'reasoning', 'general-purpose'], lifecycle: 'live', + aliases: ['gpt-5-mini'], }, 'openai:gpt-5-nano-2025-08-07': { id: 'openai:gpt-5-nano-2025-08-07', @@ -50,6 +52,7 @@ export const models: Record = { }, tags: ['low-cost', 'reasoning', 'general-purpose'], lifecycle: 'live', + aliases: ['gpt-5-nano'], }, 'openai:o4-mini-2025-04-16': { id: 'openai:o4-mini-2025-04-16', @@ -66,6 +69,7 @@ export const models: Record = { }, tags: ['reasoning', 'vision', 'coding'], lifecycle: 'live', + aliases: ['o4-mini'], }, 'openai:o3-2025-04-16': { id: 'openai:o3-2025-04-16', @@ -82,6 +86,7 @@ export const models: Record = { }, tags: ['reasoning', 'vision', 'coding'], lifecycle: 'live', + aliases: ['o3'], }, 'openai:gpt-4.1-2025-04-14': { id: 'openai:gpt-4.1-2025-04-14', @@ -98,6 +103,7 @@ export const models: Record = { }, tags: ['recommended', 'vision', 'general-purpose'], lifecycle: 'live', + aliases: ['gpt-4.1'], }, 'openai:gpt-4.1-mini-2025-04-14': { id: 'openai:gpt-4.1-mini-2025-04-14', @@ -114,6 +120,7 @@ export const models: Record = { }, tags: ['recommended', 'vision', 'general-purpose'], lifecycle: 'live', + aliases: ['gpt-4.1-mini'], }, 'openai:gpt-4.1-nano-2025-04-14': { id: 'openai:gpt-4.1-nano-2025-04-14', @@ -129,6 +136,7 @@ export const models: Record = { }, tags: ['low-cost', 'vision', 'general-purpose'], lifecycle: 'live', + aliases: ['gpt-4.1-nano'], }, 'openai:o3-mini-2025-01-31': { id: 'openai:o3-mini-2025-01-31', @@ -145,6 +153,7 @@ export const models: Record = { }, tags: ['reasoning', 'general-purpose', 'coding'], lifecycle: 'live', + aliases: ['o3-mini'], }, 'openai:o1-2024-12-17': { id: 'openai:o1-2024-12-17', @@ -177,6 +186,7 @@ export const models: Record = { }, tags: ['reasoning', 'vision', 'general-purpose'], lifecycle: 'live', + aliases: ['o1-mini'], }, 'openai:gpt-4o-mini-2024-07-18': { id: 'openai:gpt-4o-mini-2024-07-18', @@ -193,6 +203,7 @@ export const models: Record = { }, tags: ['recommended', 'vision', 'low-cost', 'general-purpose', 'function-calling'], lifecycle: 'live', + aliases: ['gpt-4o-mini'], }, 'openai:gpt-4o-2024-11-20': { id: 'openai:gpt-4o-2024-11-20', @@ -209,6 +220,7 @@ export const models: Record = { }, tags: ['recommended', 'vision', 'general-purpose', 'coding', 'agents', 'function-calling'], lifecycle: 'live', + aliases: ['gpt-4o'], }, 'openai:gpt-4o-2024-08-06': { id: 'openai:gpt-4o-2024-08-06', @@ -274,6 +286,23 @@ export const models: Record = { tags: ['deprecated', 'general-purpose', 'low-cost'], lifecycle: 'deprecated', }, + 'anthropic:claude-sonnet-4-5-2025092': { + id: 'anthropic:claude-sonnet-4-5-2025092', + name: 'Claude Sonnet 4.5', + description: + "Claude Sonnet 4.5 is Anthropic's most advanced Sonnet model to date, optimized for real-world agents and coding workflows. It delivers state-of-the-art performance on coding benchmarks, with improvements across system design, code security, and specification adherence.", + input: { + maxTokens: 200000, + costPer1MTokens: 3, + }, + output: { + maxTokens: 64000, + costPer1MTokens: 15, + }, + tags: ['recommended', 'reasoning', 'agents', 'vision', 'general-purpose', 'coding'], + lifecycle: 'live', + aliases: ['claude-sonnet-4-5'], + }, 'anthropic:claude-sonnet-4-20250514': { id: 'anthropic:claude-sonnet-4-20250514', name: 'Claude Sonnet 4', @@ -289,6 +318,7 @@ export const models: Record = { }, tags: ['recommended', 'reasoning', 'agents', 'vision', 'general-purpose', 'coding'], lifecycle: 'live', + aliases: ['claude-sonnet-4'], }, 'anthropic:claude-sonnet-4-reasoning-20250514': { id: 'anthropic:claude-sonnet-4-reasoning-20250514', @@ -305,6 +335,7 @@ export const models: Record = { }, tags: ['deprecated', 'vision', 'reasoning', 'general-purpose', 'agents', 'coding'], lifecycle: 'deprecated', + aliases: ['claude-sonnet-4-reasoning'], }, 'anthropic:claude-3-7-sonnet-20250219': { id: 'anthropic:claude-3-7-sonnet-20250219', @@ -530,8 +561,8 @@ export const models: Record = { tags: ['general-purpose'], lifecycle: 'live', }, - 'groq:openai/gpt-oss-20b': { - id: 'groq:openai/gpt-oss-20b', + 'groq:gpt-oss-20b': { + id: 'groq:gpt-oss-20b', name: 'GPT-OSS 20B (Preview)', description: 'gpt-oss-20b is a compact, open-weight language model optimized for low-latency. It shares the same training foundation and capabilities as the GPT-OSS 120B model, with faster responses and lower cost.', @@ -545,9 +576,10 @@ export const models: Record = { }, tags: ['preview', 'general-purpose', 'reasoning', 'low-cost'], lifecycle: 'live', + aliases: ['openai/gpt-oss-20b'], }, - 'groq:openai/gpt-oss-120b': { - id: 'groq:openai/gpt-oss-120b', + 'groq:gpt-oss-120b': { + id: 'groq:gpt-oss-120b', name: 'GPT-OSS 120B (Preview)', description: 'gpt-oss-120b is a high-performance, open-weight language model designed for production-grade, general-purpose use cases. It excels at complex reasoning and supports configurable reasoning effort, full chain-of-thought transparency for easier debugging and trust, and native agentic capabilities for function calling, tool use, and structured outputs.', @@ -561,6 +593,7 @@ export const models: Record = { }, tags: ['preview', 'general-purpose', 'reasoning'], lifecycle: 'live', + aliases: ['openai/gpt-oss-120b'], }, 'groq:deepseek-r1-distill-llama-70b': { id: 'groq:deepseek-r1-distill-llama-70b', @@ -960,7 +993,7 @@ export const models: Record = { costPer1MTokens: 8, }, tags: ['reasoning', 'general-purpose', 'coding'], - lifecycle: 'live', + lifecycle: 'deprecated', aliases: ['accounts/fireworks/models/deepseek-r1'], }, 'fireworks-ai:deepseek-r1-basic': { @@ -976,8 +1009,8 @@ export const models: Record = { maxTokens: 32768, costPer1MTokens: 2.19, }, - tags: ['recommended', 'reasoning', 'general-purpose', 'coding'], - lifecycle: 'live', + tags: ['reasoning', 'general-purpose', 'coding'], + lifecycle: 'deprecated', aliases: ['accounts/fireworks/models/deepseek-r1-basic'], }, 'fireworks-ai:deepseek-v3': { @@ -1061,8 +1094,8 @@ export const models: Record = { maxTokens: 65536, costPer1MTokens: 1.2, }, - tags: ['general-purpose'], - lifecycle: 'live', + tags: ['deprecated', 'general-purpose'], + lifecycle: 'deprecated', aliases: ['accounts/fireworks/models/mixtral-8x22b-instruct'], }, 'fireworks-ai:mixtral-8x7b-instruct': { @@ -1118,26 +1151,6 @@ export const models: Record = { }, } -export const knownTags = [ - 'auto', - 'best', - 'fast', - 'reasoning', - 'cheapest', - 'balance', - 'recommended', - 'reasoning', - 'general-purpose', - 'low-cost', - 'vision', - 'coding', - 'function-calling', - 'agents', - 'storytelling', - 'preview', - 'roleplay', -] - export const defaultModel: RemoteModel = { id: '', name: '', diff --git a/packages/cognitive/src/cognitive-v2/types.ts b/packages/cognitive/src/cognitive-v2/types.ts index 7a16de01ace..0b976115705 100644 --- a/packages/cognitive/src/cognitive-v2/types.ts +++ b/packages/cognitive/src/cognitive-v2/types.ts @@ -8,14 +8,13 @@ export type Models = | 'anthropic:claude-3-7-sonnet-20250219' | 'anthropic:claude-3-haiku-20240307' | 'anthropic:claude-sonnet-4-20250514' + | 'anthropic:claude-sonnet-4-5-2025092' | 'cerebras:gpt-oss-120b' | 'cerebras:llama-4-scout-17b-16e-instruct' | 'cerebras:llama3.1-8b' | 'cerebras:llama3.3-70b' | 'cerebras:qwen-3-32b' - | 'fireworks-ai:deepseek-r1' | 'fireworks-ai:deepseek-r1-0528' - | 'fireworks-ai:deepseek-r1-basic' | 'fireworks-ai:deepseek-v3-0324' | 'fireworks-ai:gpt-oss-120b' | 'fireworks-ai:gpt-oss-20b' @@ -23,7 +22,6 @@ export type Models = | 'fireworks-ai:llama-v3p3-70b-instruct' | 'fireworks-ai:llama4-maverick-instruct-basic' | 'fireworks-ai:llama4-scout-instruct-basic' - | 'fireworks-ai:mixtral-8x22b-instruct' | 'fireworks-ai:mixtral-8x7b-instruct' | 'fireworks-ai:mythomax-l2-13b' | 'google-ai:gemini-2.5-flash' @@ -31,10 +29,10 @@ export type Models = | 'google-ai:models/gemini-2.0-flash' | 'groq:deepseek-r1-distill-llama-70b' | 'groq:gemma2-9b-it' + | 'groq:gpt-oss-120b' + | 'groq:gpt-oss-20b' | 'groq:llama-3.1-8b-instant' | 'groq:llama-3.3-70b-versatile' - | 'groq:openai/gpt-oss-120b' - | 'groq:openai/gpt-oss-20b' | 'openai:gpt-4.1-2025-04-14' | 'openai:gpt-4.1-mini-2025-04-14' | 'openai:gpt-4.1-nano-2025-04-14' @@ -55,8 +53,41 @@ export type Models = | 'xai:grok-4-fast-non-reasoning' | 'xai:grok-4-fast-reasoning' | 'xai:grok-code-fast-1' + | 'openai:gpt-5' + | 'openai:gpt-5-mini' + | 'openai:gpt-5-nano' + | 'openai:o4-mini' + | 'openai:o3' + | 'openai:gpt-4.1' + | 'openai:gpt-4.1-mini' + | 'openai:gpt-4.1-nano' + | 'openai:o3-mini' + | 'openai:o1-mini' + | 'openai:gpt-4o-mini' + | 'openai:gpt-4o' + | 'anthropic:claude-sonnet-4-5' + | 'anthropic:claude-sonnet-4' + | 'anthropic:claude-sonnet-4-reasoning' + | 'groq:openai/gpt-oss-20b' + | 'groq:openai/gpt-oss-120b' + | 'fireworks-ai:accounts/fireworks/models/gpt-oss-20b' + | 'fireworks-ai:accounts/fireworks/models/gpt-oss-120b' + | 'fireworks-ai:accounts/fireworks/models/deepseek-r1-0528' + | 'fireworks-ai:accounts/fireworks/models/deepseek-v3-0324' + | 'fireworks-ai:accounts/fireworks/models/llama4-maverick-instruct-basic' + | 'fireworks-ai:accounts/fireworks/models/llama4-scout-instruct-basic' + | 'fireworks-ai:accounts/fireworks/models/llama-v3p3-70b-instruct' + | 'fireworks-ai:accounts/fireworks/models/deepseek-r1' + | 'fireworks-ai:accounts/fireworks/models/deepseek-r1-basic' + | 'fireworks-ai:accounts/fireworks/models/deepseek-v3' + | 'fireworks-ai:accounts/fireworks/models/llama-v3p1-405b-instruct' + | 'fireworks-ai:accounts/fireworks/models/llama-v3p1-70b-instruct' + | 'fireworks-ai:accounts/fireworks/models/llama-v3p1-8b-instruct' + | 'fireworks-ai:accounts/fireworks/models/mixtral-8x22b-instruct' + | 'fireworks-ai:accounts/fireworks/models/mixtral-8x7b-instruct' + | 'fireworks-ai:accounts/fireworks/models/mythomax-l2-13b' + | 'fireworks-ai:accounts/fireworks/models/gemma2-9b-it' | ({} & string) - export type CognitiveRequest = { /** * @minItems 1 @@ -127,8 +158,10 @@ export type CognitiveStreamChunk = { } } } + export type CognitiveResponse = { output: string + reasoning?: string metadata: { provider: string model?: string @@ -147,7 +180,7 @@ export type CognitiveResponse = { stopReason?: 'stop' | 'length' | 'content_filter' | 'error' reasoningEffort?: string warnings?: { - type: 'parameter_ignored' | 'provider_limitation' | 'deprecated_model' | 'fallback_used' + type: 'parameter_ignored' | 'provider_limitation' | 'deprecated_model' | 'discontinued_model' | 'fallback_used' message: string }[] /** @@ -160,6 +193,7 @@ export type CognitiveResponse = { } error?: string } + export type Model = { id: string name: string diff --git a/packages/llmz/package.json b/packages/llmz/package.json index 27d20c56b96..a338155ae4c 100644 --- a/packages/llmz/package.json +++ b/packages/llmz/package.json @@ -67,7 +67,7 @@ }, "peerDependencies": { "@botpress/client": "^1.25.0", - "@botpress/cognitive": "^0.1.45", + "@botpress/cognitive": "0.1.46", "@bpinternal/thicktoken": "^1.0.5", "@bpinternal/zui": "^1.0.1" }, diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 7589cc8e304..b02d79ba83a 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/sdk", - "version": "4.15.10", + "version": "4.15.11", "description": "Botpress SDK", "main": "./dist/index.cjs", "module": "./dist/index.mjs", diff --git a/packages/sdk/src/bot/server/workflows/update-handler.ts b/packages/sdk/src/bot/server/workflows/update-handler.ts index 95b14b2f0c9..9fa669e05bc 100644 --- a/packages/sdk/src/bot/server/workflows/update-handler.ts +++ b/packages/sdk/src/bot/server/workflows/update-handler.ts @@ -68,9 +68,9 @@ const _handleWorkflowUpdate = async (props: types.ServerProps, event: types.Work return SUCCESS_RESPONSE } - await _dispatchToHandlers(props, event) + const { updatedWorkflow } = await _dispatchToHandlers(props, event) - if (event.payload.workflow.status === 'pending') { + if (updatedWorkflow.status === 'pending') { props.logger.warn( `Workflow "${event.payload.workflow.name}" is still in pending status after processing "${updateType}" event. ` + 'This may indicate that the workflow was not properly acknowledged or terminated by the handler. ' @@ -80,18 +80,34 @@ const _handleWorkflowUpdate = async (props: types.ServerProps, event: types.Work return SUCCESS_RESPONSE } -const _dispatchToHandlers = async (props: types.ServerProps, event: types.WorkflowUpdateEvent): Promise => { +type WorkflowState = types.WorkflowUpdateEventPayload['workflow'] + +const _dispatchToHandlers = async ( + props: types.ServerProps, + event: types.WorkflowUpdateEvent +): Promise<{ updatedWorkflow: WorkflowState }> => { const updateType = bridgeUpdateTypeToSnakeCase(event.payload.type) const handlers = props.self.workflowHandlers[updateType]?.[event.payload.workflow.name] + let currentWorkflowState: WorkflowState = structuredClone(event.payload.workflow) + for (const handler of handlers!) { await handler({ ...props, event, conversation: event.payload.conversation, user: event.payload.user, - workflow: wrapWorkflowInstance({ ...props, workflow: event.payload.workflow, event }), + workflow: wrapWorkflowInstance({ + ...props, + workflow: currentWorkflowState, + event, + onWorkflowUpdate(newState) { + currentWorkflowState = newState + }, + }), workflows: proxyWorkflows(props.client), }) } + + return { updatedWorkflow: currentWorkflowState } } diff --git a/packages/sdk/src/bot/workflow-proxy/proxy.ts b/packages/sdk/src/bot/workflow-proxy/proxy.ts index 01dc777294a..7083ff4d9ae 100644 --- a/packages/sdk/src/bot/workflow-proxy/proxy.ts +++ b/packages/sdk/src/bot/workflow-proxy/proxy.ts @@ -69,6 +69,7 @@ export const wrapWorkflowInstance = < client: BotSpecificClient | client.Client workflow: client.Workflow event?: botServerTypes.WorkflowUpdateEvent + onWorkflowUpdate?: (newState: client.Workflow) => Promise | void }): WorkflowWithUtilities => { let isAcknowledged = false @@ -77,16 +78,28 @@ export const wrapWorkflowInstance = < async update(x) { const { workflow } = await props.client.updateWorkflow({ id: props.workflow.id, ...x }) - return { workflow: wrapWorkflowInstance({ client: props.client, workflow }) } + await props.onWorkflowUpdate?.(workflow) + + return { workflow: wrapWorkflowInstance({ ...props, workflow }) } }, async acknowledgeStartOfProcessing() { if (!props.event || props.workflow.status !== 'pending' || isAcknowledged) { - return + return { + workflow: wrapWorkflowInstance(props), + } } - await props.client.updateWorkflow({ id: props.workflow.id, status: 'in_progress', eventId: props.event.id }) + const { workflow } = await props.client.updateWorkflow({ + id: props.workflow.id, + status: 'in_progress', + eventId: props.event.id, + }) isAcknowledged = true + + await props.onWorkflowUpdate?.(workflow) + + return { workflow: wrapWorkflowInstance({ ...props, workflow }) } }, async setFailed({ failureReason }) { @@ -95,17 +108,24 @@ export const wrapWorkflowInstance = < status: 'failed', failureReason, }) - return { workflow: wrapWorkflowInstance({ client: props.client, workflow }) } + + await props.onWorkflowUpdate?.(workflow) + + return { workflow: wrapWorkflowInstance({ ...props, workflow }) } }, async setCompleted({ output } = {}) { const { workflow } = await props.client.updateWorkflow({ id: props.workflow.id, status: 'completed', output }) - return { workflow: wrapWorkflowInstance({ client: props.client, workflow }) } + await props.onWorkflowUpdate?.(workflow) + + return { workflow: wrapWorkflowInstance({ ...props, workflow }) } }, async cancel() { const { workflow } = await props.client.updateWorkflow({ id: props.workflow.id, status: 'cancelled' }) - return { workflow: wrapWorkflowInstance({ client: props.client, workflow }) } + await props.onWorkflowUpdate?.(workflow) + + return { workflow: wrapWorkflowInstance({ ...props, workflow }) } }, } } diff --git a/packages/sdk/src/bot/workflow-proxy/types.ts b/packages/sdk/src/bot/workflow-proxy/types.ts index a84dfb443c5..d9453c7f3a5 100644 --- a/packages/sdk/src/bot/workflow-proxy/types.ts +++ b/packages/sdk/src/bot/workflow-proxy/types.ts @@ -81,7 +81,7 @@ export type WorkflowWithUtilities< * Should a workflow not be acknowledged **in a timely fashion**, it will be * retriggered 3 times before being marked as failed. */ - acknowledgeStartOfProcessing(): Promise + acknowledgeStartOfProcessing(): Promise<{ workflow: WorkflowWithUtilities }> /** * Marks the current workflow instance as failed and stops execution diff --git a/packages/zai/package.json b/packages/zai/package.json index f188a2d87d4..1d307532166 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.1.14", + "version": "2.1.15", "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { @@ -29,7 +29,7 @@ "author": "", "license": "ISC", "dependencies": { - "@botpress/cognitive": "0.1.45", + "@botpress/cognitive": "0.1.46", "json5": "^2.2.3", "jsonrepair": "^3.10.0", "lodash-es": "^4.17.21" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b203e5afd87..cc845bf4588 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1795,6 +1795,9 @@ importers: '@segment/analytics-node': specifier: ^2.2.0 version: 2.2.0 + awesome-phonenumber: + specifier: ^7.5.0 + version: 7.5.0 axios: specifier: ^1.6.2 version: 1.6.8 @@ -2163,7 +2166,7 @@ importers: specifier: 1.25.0 version: link:../client '@botpress/sdk': - specifier: 4.15.10 + specifier: 4.15.11 version: link:../sdk '@bpinternal/const': specifier: ^0.1.0 @@ -2278,7 +2281,7 @@ importers: specifier: 1.25.0 version: link:../../../client '@botpress/sdk': - specifier: 4.15.10 + specifier: 4.15.11 version: link:../../../sdk devDependencies: '@types/node': @@ -2294,7 +2297,7 @@ importers: specifier: 1.25.0 version: link:../../../client '@botpress/sdk': - specifier: 4.15.10 + specifier: 4.15.11 version: link:../../../sdk devDependencies: '@types/node': @@ -2307,7 +2310,7 @@ importers: packages/cli/templates/empty-plugin: dependencies: '@botpress/sdk': - specifier: 4.15.10 + specifier: 4.15.11 version: link:../../../sdk devDependencies: '@types/node': @@ -2323,7 +2326,7 @@ importers: specifier: 1.25.0 version: link:../../../client '@botpress/sdk': - specifier: 4.15.10 + specifier: 4.15.11 version: link:../../../sdk devDependencies: '@types/node': @@ -2339,7 +2342,7 @@ importers: specifier: 1.25.0 version: link:../../../client '@botpress/sdk': - specifier: 4.15.10 + specifier: 4.15.11 version: link:../../../sdk axios: specifier: ^1.6.8 @@ -2477,7 +2480,7 @@ importers: specifier: ^1.25.0 version: link:../client '@botpress/cognitive': - specifier: ^0.1.45 + specifier: 0.1.46 version: link:../cognitive '@bpinternal/thicktoken': specifier: ^1.0.5 @@ -2651,7 +2654,7 @@ importers: packages/zai: dependencies: '@botpress/cognitive': - specifier: 0.1.45 + specifier: 0.1.46 version: link:../cognitive '@bpinternal/thicktoken': specifier: ^1.0.0 @@ -6162,6 +6165,10 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + awesome-phonenumber@7.5.0: + resolution: {integrity: sha512-qWQHCiMHMNVyUsICiCcaPJAYbGL4E4kGnIxnrwkS3YCwcPa8VtQCo11HGKY8AbUpXJloDG10c9Bulhw3xYkC/g==} + engines: {node: '>=18'} + aws-sign2@0.7.0: resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} @@ -7749,6 +7756,10 @@ packages: resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} engines: {node: '>=14'} + google-libphonenumber@3.2.43: + resolution: {integrity: sha512-TbIX/UC3BFRJwCxbBeCPwuRC4Qws9Jz/CECmfTM1t9RFoI3X6eRThurv6AYr9wSrt640IA9KFIHuAD/vlyjqRw==} + engines: {node: '>=0.10'} + google-logging-utils@0.0.2: resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==} engines: {node: '>=14'} @@ -15437,6 +15448,8 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 + awesome-phonenumber@7.5.0: {} + aws-sign2@0.7.0: {} aws4@1.12.0: {} @@ -17525,6 +17538,8 @@ snapshots: - encoding - supports-color + google-libphonenumber@3.2.43: {} + google-logging-utils@0.0.2: {} google-p12-pem@4.0.1: