Skip to main content

v2.6.0

Released: July 20, 2026

Highlights

  • TelegramChannel: inline keyboard support (@toolpack-sdk/agents) — send messages with interactive buttons via metadata.replyMarkup, handle button taps as first-class AgentInput, and dismiss loading spinners with answerCallbackQuery()
  • BaseAgent: labeled delegation results (@toolpack-sdk/agents) — delegate_to_agent now wraps sub-agent output with a [Response from <agent> — task complete] label so orchestrators clearly distinguish a finished result from a live conversation message

Breaking changes

None.


@toolpack-sdk/agents

TelegramChannel: inline keyboards

Sending buttons

Pass metadata.replyMarkup in the AgentOutput to attach an InlineKeyboardMarkup to any message. Existing callers that omit replyMarkup are unaffected.

await channel.send({
output: 'Approve this trade?',
metadata: {
chatId: 123456789,
replyMarkup: {
inline_keyboard: [[
{ text: '✅ Approve', callback_data: 'approve:abc-123' },
{ text: '❌ Reject', callback_data: 'reject:abc-123' },
]],
},
},
});

Receiving button taps

normalize() now handles callback_query updates as a distinct path. The resulting AgentInput has context.isCallback: true and the following fields:

Context fieldTypeDescription
isCallbacktrueAlways set on callback inputs
callbackQueryIdstringPass to answerCallbackQuery() to dismiss the spinner
callbackDatastringThe callback_data value from the tapped button
chatIdnumberNumeric chat ID
userIdnumberNumeric user ID of the person who tapped
username / firstName / lastNamestring | undefinedSender identity
messageIdnumberID of the message that contained the keyboard

Dismissing the loading spinner

New method answerCallbackQuery(callbackQueryId, text?) calls the Telegram answerCallbackQuery endpoint. The agent must call this after handling the tap — it is not triggered automatically.

async invokeAgent(input: AgentInput): Promise<AgentResult> {
if (input.context?.isCallback) {
const result = await this.handleTap(input.context.callbackData as string);
await (this.channels[0] as TelegramChannel).answerCallbackQuery(
input.context.callbackQueryId as string,
'Recorded.',
);
return result;
}
return this.run(input.message ?? '');
}

Throws if the Telegram API returns an error (e.g. the query has expired).


BaseAgent: delegate_to_agent results are now labeled

When delegation.mode is 'await', the tool result returned to the LLM is now prefixed with [Response from <agent> — task complete] before the sub-agent's output. This prevents the orchestrating LLM from treating a completed sub-agent result as a live conversational reply and accidentally responding to it instead of synthesising it for the end user.

Before: the raw AgentResult JSON was returned directly
After: the output field is prepended with [Response from browser — task complete]\n\n (using the actual target agent name)

No configuration change is required — the label is injected automatically by BaseAgent.


Install

npm install @toolpack-sdk/agents@2.6.0