v2.6.0
Released: July 20, 2026
Highlights
- TelegramChannel: inline keyboard support (
@toolpack-sdk/agents) — send messages with interactive buttons viametadata.replyMarkup, handle button taps as first-classAgentInput, and dismiss loading spinners withanswerCallbackQuery() - BaseAgent: labeled delegation results (
@toolpack-sdk/agents) —delegate_to_agentnow 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 field | Type | Description |
|---|---|---|
isCallback | true | Always set on callback inputs |
callbackQueryId | string | Pass to answerCallbackQuery() to dismiss the spinner |
callbackData | string | The callback_data value from the tapped button |
chatId | number | Numeric chat ID |
userId | number | Numeric user ID of the person who tapped |
username / firstName / lastName | string | undefined | Sender identity |
messageId | number | ID 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