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/anthropic/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,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: '18.0.0',
version: '18.0.1',
readme: 'hub.md',
icon: 'icon.svg',
entities: {
Expand Down
17 changes: 11 additions & 6 deletions integrations/anthropic/src/actions/generate-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,23 @@ export async function generateContent(
messages,
}

if (
// TODO: Remove this check once all 3.x models are removed,
// see https://platform.claude.com/docs/en/about-claude/models/migration-guide#breaking-changes
if (modelId === 'claude-opus-4-7') {
// This is a stricter check for Opuse 4.7 as it does not support sampling parameters
// see https://platform.claude.com/docs/en/about-claude/models/migration-guide#additional-breaking-changes
request.top_k = undefined
request.top_p = undefined
request.temperature = undefined
} else if (
(modelId === 'claude-sonnet-4-5-20250929' ||
modelId === 'claude-haiku-4-5-20251001' ||
modelId === 'claude-sonnet-4-6' ||
modelId === 'claude-opus-4-6' ||
modelId === 'claude-opus-4-7') &&
modelId === 'claude-opus-4-6') &&
request.temperature !== undefined &&
request.top_p !== undefined
) {
// TODO: Remove this check once all 3.x models are removed,
// see https://platform.claude.com/docs/en/about-claude/models/migration-guide#breaking-changes
request.top_p = undefined
request.temperature = undefined
}

const thinkingBudgetTokens = ThinkingModeBudgetTokens[input.reasoningEffort ?? 'none'] // Default to not use reasoning as Claude models use optional reasoning
Expand Down
2 changes: 1 addition & 1 deletion integrations/telegram/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { telegramMessageChannels } from './definitions/channels'

export default new IntegrationDefinition({
name: 'telegram',
version: '1.0.6',
version: '1.0.7',
title: 'Telegram',
description: 'Engage with your audience in real-time.',
icon: 'icon.svg',
Expand Down
122 changes: 79 additions & 43 deletions integrations/telegram/src/misc/message-handlers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { RuntimeError } from '@botpress/client'
import { Markup, Telegraf } from 'telegraf'
import { Markup, Telegraf, Telegram } from 'telegraf'
import { markdownHtmlToTelegramPayloads, stdMarkdownToTelegramHtml } from './markdown-to-telegram-html'
import { TelegramMessage } from './types'
import { ackMessage, getChat, mapToRuntimeErrorAndThrow, sendCard } from './utils'
import * as bp from '.botpress'

Expand All @@ -20,6 +21,52 @@ const sendHtmlTextMessage = async (
await ackMessage(message, ack)
}

type SendMediaMethod = (
chatId: number | string,
media: string,
extra?: { caption?: string }
) => Promise<TelegramMessage>

const sendContentOrFallback = async <P extends MessageHandlerProps<keyof bp.MessageProps['channel']>>({
props,
url,
send,
fallback,
}: {
props: P
url: string
send: (telegram: Telegram) => SendMediaMethod
fallback?: () => Promise<void>
}) => {
const { ctx, conversation, ack, logger, payload } = props
const client = new Telegraf(ctx.configuration.botToken)
const chat = getChat(conversation)
const sendFn = send(client.telegram)
const opts = 'caption' in payload ? { caption: payload.caption } : undefined
logger.forBot().debug(`calling ${sendFn.name} to Telegram chat ${chat}: ${url}`)
let message: TelegramMessage
try {
message = await sendFn
.call(client.telegram, chat, url, opts)
.catch(mapToRuntimeErrorAndThrow(`Failed to ${sendFn.name}`))
} catch (err) {
if (fallback) {
await fallback()
return
}
logger
.forBot()
.warn(
`Telegram could not send the media using ${sendFn.name}, sending it as a plain text link instead: ${String(err)}`
)
const text = opts?.caption ? `${opts.caption}\n${url}` : url
message = await client.telegram
.sendMessage(chat, text)
.catch(mapToRuntimeErrorAndThrow('Fail to send media link fallback'))
}
await ackMessage(message, ack)
}

export const handleTextMessage = async (props: MessageHandlerProps<'text'>) => {
const { payload, ctx, conversation, ack, logger } = props
const { text } = payload
Expand All @@ -44,54 +91,43 @@ export const handleTextMessage = async (props: MessageHandlerProps<'text'>) => {
}
}

export const handleImageMessage = async ({ payload, ctx, conversation, ack, logger }: MessageHandlerProps<'image'>) => {
const client = new Telegraf(ctx.configuration.botToken)
const chat = getChat(conversation)
logger.forBot().debug(`Sending image message to Telegram chat ${chat}`, payload.imageUrl)
const message = await client.telegram
.sendPhoto(chat, payload.imageUrl, {
caption: payload.caption,
})
.catch(mapToRuntimeErrorAndThrow('Fail to send photo'))
await ackMessage(message, ack)
export const handleImageMessage = async (props: MessageHandlerProps<'image'>) => {
await sendContentOrFallback({
props,
url: props.payload.imageUrl,
send: (t) => t.sendPhoto,
})
}

export const handleAudioMessage = async ({ payload, ctx, conversation, ack, logger }: MessageHandlerProps<'audio'>) => {
const client = new Telegraf(ctx.configuration.botToken)
const chat = getChat(conversation)
logger.forBot().debug(`Sending audio voice to Telegram chat ${chat}:`, payload.audioUrl)
try {
const message = await client.telegram
.sendVoice(chat, payload.audioUrl, { caption: payload.caption })
.catch(mapToRuntimeErrorAndThrow('Fail to send voice'))
await ackMessage(message, ack)
} catch {
// If the audio file is too large to be voice, Telegram should send it as an audio file, but if for some reason it doesn't, we can send it as an audio file
const message = await client.telegram
.sendAudio(chat, payload.audioUrl, { caption: payload.caption })
.catch(mapToRuntimeErrorAndThrow('Fail to send audio'))
await ackMessage(message, ack)
}
export const handleAudioMessage = async (props: MessageHandlerProps<'audio'>) => {
// If voice fails, retry as audio; if that also fails, fall back to a plain text link.
await sendContentOrFallback({
props,
url: props.payload.audioUrl,
send: (t) => t.sendVoice,
fallback: () =>
sendContentOrFallback({
props,
url: props.payload.audioUrl,
send: (t) => t.sendAudio,
}),
})
}

export const handleVideoMessage = async ({ payload, ctx, conversation, ack, logger }: MessageHandlerProps<'video'>) => {
const client = new Telegraf(ctx.configuration.botToken)
const chat = getChat(conversation)
logger.forBot().debug(`Sending video message to Telegram chat ${chat}:`, payload.videoUrl)
const message = await client.telegram
.sendVideo(chat, payload.videoUrl)
.catch(mapToRuntimeErrorAndThrow('Fail to send video'))
await ackMessage(message, ack)
export const handleVideoMessage = async (props: MessageHandlerProps<'video'>) => {
await sendContentOrFallback({
props,
url: props.payload.videoUrl,
send: (t) => t.sendVideo,
})
}

export const handleFileMessage = async ({ payload, ctx, conversation, ack, logger }: MessageHandlerProps<'file'>) => {
const client = new Telegraf(ctx.configuration.botToken)
const chat = getChat(conversation)
logger.forBot().debug(`Sending file message to Telegram chat ${chat}:`, payload.fileUrl)
const message = await client.telegram
.sendDocument(chat, payload.fileUrl)
.catch(mapToRuntimeErrorAndThrow('Fail to send document'))
await ackMessage(message, ack)
export const handleFileMessage = async (props: MessageHandlerProps<'file'>) => {
await sendContentOrFallback({
props,
url: props.payload.fileUrl,
send: (t) => t.sendDocument,
})
}

export const handleLocationMessage = async ({
Expand Down
6 changes: 5 additions & 1 deletion integrations/whatsapp/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ const defaultBotPhoneNumberId = {
}

export const INTEGRATION_NAME = 'whatsapp'
export const INTEGRATION_VERSION = '4.13.1'
export const INTEGRATION_VERSION = '4.14.0'
export default new IntegrationDefinition({
name: INTEGRATION_NAME,
version: INTEGRATION_VERSION,
Expand Down Expand Up @@ -342,6 +342,10 @@ export default new IntegrationDefinition({
title: 'Echo Creation Type',
description: 'For echoed messages: the creation type reported by WhatsApp (e.g. "created_by_1p_bot")',
},
status: {
title: 'Delivery Status',
description: 'Latest WhatsApp delivery status reported via webhook (SENT, DELIVERED, READ, FAILED).',
},
},
},
conversation: {
Expand Down
Loading
Loading