experimental-ash 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/docs/public/channels/README.md +50 -2
  3. package/dist/docs/public/channels/discord.md +15 -7
  4. package/dist/docs/public/channels/teams.md +121 -0
  5. package/dist/docs/public/channels/telegram.md +201 -0
  6. package/dist/docs/public/typescript-api.md +41 -1
  7. package/dist/src/internal/application/package.js +1 -1
  8. package/dist/src/public/channels/teams/api.d.ts +140 -0
  9. package/dist/src/public/channels/teams/api.js +4 -0
  10. package/dist/src/public/channels/teams/attachments.d.ts +25 -0
  11. package/dist/src/public/channels/teams/attachments.js +1 -0
  12. package/dist/src/public/channels/teams/defaults.d.ts +24 -0
  13. package/dist/src/public/channels/teams/defaults.js +3 -0
  14. package/dist/src/public/channels/teams/hitl.d.ts +36 -0
  15. package/dist/src/public/channels/teams/hitl.js +1 -0
  16. package/dist/src/public/channels/teams/inbound.d.ts +80 -0
  17. package/dist/src/public/channels/teams/inbound.js +3 -0
  18. package/dist/src/public/channels/teams/index.d.ts +8 -0
  19. package/dist/src/public/channels/teams/index.js +1 -0
  20. package/dist/src/public/channels/teams/limits.d.ts +8 -0
  21. package/dist/src/public/channels/teams/limits.js +1 -0
  22. package/dist/src/public/channels/teams/teamsChannel.d.ts +162 -0
  23. package/dist/src/public/channels/teams/teamsChannel.js +1 -0
  24. package/dist/src/public/channels/teams/verify.d.ts +43 -0
  25. package/dist/src/public/channels/teams/verify.js +1 -0
  26. package/dist/src/public/channels/telegram/api.d.ts +107 -0
  27. package/dist/src/public/channels/telegram/api.js +2 -0
  28. package/dist/src/public/channels/telegram/attachments.d.ts +23 -0
  29. package/dist/src/public/channels/telegram/attachments.js +1 -0
  30. package/dist/src/public/channels/telegram/defaults.d.ts +9 -0
  31. package/dist/src/public/channels/telegram/defaults.js +3 -0
  32. package/dist/src/public/channels/telegram/hitl.d.ts +44 -0
  33. package/dist/src/public/channels/telegram/hitl.js +1 -0
  34. package/dist/src/public/channels/telegram/inbound.d.ts +88 -0
  35. package/dist/src/public/channels/telegram/inbound.js +2 -0
  36. package/dist/src/public/channels/telegram/index.d.ts +8 -0
  37. package/dist/src/public/channels/telegram/index.js +1 -0
  38. package/dist/src/public/channels/telegram/telegramChannel.d.ts +126 -0
  39. package/dist/src/public/channels/telegram/telegramChannel.js +1 -0
  40. package/dist/src/public/channels/telegram/verify.d.ts +30 -0
  41. package/dist/src/public/channels/telegram/verify.js +1 -0
  42. package/package.json +11 -1
@@ -0,0 +1,23 @@
1
+ import type { FilePart, UserContent } from "ai";
2
+ import type { FetchFileResult } from "#channel/adapter.js";
3
+ import { type TelegramApiOptions } from "#public/channels/telegram/api.js";
4
+ import type { TelegramAttachment, TelegramMessage } from "#public/channels/telegram/inbound.js";
5
+ import { type UploadPolicy } from "#public/channels/upload-policy.js";
6
+ /** URL protocol used by the channel to defer Telegram file downloads to `fetchFile`. */
7
+ export declare const TELEGRAM_FILE_URL_PROTOCOL = "telegram-file:";
8
+ /** Emits one {@link FilePart} per supported Telegram attachment. */
9
+ export declare function collectTelegramFileParts(attachments: readonly TelegramAttachment[], policy: UploadPolicy): FilePart[];
10
+ /** Combines text/caption + file parts into the {@link UserContent} shape the harness expects. */
11
+ export declare function buildTelegramTurnMessage(message: Pick<TelegramMessage, "caption" | "text">, fileParts: readonly FilePart[]): string | UserContent;
12
+ /** Creates a `fetchFile` function for Telegram-owned file URLs. */
13
+ export declare function createTelegramFetchFile(input: {
14
+ readonly api?: Omit<TelegramApiOptions, "credentials">;
15
+ readonly credentials?: TelegramApiOptions["credentials"];
16
+ readonly policy: UploadPolicy;
17
+ }): (url: string) => Promise<FetchFileResult | null>;
18
+ /** Builds the internal URL used to defer a Telegram file download. */
19
+ export declare function createTelegramFileUrl(input: {
20
+ readonly fileId: string;
21
+ readonly filename?: string;
22
+ readonly mediaType?: string;
23
+ }): URL;
@@ -0,0 +1 @@
1
+ import{createLogger}from"#internal/logging.js";import{evaluateFilePart,formatUploadPolicyViolation,isMediaTypeAllowed,isUploadsDisabled}from"#public/channels/upload-policy.js";import{downloadTelegramFile,getTelegramFile}from"#public/channels/telegram/api.js";var log=createLogger(`telegram.attachments`),TELEGRAM_FILE_URL_PROTOCOL=`telegram-file:`;function collectTelegramFileParts(e,t){if(isUploadsDisabled(t))return[];let r=[];for(let i of e){let e=toTelegramFilePart(i,r.length);if(e===null)continue;let a=evaluateTelegramFilePart(e,i.size,t);if(a!==null){log.warn(`dropped attachment — ${formatUploadPolicyViolation(a)}`,{fileId:i.fileId,name:i.fileName});continue}r.push(e)}return r}function buildTelegramTurnMessage(e,t){let n=e.text||e.caption;return t.length===0?n:n.trim().length===0?[...t]:[{type:`text`,text:n},...t]}function createTelegramFetchFile(e){return async r=>{let i=parseTelegramFileUrl(r);if(i===null)return null;let o=await getTelegramFile({apiBaseUrl:e.api?.apiBaseUrl,credentials:e.credentials,fetch:e.api?.fetch,fileId:i.fileId}),s=await downloadTelegramFile({apiBaseUrl:e.api?.apiBaseUrl,credentials:e.credentials,fetch:e.api?.fetch,fileBaseUrl:e.api?.fileBaseUrl,filePath:o.filePath});if(!s.ok)throw Error(`Telegram file fetch returned HTTP ${s.status} for ${r}.`);let c=Buffer.from(await s.arrayBuffer()),l=s.headers.get(`content-type`)??i.mediaType??`application/octet-stream`,u={bytes:c,filename:i.filename,mediaType:l},d=evaluateFilePart({data:u.bytes,filename:u.filename,mediaType:l,type:`file`},e.policy);if(d!==null)throw Error(`Telegram file rejected — ${formatUploadPolicyViolation(d)}`);return u}}function createTelegramFileUrl(e){let t=new URLSearchParams;e.filename!==void 0&&t.set(`filename`,e.filename),e.mediaType!==void 0&&t.set(`mediaType`,e.mediaType);let n=t.size>0?`?${t.toString()}`:``;return new URL(`${TELEGRAM_FILE_URL_PROTOCOL}${encodeURIComponent(e.fileId)}${n}`)}function toTelegramFilePart(e,t){let n=e.mediaType??(e.kind===`photo`?`image/jpeg`:`application/octet-stream`),r=e.fileName??(e.kind===`photo`?`photo-${t}.jpg`:`file-${t}`);return{data:createTelegramFileUrl({fileId:e.fileId,filename:r,mediaType:n}),filename:r,mediaType:n,type:`file`}}function parseTelegramFileUrl(e){let t;try{t=new URL(e)}catch{return null}if(t.protocol!==`telegram-file:`)return null;let n=decodeURIComponent(t.pathname);return n?{fileId:n,filename:t.searchParams.get(`filename`)??void 0,mediaType:t.searchParams.get(`mediaType`)??void 0}:null}function evaluateTelegramFilePart(e,n,i){return i===`disabled`||!isMediaTypeAllowed(e.mediaType,i)?evaluateFilePart(e,i):n!==void 0&&n>i.maxBytes?{byteLength:n,filename:e.filename,kind:`too-large`,limit:i.maxBytes,mediaType:e.mediaType}:null}export{TELEGRAM_FILE_URL_PROTOCOL,buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile,createTelegramFileUrl};
@@ -0,0 +1,9 @@
1
+ import type { SessionAuthContext } from "#channel/types.js";
2
+ import type { TelegramMessage } from "#public/channels/telegram/inbound.js";
3
+ import type { TelegramChannelEvents, TelegramContext, TelegramInboundResult } from "#public/channels/telegram/telegramChannel.js";
4
+ /** Default auth projection for Telegram webhook actors. */
5
+ export declare function defaultTelegramAuth(message: TelegramMessage): SessionAuthContext | null;
6
+ /** Default inbound message hook: dispatch allowed messages with Telegram user auth. */
7
+ export declare function defaultOnMessage(ctx: TelegramContext, message: TelegramMessage): Promise<TelegramInboundResult>;
8
+ /** Built-in Telegram event handlers for typing, replies, HITL, and terminal errors. */
9
+ export declare const defaultEvents: TelegramChannelEvents;
@@ -0,0 +1,3 @@
1
+ import{extractErrorId,formatErrorHint}from"#internal/logging.js";import{registerTelegramFreeformPrompt,renderTelegramInputRequest}from"#public/channels/telegram/hitl.js";function defaultTelegramAuth(e){let t=e.from;if(!t)return null;let n={chat_id:e.chat.id,chat_type:e.chat.type,message_id:e.messageId,user_id:t.id};e.chat.title!==void 0&&(n.chat_title=e.chat.title),e.messageThreadId!==void 0&&(n.message_thread_id=String(e.messageThreadId)),t.username!==void 0&&(n.username=t.username);let r=e.chat.type===`group`||e.chat.type===`supergroup`,i=r?`telegram:${e.chat.id}:${t.id}`:`telegram:${t.id}`;return{attributes:n,authenticator:`telegram-webhook`,issuer:r?`telegram:${e.chat.id}`:`telegram`,principalId:i,principalType:t.isBot?`service`:`user`}}async function defaultOnMessage(e,t){return shouldDispatchTelegramMessage(t,e.telegram.botUsername)?(await e.telegram.startTyping(),{auth:defaultTelegramAuth(t)}):null}var defaultEvents={async"turn.started"(e,t){await t.telegram.startTyping()},async"actions.requested"(e,t){await t.telegram.startTyping()},async"input.requested"(e,t){for(let i of e.requests){let e=renderTelegramInputRequest(i,t.state),a=await t.telegram.post({reply_markup:e.replyMarkup,text:e.text});e.freeformRequestId!==void 0&&a.id&&registerTelegramFreeformPrompt(t.state,{messageId:a.id,requestId:e.freeformRequestId})}},async"message.completed"(e,t){e.finishReason===`tool-calls`||!e.message||await t.telegram.post(e.message)},async"turn.failed"(n,r){let i=formatErrorHint(n),a=extractErrorId(n.details);await r.telegram.post([`I hit an error while handling your request${i}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...a?[``,`Error id: ${a}`]:[]].join(`
2
+ `))},async"session.failed"(n,r){let i=formatErrorHint(n),a=extractErrorId(n.details);await r.telegram.post([`This session could not recover from an error${i}.`,``,`Start a new message to continue.`,...a?[``,`Error id: ${a}`]:[]].join(`
3
+ `))}};function shouldDispatchTelegramMessage(e,t){if(e.from?.isBot===!0||e.chat.type===`channel`)return!1;let n=e.text||e.caption;return n.trim().length>0||e.attachments.length>0?!!(e.chat.type===`private`||e.replyToMessage?.from?.isBot===!0||isBotCommand(n,t)||t!==void 0&&mentionsBot(n,t)):!1}function isBotCommand(e,t){let n=/^\/(?<command>[A-Za-z0-9_]+)(?:@(?<target>[A-Za-z0-9_]+))?(?:\s|$)/u.exec(e);if(!n)return!1;let r=n.groups?.target;return r===void 0?!0:t!==void 0&&r.toLowerCase()===t.toLowerCase()}function mentionsBot(e,t){return e.toLowerCase().includes(`@${t.toLowerCase()}`)}export{defaultEvents,defaultOnMessage,defaultTelegramAuth};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Telegram HITL inline keyboard rendering + compact callback decode helpers.
3
+ *
4
+ * Telegram limits `InlineKeyboardButton.callback_data` to 64 bytes, so
5
+ * Ash stores compact callback ids in durable channel state and remaps
6
+ * them back to full input responses inside the channel deliver hook.
7
+ */
8
+ import type { InputRequest, InputResponse } from "#runtime/input/types.js";
9
+ /** Prefix used in Telegram callback_data values generated by Ash. */
10
+ export declare const TELEGRAM_HITL_CALLBACK_PREFIX = "ash:";
11
+ /** Synthetic request id prefix sent through `send()` for callback queries. */
12
+ export declare const TELEGRAM_CALLBACK_RESPONSE_PREFIX = "telegram_callback:";
13
+ /** Synthetic request id prefix sent through `send()` for replies to ForceReply prompts. */
14
+ export declare const TELEGRAM_REPLY_RESPONSE_PREFIX = "telegram_reply:";
15
+ /** Minimal durable state shape used by the HITL helpers. */
16
+ export interface TelegramHitlState {
17
+ hitlCallbacks?: Record<string, InputResponse>;
18
+ nextHitlCallbackId?: number;
19
+ pendingFreeformReplies?: Record<string, string>;
20
+ }
21
+ /** Rendered Telegram message body for one pending input request. */
22
+ export interface TelegramInputRequestMessage {
23
+ readonly freeformRequestId?: string;
24
+ readonly replyMarkup?: Readonly<Record<string, unknown>>;
25
+ readonly text: string;
26
+ }
27
+ /** Renders one `InputRequest` as Telegram inline keyboard or ForceReply markup. */
28
+ export declare function renderTelegramInputRequest(request: InputRequest, state: TelegramHitlState): TelegramInputRequestMessage;
29
+ /** Records the Telegram message id that a freeform answer should reply to. */
30
+ export declare function registerTelegramFreeformPrompt(state: TelegramHitlState, input: {
31
+ readonly messageId: string;
32
+ readonly requestId: string;
33
+ }): void;
34
+ /** Wraps Telegram callback data in an `InputResponse` shape for delivery. */
35
+ export declare function telegramCallbackInputResponse(callbackData: string): InputResponse;
36
+ /** Wraps a reply-to-message id in an `InputResponse` shape for delivery. */
37
+ export declare function telegramReplyInputResponse(input: {
38
+ readonly messageId: string;
39
+ readonly text: string;
40
+ }): InputResponse;
41
+ /** True when an input response needs Telegram-specific durable-state remapping. */
42
+ export declare function isTelegramSyntheticResponse(response: InputResponse): boolean;
43
+ /** Remaps compact Telegram callback/reply ids into real Ash input responses. */
44
+ export declare function resolveTelegramInputResponses(state: TelegramHitlState, responses: readonly InputResponse[]): InputResponse[];
@@ -0,0 +1 @@
1
+ var TELEGRAM_HITL_CALLBACK_PREFIX=`ash:`,TELEGRAM_CALLBACK_RESPONSE_PREFIX=`telegram_callback:`,TELEGRAM_REPLY_RESPONSE_PREFIX=`telegram_reply:`,TELEGRAM_BUTTON_LABEL_MAX_LENGTH=64,TELEGRAM_INPUT_PLACEHOLDER_MAX_LENGTH=64,TELEGRAM_PROMPT_MAX_LENGTH=4e3,TELEGRAM_INLINE_ROW_SIZE=2;function renderTelegramInputRequest(e,t){let n=e.options;return n&&n.length>0?{replyMarkup:{inline_keyboard:chunk(n.map(n=>({callback_data:registerTelegramCallback(t,{optionId:n.id,requestId:e.requestId}),text:truncate(n.label,TELEGRAM_BUTTON_LABEL_MAX_LENGTH)})),TELEGRAM_INLINE_ROW_SIZE)},text:truncate(e.prompt,TELEGRAM_PROMPT_MAX_LENGTH)}:{freeformRequestId:e.requestId,replyMarkup:{force_reply:!0,input_field_placeholder:truncate(`Type your answer`,TELEGRAM_INPUT_PLACEHOLDER_MAX_LENGTH),selective:!0},text:truncate(e.prompt,TELEGRAM_PROMPT_MAX_LENGTH)}}function registerTelegramFreeformPrompt(e,t){e.pendingFreeformReplies={...e.pendingFreeformReplies,[t.messageId]:t.requestId}}function telegramCallbackInputResponse(e){return{optionId:`selected`,requestId:`${TELEGRAM_CALLBACK_RESPONSE_PREFIX}${e}`}}function telegramReplyInputResponse(e){return{requestId:`${TELEGRAM_REPLY_RESPONSE_PREFIX}${e.messageId}`,text:e.text}}function isTelegramSyntheticResponse(e){return e.requestId.startsWith(`telegram_callback:`)||e.requestId.startsWith(`telegram_reply:`)}function resolveTelegramInputResponses(e,r){let i=[];for(let a of r){if(a.requestId.startsWith(`telegram_callback:`)){let n=a.requestId.slice(TELEGRAM_CALLBACK_RESPONSE_PREFIX.length),r=e.hitlCallbacks?.[n];r!==void 0&&(i.push(r),delete e.hitlCallbacks?.[n]);continue}if(a.requestId.startsWith(`telegram_reply:`)){let t=a.requestId.slice(TELEGRAM_REPLY_RESPONSE_PREFIX.length),r=e.pendingFreeformReplies?.[t];r!==void 0&&a.text!==void 0&&(i.push({requestId:r,text:a.text}),delete e.pendingFreeformReplies?.[t]);continue}i.push(a)}return i}function registerTelegramCallback(t,n){let r=t.nextHitlCallbackId??0;t.nextHitlCallbackId=r+1;let i=`${TELEGRAM_HITL_CALLBACK_PREFIX}${r.toString(36)}`;return t.hitlCallbacks={...t.hitlCallbacks,[i]:n},i}function truncate(e,t){if(e.length<=t)return e;let n=Math.max(0,t-3);return`${e.slice(0,n).trimEnd()}...`}function chunk(e,t){let n=[];for(let r=0;r<e.length;r+=t)n.push(e.slice(r,r+t));return n}export{TELEGRAM_CALLBACK_RESPONSE_PREFIX,TELEGRAM_HITL_CALLBACK_PREFIX,TELEGRAM_REPLY_RESPONSE_PREFIX,isTelegramSyntheticResponse,registerTelegramFreeformPrompt,renderTelegramInputRequest,resolveTelegramInputResponses,telegramCallbackInputResponse,telegramReplyInputResponse};
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Inbound Telegram update parsing and prompt shaping.
3
+ *
4
+ * The channel owns small, documented data shapes instead of exposing
5
+ * Telegram's raw webhook payloads as the primary public API.
6
+ */
7
+ import type { UserContent } from "ai";
8
+ /** Telegram chat types handled by the native channel. */
9
+ export type TelegramChatType = "channel" | "group" | "private" | "supergroup";
10
+ /** Telegram user metadata surfaced by inbound messages and callbacks. */
11
+ export interface TelegramUser {
12
+ readonly firstName?: string;
13
+ readonly id: string;
14
+ readonly isBot: boolean;
15
+ readonly languageCode?: string;
16
+ readonly lastName?: string;
17
+ readonly username?: string;
18
+ }
19
+ /** Telegram chat metadata surfaced by inbound messages. */
20
+ export interface TelegramChat {
21
+ readonly id: string;
22
+ readonly title?: string;
23
+ readonly type: TelegramChatType;
24
+ readonly username?: string;
25
+ }
26
+ /** Inbound Telegram file attachment. */
27
+ export interface TelegramAttachment {
28
+ readonly fileId: string;
29
+ readonly fileName?: string;
30
+ readonly fileUniqueId?: string;
31
+ readonly height?: number;
32
+ readonly kind: "document" | "photo";
33
+ readonly mediaType?: string;
34
+ readonly size?: number;
35
+ readonly width?: number;
36
+ }
37
+ /** Minimal Telegram message representation nested on replies/callbacks. */
38
+ export interface TelegramMessageReference {
39
+ readonly chat: TelegramChat;
40
+ readonly from?: TelegramUser;
41
+ readonly messageId: string;
42
+ readonly messageThreadId?: number;
43
+ }
44
+ /** Channel-owned representation of one inbound Telegram message. */
45
+ export interface TelegramMessage {
46
+ readonly attachments: readonly TelegramAttachment[];
47
+ readonly caption: string;
48
+ readonly chat: TelegramChat;
49
+ readonly from?: TelegramUser;
50
+ readonly messageId: string;
51
+ readonly messageThreadId?: number;
52
+ readonly raw: Record<string, unknown>;
53
+ readonly replyToMessage?: TelegramMessageReference;
54
+ readonly text: string;
55
+ }
56
+ /** Channel-owned representation of one inbound Telegram callback query. */
57
+ export interface TelegramCallbackQuery {
58
+ readonly data?: string;
59
+ readonly from: TelegramUser;
60
+ readonly id: string;
61
+ readonly message?: TelegramMessageReference;
62
+ readonly raw: Record<string, unknown>;
63
+ }
64
+ /** Telegram update variants handled by the native channel. */
65
+ export type TelegramUpdate = {
66
+ readonly kind: "callback_query";
67
+ readonly callbackQuery: TelegramCallbackQuery;
68
+ } | {
69
+ readonly kind: "message";
70
+ readonly message: TelegramMessage;
71
+ };
72
+ /** Inbound identity and response guidance rendered into the model-visible context block. */
73
+ export interface TelegramInboundContext {
74
+ readonly botUsername?: string;
75
+ readonly chatId: string;
76
+ readonly chatTitle?: string;
77
+ readonly chatType: TelegramChatType;
78
+ readonly messageId: string;
79
+ readonly messageThreadId?: number;
80
+ readonly userId?: string;
81
+ readonly username?: string;
82
+ }
83
+ /** Parses one JSON-decoded Telegram update payload. */
84
+ export declare function parseTelegramUpdate(value: unknown): TelegramUpdate | null;
85
+ /** Renders one {@link TelegramInboundContext} as a deterministic context block. */
86
+ export declare function formatTelegramContextBlock(context: TelegramInboundContext): string;
87
+ /** Prepends a `<telegram_context>` block to the inbound turn message. */
88
+ export declare function prependTelegramContext(message: string | UserContent, context: TelegramInboundContext): string | UserContent;
@@ -0,0 +1,2 @@
1
+ import{isNonEmptyString,isObject}from"#shared/guards.js";var TELEGRAM_RESPONSE_INSTRUCTIONS=`Reply for Telegram in concise plain text. Avoid tables, long code fences, and formatting that depends on Markdown rendering.`;function parseTelegramUpdate(e){if(!isObject(e))return null;let n=parseTelegramMessage(e.message);if(n!==null)return{kind:`message`,message:n};let r=parseTelegramCallbackQuery(e.callback_query);return r===null?null:{callbackQuery:r,kind:`callback_query`}}function formatTelegramContextBlock(e){return[`<telegram_context>`,`response_medium: telegram`,`response_instructions: ${TELEGRAM_RESPONSE_INSTRUCTIONS}`,`chat_id: ${e.chatId}`,`chat_type: ${e.chatType}`,...e.chatTitle?[`chat_title: ${e.chatTitle}`]:[],`message_id: ${e.messageId}`,...e.messageThreadId===void 0?[]:[`message_thread_id: ${e.messageThreadId}`],...e.userId?[`user_id: ${e.userId}`]:[],...e.username?[`username: ${e.username}`]:[],...e.botUsername?[`bot_username: ${e.botUsername}`]:[],`</telegram_context>`].join(`
2
+ `)}function prependTelegramContext(e,t){let n=formatTelegramContextBlock(t);return typeof e==`string`?e.length>0?`${n}\n\n${e}`:n:[{type:`text`,text:n},...e]}function parseTelegramMessage(e){if(!isObject(e))return null;let n=parseTelegramChat(e.chat),r=numberLikeToString(e.message_id);return!n||!r?null:{attachments:parseAttachments(e),caption:typeof e.caption==`string`?e.caption:``,chat:n,from:parseTelegramUser(e.from),messageId:r,messageThreadId:typeof e.message_thread_id==`number`?e.message_thread_id:void 0,raw:e,replyToMessage:parseMessageReference(e.reply_to_message),text:typeof e.text==`string`?e.text:``}}function parseTelegramCallbackQuery(n){if(!isObject(n)||!isNonEmptyString(n.id))return null;let r=parseTelegramUser(n.from);return r?{data:typeof n.data==`string`?n.data:void 0,from:r,id:n.id,message:parseMessageReference(n.message),raw:n}:null}function parseMessageReference(e){if(!isObject(e))return;let n=parseTelegramChat(e.chat),r=numberLikeToString(e.message_id);if(!(!n||!r))return{chat:n,from:parseTelegramUser(e.from),messageId:r,messageThreadId:typeof e.message_thread_id==`number`?e.message_thread_id:void 0}}function parseTelegramChat(e){if(!isObject(e))return null;let n=numberLikeToString(e.id),r=parseChatType(e.type);return!n||!r?null:{id:n,title:typeof e.title==`string`?e.title:void 0,type:r,username:typeof e.username==`string`?e.username:void 0}}function parseTelegramUser(e){if(!isObject(e))return;let n=numberLikeToString(e.id);if(n)return{firstName:typeof e.first_name==`string`?e.first_name:void 0,id:n,isBot:e.is_bot===!0,languageCode:typeof e.language_code==`string`?e.language_code:void 0,lastName:typeof e.last_name==`string`?e.last_name:void 0,username:typeof e.username==`string`?e.username:void 0}}function parseAttachments(e){let t=[],n=parseLargestPhoto(e.photo);n!==null&&t.push(n);let r=parseDocument(e.document);return r!==null&&t.push(r),t}function parseLargestPhoto(e){if(!Array.isArray(e)||e.length===0)return null;let n=e.filter(isObject).map(e=>({fileId:typeof e.file_id==`string`?e.file_id:``,fileUniqueId:typeof e.file_unique_id==`string`?e.file_unique_id:void 0,height:typeof e.height==`number`?e.height:void 0,size:typeof e.file_size==`number`?e.file_size:void 0,width:typeof e.width==`number`?e.width:void 0})).filter(e=>e.fileId.length>0).sort((e,t)=>scorePhoto(t)-scorePhoto(e))[0];return n?{fileId:n.fileId,fileName:`photo.jpg`,fileUniqueId:n.fileUniqueId,height:n.height,kind:`photo`,mediaType:`image/jpeg`,size:n.size,width:n.width}:null}function parseDocument(e){return!isObject(e)||typeof e.file_id!=`string`?null:{fileId:e.file_id,fileName:typeof e.file_name==`string`?e.file_name:void 0,fileUniqueId:typeof e.file_unique_id==`string`?e.file_unique_id:void 0,kind:`document`,mediaType:typeof e.mime_type==`string`?e.mime_type:void 0,size:typeof e.file_size==`number`?e.file_size:void 0}}function scorePhoto(e){return e.size===void 0?(e.width??0)*(e.height??0):e.size}function parseChatType(e){return e===`channel`||e===`group`||e===`private`||e===`supergroup`?e:null}function numberLikeToString(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{formatTelegramContextBlock,parseTelegramUpdate,prependTelegramContext};
@@ -0,0 +1,8 @@
1
+ export type { ModelMessage } from "ai";
2
+ export { telegramChannel, type TelegramChannel, type TelegramChannelConfig, type TelegramChannelCredentials, type TelegramChannelEvents, type TelegramChannelState, type TelegramContext, type TelegramEventContext, type TelegramHandle, type TelegramInboundResult, type TelegramInboundResultOrPromise, type TelegramReceiveArgs, } from "#public/channels/telegram/telegramChannel.js";
3
+ export { callTelegramApi, answerTelegramCallbackQuery, downloadTelegramFile, editTelegramMessageReplyMarkup, getTelegramFile, resolveTelegramBotToken, sendTelegramChatAction, sendTelegramMessage, splitTelegramMessageText, telegramContinuationToken, TELEGRAM_MESSAGE_TEXT_MAX_LENGTH, type TelegramApiOptions, type TelegramApiResponse, type TelegramBotToken, type TelegramCredentials, type TelegramFetch, type TelegramMessageBody, type TelegramMessageResult, } from "#public/channels/telegram/api.js";
4
+ export { formatTelegramContextBlock, parseTelegramUpdate, prependTelegramContext, type TelegramAttachment, type TelegramCallbackQuery, type TelegramChat, type TelegramChatType, type TelegramInboundContext, type TelegramMessage, type TelegramMessageReference, type TelegramUpdate, type TelegramUser, } from "#public/channels/telegram/inbound.js";
5
+ export { TELEGRAM_CALLBACK_RESPONSE_PREFIX, TELEGRAM_HITL_CALLBACK_PREFIX, TELEGRAM_REPLY_RESPONSE_PREFIX, isTelegramSyntheticResponse, registerTelegramFreeformPrompt, renderTelegramInputRequest, resolveTelegramInputResponses, telegramCallbackInputResponse, telegramReplyInputResponse, type TelegramHitlState, type TelegramInputRequestMessage, } from "#public/channels/telegram/hitl.js";
6
+ export { TELEGRAM_FILE_URL_PROTOCOL, buildTelegramTurnMessage, collectTelegramFileParts, createTelegramFetchFile, createTelegramFileUrl, } from "#public/channels/telegram/attachments.js";
7
+ export { defaultTelegramAuth } from "#public/channels/telegram/defaults.js";
8
+ export { resolveTelegramWebhookSecretToken, verifyTelegramRequest, type TelegramVerifyOptions, type TelegramWebhookSecretToken, type TelegramWebhookVerifier, } from "#public/channels/telegram/verify.js";
@@ -0,0 +1 @@
1
+ import{TELEGRAM_MESSAGE_TEXT_MAX_LENGTH,answerTelegramCallbackQuery,callTelegramApi,downloadTelegramFile,editTelegramMessageReplyMarkup,getTelegramFile,resolveTelegramBotToken,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken}from"#public/channels/telegram/api.js";import{TELEGRAM_CALLBACK_RESPONSE_PREFIX,TELEGRAM_HITL_CALLBACK_PREFIX,TELEGRAM_REPLY_RESPONSE_PREFIX,isTelegramSyntheticResponse,registerTelegramFreeformPrompt,renderTelegramInputRequest,resolveTelegramInputResponses,telegramCallbackInputResponse,telegramReplyInputResponse}from"#public/channels/telegram/hitl.js";import{telegramChannel}from"#public/channels/telegram/telegramChannel.js";import{formatTelegramContextBlock,parseTelegramUpdate,prependTelegramContext}from"#public/channels/telegram/inbound.js";import{TELEGRAM_FILE_URL_PROTOCOL,buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile,createTelegramFileUrl}from"#public/channels/telegram/attachments.js";import{defaultTelegramAuth}from"#public/channels/telegram/defaults.js";import{resolveTelegramWebhookSecretToken,verifyTelegramRequest}from"#public/channels/telegram/verify.js";export{TELEGRAM_CALLBACK_RESPONSE_PREFIX,TELEGRAM_FILE_URL_PROTOCOL,TELEGRAM_HITL_CALLBACK_PREFIX,TELEGRAM_MESSAGE_TEXT_MAX_LENGTH,TELEGRAM_REPLY_RESPONSE_PREFIX,answerTelegramCallbackQuery,buildTelegramTurnMessage,callTelegramApi,collectTelegramFileParts,createTelegramFetchFile,createTelegramFileUrl,defaultTelegramAuth,downloadTelegramFile,editTelegramMessageReplyMarkup,formatTelegramContextBlock,getTelegramFile,isTelegramSyntheticResponse,parseTelegramUpdate,prependTelegramContext,registerTelegramFreeformPrompt,renderTelegramInputRequest,resolveTelegramBotToken,resolveTelegramInputResponses,resolveTelegramWebhookSecretToken,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramCallbackInputResponse,telegramChannel,telegramContinuationToken,telegramReplyInputResponse,verifyTelegramRequest};
@@ -0,0 +1,126 @@
1
+ import type { ModelMessage } from "ai";
2
+ import type { TypedReceiveRoute } from "#channel/receive-args.js";
3
+ import type { SessionHandle } from "#channel/session.js";
4
+ import type { SessionAuthContext } from "#channel/types.js";
5
+ import type { HandleMessageStreamEvent } from "#protocol/message.js";
6
+ import { type TelegramApiOptions, type TelegramApiResponse, type TelegramCredentials, type TelegramMessageBody, type TelegramMessageResult } from "#public/channels/telegram/api.js";
7
+ import { type TelegramHitlState } from "#public/channels/telegram/hitl.js";
8
+ import { type TelegramCallbackQuery, type TelegramChatType, type TelegramMessage } from "#public/channels/telegram/inbound.js";
9
+ import { type UploadPolicyInput } from "#public/channels/upload-policy.js";
10
+ import { type TelegramWebhookSecretToken, type TelegramWebhookVerifier } from "#public/channels/telegram/verify.js";
11
+ import { type Channel } from "#public/definitions/defineChannel.js";
12
+ import { type JsonObject } from "#shared/json.js";
13
+ type EventData<T extends HandleMessageStreamEvent["type"]> = Extract<HandleMessageStreamEvent, {
14
+ type: T;
15
+ }> extends {
16
+ data: infer D;
17
+ } ? D : undefined;
18
+ /** Pre-dispatch Telegram context passed to inbound hooks. */
19
+ export interface TelegramContext {
20
+ readonly telegram: TelegramHandle;
21
+ }
22
+ /** Event-handler Telegram context, including mutable per-conversation state. */
23
+ export interface TelegramEventContext extends TelegramContext {
24
+ readonly session: SessionHandle;
25
+ state: TelegramChannelState;
26
+ }
27
+ /** JSON-serializable Telegram channel state. */
28
+ export interface TelegramChannelState extends TelegramHitlState {
29
+ /** Telegram bot username used for group mention detection, when configured. */
30
+ botUsername?: string | null;
31
+ /** Telegram chat id. */
32
+ chatId: string | null;
33
+ /** Telegram chat type, when known from an inbound update. */
34
+ chatType: TelegramChatType | null;
35
+ /** Group/supergroup conversation anchor message id. */
36
+ conversationId: string | null;
37
+ /** Forum topic id, when known. */
38
+ messageThreadId: number | null;
39
+ /** Telegram user id that triggered the current session/turn. */
40
+ triggeringUserId?: string | null;
41
+ }
42
+ /** Telegram channel credentials. */
43
+ export interface TelegramChannelCredentials extends TelegramCredentials {
44
+ /**
45
+ * Telegram webhook secret token configured through setWebhook.
46
+ * Falls back to `TELEGRAM_WEBHOOK_SECRET_TOKEN` when neither this
47
+ * nor `webhookVerifier` is supplied.
48
+ */
49
+ readonly webhookSecretToken?: TelegramWebhookSecretToken;
50
+ /** Custom inbound webhook verifier for forwarded webhooks. */
51
+ readonly webhookVerifier?: TelegramWebhookVerifier;
52
+ }
53
+ /** Arguments accepted by `receive(telegram, args)` for proactive sessions. */
54
+ export interface TelegramReceiveArgs {
55
+ readonly chatId: number | string;
56
+ readonly conversationId?: number | string;
57
+ readonly initialMessage?: string | TelegramMessageBody;
58
+ readonly messageThreadId?: number;
59
+ }
60
+ /** Result of an inbound Telegram message hook. Return `null` to drop the update. */
61
+ export type TelegramInboundResult = {
62
+ readonly auth: SessionAuthContext | null;
63
+ readonly modelContext?: readonly ModelMessage[];
64
+ } | null;
65
+ /** Sync or async {@link TelegramInboundResult}. */
66
+ export type TelegramInboundResultOrPromise = TelegramInboundResult | Promise<TelegramInboundResult>;
67
+ type TelegramEventHandler<T extends HandleMessageStreamEvent["type"]> = (data: EventData<T>, ctx: TelegramEventContext) => void | Promise<void>;
68
+ /** Event handlers supported by `telegramChannel({ events })`. */
69
+ export interface TelegramChannelEvents {
70
+ readonly "turn.started"?: TelegramEventHandler<"turn.started">;
71
+ readonly "actions.requested"?: TelegramEventHandler<"actions.requested">;
72
+ readonly "action.result"?: TelegramEventHandler<"action.result">;
73
+ readonly "message.completed"?: TelegramEventHandler<"message.completed">;
74
+ readonly "message.appended"?: TelegramEventHandler<"message.appended">;
75
+ readonly "input.requested"?: TelegramEventHandler<"input.requested">;
76
+ readonly "turn.failed"?: TelegramEventHandler<"turn.failed">;
77
+ readonly "turn.completed"?: TelegramEventHandler<"turn.completed">;
78
+ readonly "session.failed"?: TelegramEventHandler<"session.failed">;
79
+ readonly "session.completed"?: TelegramEventHandler<"session.completed">;
80
+ readonly "session.waiting"?: TelegramEventHandler<"session.waiting">;
81
+ readonly "connection.authorization_required"?: TelegramEventHandler<"connection.authorization_required">;
82
+ readonly "connection.authorization_pending"?: TelegramEventHandler<"connection.authorization_pending">;
83
+ readonly "connection.authorization_completed"?: TelegramEventHandler<"connection.authorization_completed">;
84
+ }
85
+ /** Configuration for {@link telegramChannel}. */
86
+ export interface TelegramChannelConfig {
87
+ readonly api?: Omit<TelegramApiOptions, "credentials">;
88
+ readonly botUsername?: string;
89
+ readonly credentials?: TelegramChannelCredentials;
90
+ readonly events?: TelegramChannelEvents;
91
+ /** Handler for non-HITL callback queries. */
92
+ readonly onCallbackQuery?: (ctx: TelegramContext, query: TelegramCallbackQuery) => void | Promise<void>;
93
+ /** Inbound message hook. Defaults to Telegram user auth and dispatch gating. */
94
+ readonly onMessage?: (ctx: TelegramContext, message: TelegramMessage) => TelegramInboundResultOrPromise;
95
+ /** Override the default webhook route path (`/ash/v1/telegram`). */
96
+ readonly route?: string;
97
+ /** Inbound upload policy for Telegram photos and documents. */
98
+ readonly uploadPolicy?: UploadPolicyInput;
99
+ }
100
+ /** Low-level Telegram handle exposed to hooks and event handlers. */
101
+ export interface TelegramHandle {
102
+ readonly botUsername: string | undefined;
103
+ readonly chatId: string;
104
+ readonly chatType: TelegramChatType | undefined;
105
+ readonly conversationId: string | undefined;
106
+ readonly messageThreadId: number | undefined;
107
+ request(method: string, body?: JsonObject): Promise<TelegramApiResponse>;
108
+ post(message: string | TelegramMessageBody): Promise<TelegramMessageResult>;
109
+ sendMessage(message: string | TelegramMessageBody): Promise<TelegramMessageResult>;
110
+ startTyping(action?: string): Promise<void>;
111
+ answerCallbackQuery(input: {
112
+ readonly callbackQueryId: string;
113
+ readonly showAlert?: boolean;
114
+ readonly text?: string;
115
+ }): Promise<TelegramApiResponse>;
116
+ editMessageReplyMarkup(input: {
117
+ readonly messageId: number | string;
118
+ readonly replyMarkup?: Readonly<Record<string, unknown>>;
119
+ }): Promise<TelegramApiResponse>;
120
+ }
121
+ /** Concrete return type of {@link telegramChannel}. */
122
+ export interface TelegramChannel extends Channel<TelegramChannelState>, TypedReceiveRoute<TelegramReceiveArgs> {
123
+ }
124
+ /** Telegram channel factory for webhook updates and proactive messages. */
125
+ export declare function telegramChannel(config?: TelegramChannelConfig): TelegramChannel;
126
+ export {};
@@ -0,0 +1 @@
1
+ import{createLogger}from"#internal/logging.js";import{isCompiledChannel}from"#channel/compiled-channel.js";import{parseJsonObject}from"#shared/json.js";import{defaultDeliverResult}from"#channel/adapter.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{answerTelegramCallbackQuery,callTelegramApi,editTelegramMessageReplyMarkup,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken}from"#public/channels/telegram/api.js";import{TELEGRAM_HITL_CALLBACK_PREFIX,isTelegramSyntheticResponse,resolveTelegramInputResponses,telegramCallbackInputResponse,telegramReplyInputResponse}from"#public/channels/telegram/hitl.js";import{parseTelegramUpdate,prependTelegramContext}from"#public/channels/telegram/inbound.js";import{buildTelegramTurnMessage,collectTelegramFileParts,createTelegramFetchFile}from"#public/channels/telegram/attachments.js";import{defaultEvents,defaultOnMessage}from"#public/channels/telegram/defaults.js";import{verifyTelegramRequest}from"#public/channels/telegram/verify.js";var log=createLogger(`telegram.channel`);function telegramChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),r=e.onMessage??defaultOnMessage,s={...defaultEvents,...e.events},c=defineChannel({kindHint:`telegram`,state:initialTelegramState(e.botUsername),fetchFile:createTelegramFetchFile({api:e.api,credentials:e.credentials,policy:t}),context(t,n){return rebuildTelegramContext(t,n,e)},routes:[POST(e.route??`/ash/v1/telegram`,async(i,{send:a,waitUntil:o})=>{let s=await verifyInbound(i,e.credentials);if(s===null)return new Response(`unauthorized`,{status:401});let c;try{c=parseJsonObject(JSON.parse(s))}catch(e){return log.warn(`inbound Telegram body is not valid JSON`,{error:e}),new Response(`ok`)}let l=parseTelegramUpdate(c);return l===null?new Response(`ok`):l.kind===`message`?(o(dispatchMessage({config:e,message:l.message,onMessage:r,send:a,uploadPolicy:t})),new Response(`ok`)):(o(dispatchCallbackQuery({config:e,query:l.callbackQuery,send:a})),new Response(`ok`))})],async receive(t,{send:n}){let r=t.args,i=readChatId(r.chatId);if(i===void 0)throw Error(`telegramChannel().receive requires args.chatId.`);let a=typeof r.messageThreadId==`number`?r.messageThreadId:void 0,o=readOptionalString(r.conversationId),s=r.initialMessage;if(s!==void 0&&o!==void 0)throw Error("telegramChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let c=o;return s!==void 0&&(c=(await buildTelegramHandle({config:e,state:{...initialTelegramState(e.botUsername),chatId:i,messageThreadId:a??null}}).sendMessage(s)).id||void 0),n(t.message,{auth:t.auth,continuationToken:telegramContinuationToken({chatId:i,conversationId:c,messageThreadId:a}),state:{...initialTelegramState(e.botUsername),chatId:i,conversationId:c??null,messageThreadId:a??null}})},events:s});return attachTelegramDeliver(c),c}function rebuildTelegramContext(e,t,n){return{session:t,state:e,telegram:buildTelegramHandle({config:n,session:t,state:e})}}function buildTelegramHandle(e){let t=e.config.api,n=e.state,r=e.config.credentials;function anchor(t){!t.id||n.chatType===`private`||(n.conversationId=t.id,n.chatId&&e.session?.setContinuationToken(telegramContinuationToken({chatId:n.chatId,conversationId:t.id,messageThreadId:n.messageThreadId??void 0})))}async function sendOne(e){let i=n.chatId??``;if(!i)throw Error(`telegramChannel: missing chat id for outbound message.`);let a=await sendTelegramMessage({apiBaseUrl:t?.apiBaseUrl,body:{...e,message_thread_id:e.message_thread_id??n.messageThreadId??void 0},credentials:r,fetch:t?.fetch,fileBaseUrl:t?.fileBaseUrl,chatId:i});return anchor(a),a}return{botUsername:n.botUsername??e.config.botUsername,chatId:n.chatId??``,chatType:n.chatType??void 0,conversationId:n.conversationId??void 0,messageThreadId:n.messageThreadId??void 0,answerCallbackQuery(e){return answerTelegramCallbackQuery({apiBaseUrl:t?.apiBaseUrl,callbackQueryId:e.callbackQueryId,credentials:r,fetch:t?.fetch,showAlert:e.showAlert,text:e.text})},editMessageReplyMarkup(e){let i=n.chatId??``;if(!i)throw Error(`telegramChannel: missing chat id for reply-markup edit.`);return editTelegramMessageReplyMarkup({apiBaseUrl:t?.apiBaseUrl,chatId:i,credentials:r,fetch:t?.fetch,messageId:e.messageId,replyMarkup:e.replyMarkup})},post(e){return postTelegramMessage(e,sendOne)},request(e,n){return callTelegramApi({apiBaseUrl:t?.apiBaseUrl,body:n,botToken:r?.botToken,fetch:t?.fetch,method:e})},sendMessage(e){return postTelegramMessage(e,sendOne)},async startTyping(e=`typing`){let i=n.chatId??``;if(i)try{await sendTelegramChatAction({action:e,apiBaseUrl:t?.apiBaseUrl,chatId:i,credentials:r,fetch:t?.fetch,messageThreadId:n.messageThreadId??void 0})}catch(e){log.debug(`Telegram typing indicator failed — swallowed`,{error:e})}}}}async function postTelegramMessage(e,t){let n=typeof e==`string`?{text:e}:e,r=splitTelegramMessageText(n.text),i;for(let[e,a]of r.entries()){let r=await t(e===0?{...n,text:a}:{text:a});i===void 0&&(i=r)}return i??{id:``,raw:null}}async function verifyInbound(e,t){try{return await verifyTelegramRequest(e,{secretToken:t?.webhookVerifier?void 0:t?.webhookSecretToken,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`telegram inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){if(e.message.from?.isBot===!0)return;let t=stateFromMessage(e.message,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})},r;try{r=await e.onMessage(n,e.message)}catch(e){log.error(`message handler failed`,{error:e});return}if(r==null)return;let i=collectTelegramFileParts(e.message.attachments,e.uploadPolicy),a=prependTelegramContext(buildTelegramTurnMessage(e.message,i),{botUsername:e.config.botUsername,chatId:e.message.chat.id,chatTitle:e.message.chat.title,chatType:e.message.chat.type,messageId:e.message.messageId,messageThreadId:e.message.messageThreadId,userId:e.message.from?.id,username:e.message.from?.username}),o=e.message.text||e.message.caption,s=e.message.replyToMessage?.from?.isBot===!0&&o.trim().length>0?[telegramReplyInputResponse({messageId:e.message.replyToMessage.messageId,text:o})]:void 0;try{await e.send({inputResponses:s,message:a,modelContext:r.modelContext},{auth:r.auth,continuationToken:continuationTokenFromState(t),state:t})}catch(e){log.error(`message delivery failed`,{error:e})}}async function dispatchCallbackQuery(e){let t=stateFromCallbackQuery(e.query,e.config),n={telegram:buildTelegramHandle({config:e.config,state:t})};if(e.query.data?.startsWith(TELEGRAM_HITL_CALLBACK_PREFIX)===!0){try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Answer received.`})}catch(e){log.warn(`Telegram callback-query acknowledgement failed`,{error:e})}if(!e.query.message||!t.chatId)return;try{await e.send({inputResponses:[telegramCallbackInputResponse(e.query.data)]},{auth:null,continuationToken:continuationTokenFromState(t),state:t})}catch(e){log.error(`callback query delivery failed`,{error:e})}return}if(e.config.onCallbackQuery!==void 0){try{await e.config.onCallbackQuery(n,e.query)}catch(e){log.error(`custom callback-query handler failed`,{error:e})}return}try{await n.telegram.answerCallbackQuery({callbackQueryId:e.query.id,text:`Unsupported action.`})}catch(e){log.warn(`Telegram unsupported callback-query acknowledgement failed`,{error:e})}}function attachTelegramDeliver(e){if(!isCompiledChannel(e))return;let n=e.adapter;n.deliver=(e,t)=>{let n=e.inputResponses??[];if(n.some(isTelegramSyntheticResponse)){let r=resolveTelegramInputResponses(t.state,n);return r.length>0?{inputResponses:r,modelContext:e.modelContext}:e.message===void 0?void 0:{message:e.message,modelContext:e.modelContext}}return defaultDeliverResult(e)}}function stateFromMessage(e,t){let n=e.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:e.chat.id,chatType:e.chat.type,conversationId:n?null:conversationIdForMessage(e),messageThreadId:e.messageThreadId??null,triggeringUserId:e.from?.id??null}}function stateFromCallbackQuery(e,t){let n=e.message;if(!n)return{...initialTelegramState(t.botUsername),triggeringUserId:e.from.id};let r=n.chat.type===`private`;return{...initialTelegramState(t.botUsername),chatId:n.chat.id,chatType:n.chat.type,conversationId:r?null:n.messageId,messageThreadId:n.messageThreadId??null,triggeringUserId:e.from.id}}function conversationIdForMessage(e){return e.replyToMessage?.from?.isBot===!0?e.replyToMessage.messageId:e.messageId}function continuationTokenFromState(e){return telegramContinuationToken({chatId:e.chatId??``,conversationId:e.chatType===`private`?void 0:e.conversationId??void 0,messageThreadId:e.messageThreadId??void 0})}function initialTelegramState(e){return{botUsername:e??null,chatId:null,chatType:null,conversationId:null,hitlCallbacks:{},messageThreadId:null,nextHitlCallbackId:0,pendingFreeformReplies:{},triggeringUserId:null}}function readChatId(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}function readOptionalString(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e==`number`&&Number.isFinite(e))return String(e)}export{telegramChannel};
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Telegram inbound-webhook verification.
3
+ *
4
+ * When a webhook is configured with `secret_token`, Telegram includes
5
+ * that exact value in `X-Telegram-Bot-Api-Secret-Token` for every
6
+ * webhook request. The native channel verifies the header directly or
7
+ * delegates to a caller-supplied verifier for forwarded webhooks.
8
+ */
9
+ /** Secret token configured on Telegram's `setWebhook` call. */
10
+ export type TelegramWebhookSecretToken = string | (() => string | Promise<string>);
11
+ /**
12
+ * Caller-supplied inbound webhook verifier. Used as an alternative to
13
+ * Telegram's secret-token header when an integration authenticates
14
+ * forwarded webhooks before they reach Ash.
15
+ */
16
+ export type TelegramWebhookVerifier = (request: Request, body: string) => unknown | Promise<unknown>;
17
+ /** Options for {@link verifyTelegramRequest}. */
18
+ export interface TelegramVerifyOptions {
19
+ readonly secretToken: TelegramWebhookSecretToken | undefined;
20
+ readonly webhookVerifier?: TelegramWebhookVerifier;
21
+ }
22
+ /** Resolves a Telegram webhook secret, falling back to `TELEGRAM_WEBHOOK_SECRET_TOKEN`. */
23
+ export declare function resolveTelegramWebhookSecretToken(secretToken?: TelegramWebhookSecretToken): Promise<string>;
24
+ /**
25
+ * Verifies an inbound Telegram webhook and returns its raw body.
26
+ *
27
+ * Throws when no secret/verifier is configured, the secret header is
28
+ * missing, or the supplied verifier/header rejects.
29
+ */
30
+ export declare function verifyTelegramRequest(request: Request, options: TelegramVerifyOptions): Promise<string>;
@@ -0,0 +1 @@
1
+ import{createLogger}from"#internal/logging.js";import{timingSafeEqual}from"node:crypto";var log=createLogger(`telegram.verify`);async function resolveTelegramWebhookSecretToken(e){let t=e??process.env.TELEGRAM_WEBHOOK_SECRET_TOKEN;if(!t)throw Error(`TELEGRAM_WEBHOOK_SECRET_TOKEN is required.`);return typeof t==`function`?await t():t}async function verifyTelegramRequest(e,t){let n=await e.text();if(t.webhookVerifier!==void 0){let r=await t.webhookVerifier(e,n);if(!r)throw Error(`telegramChannel: inbound webhook verifier rejected the request.`);return typeof r==`string`?r:n}let r=await resolveTelegramWebhookSecretToken(t.secretToken),i=e.headers.get(`x-telegram-bot-api-secret-token`)??``;if(!i)throw Error(`telegramChannel: inbound request missing Telegram secret-token header.`);if(!constantTimeCompare(r,i))throw Error(`telegramChannel: inbound request secret-token mismatch.`);return n}function constantTimeCompare(e,r){if(e.length!==r.length)return!1;try{return timingSafeEqual(Buffer.from(e),Buffer.from(r))}catch(e){return log.debug(`timingSafeEqual threw`,{error:e}),!1}}export{resolveTelegramWebhookSecretToken,verifyTelegramRequest};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "experimental-ash",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "bin": {
5
5
  "ash": "./bin/ash.js",
6
6
  "experimental-ash": "./bin/ash.js"
@@ -156,6 +156,16 @@
156
156
  "import": "./dist/src/public/channels/twilio/index.js",
157
157
  "default": "./dist/src/public/channels/twilio/index.js"
158
158
  },
159
+ "./channels/telegram": {
160
+ "types": "./dist/src/public/channels/telegram/index.d.ts",
161
+ "import": "./dist/src/public/channels/telegram/index.js",
162
+ "default": "./dist/src/public/channels/telegram/index.js"
163
+ },
164
+ "./channels/teams": {
165
+ "types": "./dist/src/public/channels/teams/index.d.ts",
166
+ "import": "./dist/src/public/channels/teams/index.js",
167
+ "default": "./dist/src/public/channels/teams/index.js"
168
+ },
159
169
  "./package.json": "./package.json"
160
170
  },
161
171
  "publishConfig": {