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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # experimental-ash
2
2
 
3
+ ## 0.28.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2e8ebcb: Add a native Microsoft Teams channel backed by the Bot Framework Activity and Connector protocols, including inbound verification, Teams conversation state, Adaptive Card HITL, proactive receive support, docs, tests, and smoke coverage.
8
+ - 70018bd: Add a native Telegram channel for verified Bot API webhooks, Telegram delivery, HITL interactions, attachments, and proactive sessions.
9
+
3
10
  ## 0.27.0
4
11
 
5
12
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: "Channels"
3
- description: "Deliver your agent over HTTP, Slack, Discord, Twilio, and custom transports."
3
+ description: "Deliver your agent over HTTP, Slack, Discord, Twilio, Telegram, Microsoft Teams, and custom transports."
4
4
  url: /channels
5
5
  ---
6
6
 
@@ -23,7 +23,7 @@ Ash ships the public HTTP protocol as channels:
23
23
  - `ashChannel({ auth })` for the built-in Ash protocol channel
24
24
 
25
25
  Ash also supports authored channels under `agent/channels/` for platform-specific integrations such
26
- as Slack, Discord, Twilio, or custom webhooks.
26
+ as Slack, Discord, Twilio, Telegram, Microsoft Teams, or custom webhooks.
27
27
 
28
28
  ## Filesystem Shape
29
29
 
@@ -279,6 +279,53 @@ resolver when the allowed phone numbers come from dynamic state. Use `allowFrom:
279
279
 
280
280
  See [Twilio channel setup](./twilio.md) for webhook URLs, environment variables, and overrides.
281
281
 
282
+ ## Telegram Channels
283
+
284
+ Telegram channels are authored with `telegramChannel()`:
285
+
286
+ ```ts
287
+ import { telegramChannel } from "experimental-ash/channels/telegram";
288
+
289
+ export default telegramChannel({
290
+ botUsername: "my_bot",
291
+ });
292
+ ```
293
+
294
+ The channel verifies Telegram's `X-Telegram-Bot-Api-Secret-Token` webhook header, accepts updates at
295
+ `/ash/v1/telegram`, dispatches private messages and addressed group messages, and resolves HITL
296
+ inline-keyboard callbacks and ForceReply answers back into Ash input responses. Private chats use
297
+ chat-wide continuation tokens. Group, supergroup, and forum-topic sessions include the chat id,
298
+ optional `message_thread_id`, and a conversation anchor so multiple bot conversations in the same
299
+ chat do not collapse.
300
+
301
+ Default delivery sends plain text through Telegram `sendMessage` with no `parse_mode`, splits text
302
+ at Telegram's 4096-character limit, and uses best-effort `sendChatAction("typing")` progress
303
+ indicators. Photos and documents are exposed as file parts and fetched through Telegram `getFile`
304
+ when the model needs the bytes.
305
+
306
+ See [Telegram channel setup](./telegram.md) for webhook registration, environment variables, group
307
+ privacy notes, HITL behavior, attachments, and proactive sessions.
308
+
309
+ ## Microsoft Teams Channels
310
+
311
+ Teams channels are authored with `teamsChannel()`:
312
+
313
+ ```ts
314
+ import { teamsChannel } from "experimental-ash/channels/teams";
315
+
316
+ export default teamsChannel();
317
+ ```
318
+
319
+ The channel verifies Bot Connector bearer JWTs, accepts Teams Bot Framework Activity POSTs at
320
+ `/ash/v1/teams`, dispatches personal messages and direct bot mentions, renders HITL prompts as
321
+ Adaptive Cards, and sends agent replies through the Bot Framework Connector REST API. Personal chats
322
+ resume by conversation id; channel and group-chat threads resume by conversation id plus root
323
+ activity id.
324
+
325
+ Proactive `receive(teams, args)` sessions require an existing conversation reference
326
+ (`serviceUrl` and `conversationId`). See [Microsoft Teams channel setup](./teams.md) for Azure Bot
327
+ setup, environment variables, proactive handoff, HITL, and file options.
328
+
282
329
  ## Cross-Channel Hand-off
283
330
 
284
331
  Route handlers can start a session on a different channel via `args.receive(channel, ...)`.
@@ -472,6 +519,7 @@ See [Channel file uploads](./attachments.md) for the full guide.
472
519
  ## What To Read Next
473
520
 
474
521
  - [Slack channel setup](./slack.md)
522
+ - [Telegram channel setup](./telegram.md)
475
523
  - [Channel file uploads](./attachments.md)
476
524
  - [Project Layout](../project-layout.md)
477
525
  - [TypeScript API](../typescript-api.md)
@@ -132,19 +132,27 @@ Component and modal submissions resume the parked Ash session automatically.
132
132
 
133
133
  ## Proactive Sessions
134
134
 
135
- Use `receive(discord, args)` from schedules or another channel to start a Discord session:
135
+ Use `receive(discord, { message, args, auth })` from a schedule `run` handler, or
136
+ `args.receive(discord, ...)` from another channel, to start a Discord session:
136
137
 
137
138
  ```ts
138
- import { defineSchedule, receive } from "experimental-ash/schedules";
139
+ import { defineSchedule } from "experimental-ash/schedules";
139
140
  import discord from "../channels/discord.js";
140
141
 
141
142
  export default defineSchedule({
142
143
  cron: "0 9 * * 1-5",
143
- markdown: "Post the daily summary.",
144
- channel: receive(discord, {
145
- channelId: "123456789012345678",
146
- initialMessage: "Daily summary",
147
- }),
144
+ async run({ receive, waitUntil, appAuth }) {
145
+ waitUntil(
146
+ receive(discord, {
147
+ message: "Post the daily summary.",
148
+ args: {
149
+ channelId: "123456789012345678",
150
+ initialMessage: "Daily summary",
151
+ },
152
+ auth: appAuth,
153
+ }),
154
+ );
155
+ },
148
156
  });
149
157
  ```
150
158
 
@@ -0,0 +1,121 @@
1
+ ---
2
+ title: "Microsoft Teams channel setup"
3
+ description: "Create a Microsoft Teams-backed Ash channel with the Bot Framework Activity protocol."
4
+ ---
5
+
6
+ # Microsoft Teams Channel Setup
7
+
8
+ The Teams channel accepts Bot Framework Activity POSTs from Microsoft Teams, verifies Bot
9
+ Connector bearer JWTs, dispatches message activities to Ash, renders HITL prompts as Adaptive
10
+ Cards, and delivers agent responses through the Bot Framework Connector REST API.
11
+
12
+ ## Add The Channel
13
+
14
+ Create `agent/channels/teams.ts`:
15
+
16
+ ```ts
17
+ import { teamsChannel } from "experimental-ash/channels/teams";
18
+
19
+ export default teamsChannel();
20
+ ```
21
+
22
+ Set the credentials Ash needs:
23
+
24
+ ```bash
25
+ MICROSOFT_APP_ID=...
26
+ MICROSOFT_APP_PASSWORD=...
27
+ # Optional for single-tenant bots:
28
+ MICROSOFT_TENANT_ID=...
29
+ ```
30
+
31
+ By default, the channel mounts `POST /ash/v1/teams`. Configure your Azure Bot or Teams app
32
+ messaging endpoint to that public URL.
33
+
34
+ Use `route` when the bot endpoint needs a different path:
35
+
36
+ ```ts
37
+ export default teamsChannel({
38
+ route: "/api/teams/activity",
39
+ });
40
+ ```
41
+
42
+ ## Message Dispatch
43
+
44
+ The default `onMessage` behavior dispatches personal-chat messages and channel/group-chat messages
45
+ that directly mention the bot. Ambient messages delivered through Teams resource-specific consent
46
+ are parsed but ignored unless you override `onMessage`.
47
+
48
+ ```ts
49
+ import { teamsChannel, defaultTeamsAuth } from "experimental-ash/channels/teams";
50
+
51
+ export default teamsChannel({
52
+ onMessage(ctx, message) {
53
+ if (message.scope !== "personal" && !message.isBotMentioned) return null;
54
+ return { auth: defaultTeamsAuth(message) };
55
+ },
56
+ });
57
+ ```
58
+
59
+ Ash strips the bot mention from channel/group prompts, adds a compact `<teams_context>` block, and
60
+ keeps personal chats scoped to one Ash session per Teams conversation. Channel and group-chat
61
+ threads are scoped by the root activity id (`replyToId ?? id`).
62
+
63
+ ## Delivery And HITL
64
+
65
+ Default delivery posts Markdown messages with `textFormat: "markdown"`, splits oversized text, and
66
+ sends typing indicators on turn start and action requests. `input.requested` renders Adaptive Cards:
67
+ buttons/options use `Action.Submit`, select requests use `Input.ChoiceSet`, and freeform requests
68
+ use `Input.Text`.
69
+
70
+ Adaptive Card submit activities are converted into Ash `inputResponses` automatically. Non-HITL
71
+ invoke activities can be handled with `onInvoke(ctx, activity)`.
72
+
73
+ ## Proactive Sessions
74
+
75
+ Use `receive(teams, args)` only when you already have a Teams conversation reference. Teams v1 does
76
+ not create new chats by AAD user id.
77
+
78
+ ```ts
79
+ import { defineSchedule, receive } from "experimental-ash/schedules";
80
+ import teams from "../channels/teams.js";
81
+
82
+ export default defineSchedule({
83
+ cron: "0 9 * * 1-5",
84
+ markdown: "Post the daily summary.",
85
+ channel: receive(teams, {
86
+ serviceUrl: "https://smba.trafficmanager.net/teams",
87
+ conversationId: "19:...",
88
+ conversationType: "channel",
89
+ tenantId: "tenant-id",
90
+ initialMessage: "Daily summary",
91
+ }),
92
+ });
93
+ ```
94
+
95
+ For channel/group-chat proactive sessions without `replyToActivityId`, the first posted message
96
+ becomes the thread anchor and the channel re-keys the Ash continuation token to that activity id.
97
+
98
+ ## Files
99
+
100
+ Text support is enabled by default. Inbound file parts are opt-in:
101
+
102
+ ```ts
103
+ export default teamsChannel({
104
+ files: {
105
+ enabled: true,
106
+ allowedHosts: ["contoso.sharepoint.com"],
107
+ },
108
+ });
109
+ ```
110
+
111
+ This covers personal-scope Teams file download attachments and simple public media URLs. Graph-based
112
+ channel file retrieval and outbound file-consent upload are intentionally outside v1.
113
+
114
+ ## Teams References
115
+
116
+ - [Teams conversational bots](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/build-conversational-capability)
117
+ - [Teams channel and group chat bot conversations](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/channel-and-group-conversations)
118
+ - [Bot Connector authentication](https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-authentication?view=azure-bot-service-4.0)
119
+ - [Bot Connector REST API reference](https://learn.microsoft.com/sr-latn-rs/azure/bot-service/rest-api/bot-framework-rest-connector-api-reference?view=azure-bot-service-4.0)
120
+ - [Teams proactive messages](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/send-proactive-messages)
121
+ - [Teams card actions](https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/cards-actions)
@@ -0,0 +1,201 @@
1
+ ---
2
+ title: "Telegram channel setup"
3
+ description: "Create a Telegram-backed Ash channel for bot webhooks, replies, HITL, and attachments."
4
+ ---
5
+
6
+ # Telegram Channel Setup
7
+
8
+ The Telegram channel accepts Telegram Bot API webhooks. It verifies Telegram's
9
+ `X-Telegram-Bot-Api-Secret-Token` header before parsing the update, dispatches private messages and
10
+ addressed group messages, and sends default replies through `sendMessage`.
11
+
12
+ ## Add The Channel
13
+
14
+ Create `agent/channels/telegram.ts`:
15
+
16
+ ```ts
17
+ import { telegramChannel } from "experimental-ash/channels/telegram";
18
+
19
+ export default telegramChannel({
20
+ botUsername: "my_bot",
21
+ });
22
+ ```
23
+
24
+ Set the credentials Ash needs for inbound verification and outbound replies:
25
+
26
+ ```bash
27
+ TELEGRAM_BOT_TOKEN=123456:...
28
+ TELEGRAM_WEBHOOK_SECRET_TOKEN=...
29
+ ```
30
+
31
+ - `TELEGRAM_BOT_TOKEN` sends replies, typing indicators, callback acknowledgements, and proactive
32
+ messages.
33
+ - `TELEGRAM_WEBHOOK_SECRET_TOKEN` must match the `secret_token` value you register with Telegram's
34
+ `setWebhook`.
35
+
36
+ You can also pass the same values in config:
37
+
38
+ ```ts
39
+ export default telegramChannel({
40
+ botUsername: "my_bot",
41
+ credentials: {
42
+ botToken: process.env.TELEGRAM_BOT_TOKEN,
43
+ webhookSecretToken: process.env.TELEGRAM_WEBHOOK_SECRET_TOKEN,
44
+ },
45
+ });
46
+ ```
47
+
48
+ By default, the channel mounts `POST /ash/v1/telegram`.
49
+
50
+ ## Register The Webhook
51
+
52
+ Register the deployed public URL with Telegram's Bot API:
53
+
54
+ ```bash
55
+ curl -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" \
56
+ -H "Content-Type: application/json" \
57
+ -d '{
58
+ "url": "https://your-app.example.com/ash/v1/telegram",
59
+ "secret_token": "'"$TELEGRAM_WEBHOOK_SECRET_TOKEN"'",
60
+ "allowed_updates": ["message", "callback_query"]
61
+ }'
62
+ ```
63
+
64
+ Webhook setup is external to Ash; the runtime does not call `setWebhook` for you.
65
+
66
+ ## Message Dispatch
67
+
68
+ Private chats dispatch normal text, captions, photos, and documents by default. The default
69
+ `onMessage` derives Telegram user auth, starts a typing indicator, and returns `{ auth }`.
70
+
71
+ Groups and supergroups are gated more narrowly:
72
+
73
+ - bot commands dispatch, including `/ask` and `/ask@my_bot`
74
+ - `@my_bot` mentions dispatch when `botUsername` is configured
75
+ - replies to bot messages dispatch
76
+ - unaddressed group chatter is ignored
77
+
78
+ For Telegram forum topics, the channel includes `message_thread_id` in the continuation token and
79
+ outbound messages so separate topics do not collapse into one session.
80
+
81
+ Override `onMessage` when you need custom auth or additional filtering:
82
+
83
+ ```ts
84
+ import { telegramChannel } from "experimental-ash/channels/telegram";
85
+
86
+ export default telegramChannel({
87
+ botUsername: "my_bot",
88
+ onMessage(ctx, message) {
89
+ if (message.chat.type !== "private" && !message.text.includes("@my_bot")) return null;
90
+ return {
91
+ auth: {
92
+ authenticator: "telegram",
93
+ principalType: "user",
94
+ principalId: message.from?.id ?? message.chat.id,
95
+ attributes: {
96
+ chat_id: message.chat.id,
97
+ chat_type: message.chat.type,
98
+ },
99
+ },
100
+ };
101
+ },
102
+ });
103
+ ```
104
+
105
+ Telegram group privacy is controlled by BotFather. With privacy mode enabled, Telegram only sends
106
+ commands, replies, and service messages to the bot. Disable privacy mode only if your bot should see
107
+ all group messages; Ash still applies the group dispatch gate above.
108
+
109
+ ## Delivery
110
+
111
+ The default `"message.completed"` handler sends plain text with `sendMessage`. Ash does not set
112
+ `parse_mode`, so generated Markdown is delivered as literal text rather than being parsed by
113
+ Telegram. Messages longer than Telegram's 4096-character text limit are split into multiple
114
+ `sendMessage` calls.
115
+
116
+ Default progress handlers call `sendChatAction` with `typing`. Typing failures are logged and
117
+ swallowed because the indicator is only a UX hint.
118
+
119
+ Custom event handlers can use `ctx.telegram`:
120
+
121
+ ```ts
122
+ export default telegramChannel({
123
+ events: {
124
+ async "message.completed"(event, ctx) {
125
+ if (event.finishReason === "tool-calls" || !event.message) return;
126
+ await ctx.telegram.sendMessage(`Result:\n${event.message}`);
127
+ },
128
+ },
129
+ });
130
+ ```
131
+
132
+ ## Human Input
133
+
134
+ Pending Ash input requests render in Telegram:
135
+
136
+ - option requests render as inline keyboard buttons
137
+ - freeform requests render as `ForceReply` prompts
138
+
139
+ Telegram caps `callback_data` at 64 bytes, so Ash stores compact callback ids in durable channel
140
+ state and remaps them to the original Ash input response when the callback query arrives. Freeform
141
+ answers resume when the user replies to the specific prompt message.
142
+
143
+ Callback queries generated by Ash are acknowledged with `answerCallbackQuery` automatically. Other
144
+ callback queries are passed to `onCallbackQuery` when you provide one.
145
+
146
+ ## Attachments
147
+
148
+ Inbound photos and documents become AI SDK file parts with internal `telegram-file:` URLs. When the
149
+ model needs the bytes, the channel calls Telegram `getFile`, downloads the file through the Bot API
150
+ file endpoint, and enforces the channel upload policy.
151
+
152
+ ```ts
153
+ export default telegramChannel({
154
+ uploadPolicy: {
155
+ allowedMediaTypes: ["image/*", "application/pdf"],
156
+ maxBytes: 10 * 1024 * 1024,
157
+ },
158
+ });
159
+ ```
160
+
161
+ V1 supports inbound photos and documents. Polling, inline mode, payments, business-account updates,
162
+ channel posts, stickers, and outbound sandbox-file sharing are not included.
163
+
164
+ ## Proactive Sessions
165
+
166
+ Use `receive(telegram, { message, args, auth })` from a schedule `run` handler, or
167
+ `args.receive(telegram, ...)` from another channel, to start a Telegram session:
168
+
169
+ ```ts
170
+ import { defineSchedule } from "experimental-ash/schedules";
171
+ import telegram from "../channels/telegram.js";
172
+
173
+ export default defineSchedule({
174
+ cron: "0 9 * * 1-5",
175
+ async run({ receive, waitUntil, appAuth }) {
176
+ waitUntil(
177
+ receive(telegram, {
178
+ message: "Post the daily summary.",
179
+ args: {
180
+ chatId: "123456789",
181
+ initialMessage: "Daily summary",
182
+ },
183
+ auth: appAuth,
184
+ }),
185
+ );
186
+ },
187
+ });
188
+ ```
189
+
190
+ `chatId` is required. Pass `messageThreadId` for forum topics. Pass `conversationId` to resume a
191
+ known group conversation, or `initialMessage` to post an anchor message first; those two options are
192
+ mutually exclusive.
193
+
194
+ ## Telegram References
195
+
196
+ - [Telegram Bot API](https://core.telegram.org/bots/api)
197
+ - [setWebhook](https://core.telegram.org/bots/api#setwebhook)
198
+ - [sendMessage](https://core.telegram.org/bots/api#sendmessage)
199
+ - [sendChatAction](https://core.telegram.org/bots/api#sendchataction)
200
+ - [answerCallbackQuery](https://core.telegram.org/bots/api#answercallbackquery)
201
+ - [getFile](https://core.telegram.org/bots/api#getfile)
@@ -30,7 +30,9 @@ for `defineAgent`.
30
30
  - `defineChannel(...)` - HTTP channel entrypoints in `channels/` with `routes`, `state`, `context()`, and typed event handlers (`experimental-ash/channels`)
31
31
  - `defineHook(...)` - lifecycle and stream-event subscriber in `hooks/<slug>.ts` (`experimental-ash/hooks`)
32
32
  - `defineSandbox(...)` - override the agent's single sandbox in `sandbox.ts` (or `sandbox/sandbox.ts` when paired with a `workspace/` folder) (`experimental-ash/sandbox`)
33
- - `defineSchedule(...)` - recurring jobs in `schedules/<name>.ts` or `schedules/<name>.md`; requires `cron` and `markdown`, `channel` is optional (`experimental-ash/schedules`)
33
+ - `defineSchedule(...)` - recurring jobs in `schedules/<name>.ts` or `schedules/<name>.md`;
34
+ TypeScript schedules require `cron` and exactly one of `markdown` or `run`
35
+ (`experimental-ash/schedules`)
34
36
  - `defineSkill(...)` - module-authored skills (`experimental-ash/skills`)
35
37
  - `defineInstructions(...)` - module-authored instructions prompt (`experimental-ash/instructions`)
36
38
  - `defineTool(...)` - typed executable integration in `tools/<name>.ts` (`experimental-ash/tools`)
@@ -109,6 +111,43 @@ Channel and Twilio types exported from `experimental-ash/channels/twilio`:
109
111
  `confidence`, ...)
110
112
  - `verifyTwilioRequest`, `signTwilioRequest` - Ash-owned Twilio webhook signature helpers
111
113
 
114
+ Channel and Telegram types exported from `experimental-ash/channels/telegram`:
115
+
116
+ - `telegramChannel` - Telegram Bot API webhook channel factory for messages, callback queries,
117
+ HITL, attachments, typing indicators, and proactive sessions
118
+ - `telegramContinuationToken` - helper for the channel-local raw token
119
+ (`chatId:messageThreadId:conversationId`)
120
+ - `TelegramChannelConfig` - config type for credentials, route, Bot API overrides, upload policy,
121
+ `botUsername`, hooks, and event handlers
122
+ - `TelegramContext` - pre-dispatch context for `onMessage` and `onCallbackQuery` (`telegram`)
123
+ - `TelegramEventContext` - event-handler context (`telegram`, `session`, plus mutable `state`)
124
+ - `TelegramHandle` - Telegram handle with `request()`, `post()`, `sendMessage()`,
125
+ `startTyping()`, `answerCallbackQuery()`, and `editMessageReplyMarkup()`
126
+ - `TelegramMessage` - parsed inbound message payload (`text`, `caption`, `chat`, `from`,
127
+ `attachments`, reply context, ...)
128
+ - `TelegramCallbackQuery` - parsed callback query payload passed to `onCallbackQuery`
129
+ - `TelegramAttachment` - parsed inbound photo or document metadata
130
+ - `TelegramReceiveArgs` - arguments accepted by proactive `receive(telegram, ...)`
131
+ - `verifyTelegramRequest` - Ash-owned webhook secret-token verification helper
132
+
133
+ Channel and Microsoft Teams types exported from `experimental-ash/channels/teams`:
134
+
135
+ - `teamsChannel` - Teams channel factory for Bot Framework Activity webhooks and Connector delivery
136
+ - `TeamsChannelConfig` - config type for credentials, route, event handlers, message/invoke hooks,
137
+ Adaptive Card version, and opt-in file handling
138
+ - `TeamsContext` - pre-dispatch context for `onMessage` and `onInvoke` (`thread`, `teams`)
139
+ - `TeamsEventContext` - event-handler context (`thread`, `teams`, mutable `state`)
140
+ - `TeamsHandle` - low-level Connector handle with `sendActivity`, `replyToActivity`,
141
+ `updateActivity`, `startTyping`, and `request`
142
+ - `TeamsThread` - conversation-scoped `post`, `update`, `startTyping`, and `mentionUser`
143
+ - `TeamsMessageActivity` / `TeamsInvokeActivity` - parsed inbound Activity payloads
144
+ - `TeamsReceiveArgs` - proactive/cross-channel handoff args requiring `serviceUrl` and
145
+ `conversationId`
146
+ - `teamsContinuationToken` - channel-local Teams continuation-token helper
147
+ - `defaultTeamsAuth` - default Teams actor-to-session-auth projection
148
+ - `verifyTeamsRequest`, `verifyTeamsJwt` - Ash-owned Bot Connector request/JWT verification helpers
149
+ - `sendTeamsActivity`, `replyToTeamsActivity`, `updateTeamsActivity` - Connector REST helpers
150
+
112
151
  Channel types exported from `experimental-ash/channels`:
113
152
 
114
153
  - `defineChannel` - channel primitive with `routes`, `state`, `context()`, and event handlers
@@ -138,6 +177,7 @@ import { defineChannel, POST, GET } from "experimental-ash/channels";
138
177
  import { ashChannel } from "experimental-ash/channels/ash";
139
178
  import { vercelOidc } from "experimental-ash/channels/auth";
140
179
  import { slackChannel } from "experimental-ash/channels/slack";
180
+ import { telegramChannel } from "experimental-ash/channels/telegram";
141
181
  ```
142
182
 
143
183
  Inside a route handler, the helpers object exposes:
@@ -1 +1 @@
1
- import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{ASH_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";var cachedPackageInfo,BUNDLED_FALLBACK_PACKAGE_VERSION=`0.27.0`,BUNDLED_FALLBACK_PACKAGE_VERSION_PLACEHOLDER=`__ASH_PACKAGE_VERSION_PLACEHOLDER__`,WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return BUNDLED_FALLBACK_PACKAGE_VERSION===BUNDLED_FALLBACK_PACKAGE_VERSION_PLACEHOLDER?`0.0.0`:BUNDLED_FALLBACK_PACKAGE_VERSION}var FALLBACK_PACKAGE_INFO={name:ASH_PACKAGE_NAME,version:resolveFallbackPackageVersion()};function resolveCurrentModulePath(){return typeof __filename==`string`?__filename:resolveCurrentModulePathFromStack()}function resolveCurrentModulePathFromStack(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(e,t)=>t;let e=Error().stack?.[0]?.getFileName();if(typeof e!=`string`||e.length===0)throw Error(`Failed to resolve the current module path from the stack trace.`);return e.startsWith(`file:`)?fileURLToPath(e):e}finally{Error.prepareStackTrace=e}}var require=createRequire(resolveCurrentModulePath());function isBuildOutputPackageRoot(e){return basename(e)===`dist`&&existsSync(join(dirname(e),`package.json`))}function resolvePackageBuildRoot(){let e=dirname(realpathSync(resolveCurrentModulePath()));for(;;){if(isBuildOutputPackageRoot(e))return e;let t=dirname(e);if(t===e)return null;e=t}}function findNearestPackageRoot(e){let t=e;for(;;){if(existsSync(join(t,`package.json`))&&!isBuildOutputPackageRoot(t))return t;let r=dirname(t);if(r===t)throw Error(`Failed to resolve package root from "${e}".`);t=r}}function resolvePackageRoot(){return findNearestPackageRoot(dirname(realpathSync(resolveCurrentModulePath())))}function tryResolvePackageRoot(){try{return resolvePackageRoot()}catch{return}}function rewriteSourceFilePathForBuild(e){return e.replace(/\.[cm]?tsx?$/,`.js`)}function resolvePackageSourceFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),e):join(t,rewriteSourceFilePathForBuild(e))}function resolvePackageSourceDirectoryPath(e){let t=resolvePackageBuildRoot();return join(t===null?resolvePackageRoot():t,e)}function resolvePackageCompiledFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),`.generated`,`compiled`,e.replace(/^src\/compiled\//,``)):join(t,e)}function normalizeInstalledPackageInfo(e){let t=e;if(!(typeof t.name!=`string`||typeof t.version!=`string`))return{name:t.name,version:t.version}}function tryReadInstalledPackageInfo(e,t){let n=normalizeInstalledPackageInfo(JSON.parse(readFileSync(e,`utf8`)));if(n?.name===t)return n}function resolveInstalledPackageInfo(){if(cachedPackageInfo)return cachedPackageInfo;let e=tryResolvePackageRoot(),t=e===void 0?void 0:tryReadInstalledPackageInfo(join(e,`package.json`),ASH_PACKAGE_NAME);if(t)return cachedPackageInfo=t,cachedPackageInfo;try{let e=tryReadInstalledPackageInfo(require.resolve(`${ASH_PACKAGE_NAME}/package.json`),ASH_PACKAGE_NAME);if(e)return cachedPackageInfo=e,cachedPackageInfo}catch{}return cachedPackageInfo={...FALLBACK_PACKAGE_INFO},cachedPackageInfo}function resolveWorkflowModulePath(e){if(e===`workflow`)return resolvePackageSourceFilePath(`src/internal/workflow/index.ts`);if(e===`workflow/internal/builtins`)return resolvePackageSourceFilePath(`src/internal/workflow/builtins.ts`);let t=WORKFLOW_MODULE_ALIASES[e];return t===void 0?require.resolve(e):resolvePackageCompiledFilePath(t)}export{resolveInstalledPackageInfo,resolvePackageRoot,resolvePackageSourceDirectoryPath,resolvePackageSourceFilePath,resolveWorkflowModulePath};
1
+ import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{ASH_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";var cachedPackageInfo,BUNDLED_FALLBACK_PACKAGE_VERSION=`0.28.0`,BUNDLED_FALLBACK_PACKAGE_VERSION_PLACEHOLDER=`__ASH_PACKAGE_VERSION_PLACEHOLDER__`,WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return BUNDLED_FALLBACK_PACKAGE_VERSION===BUNDLED_FALLBACK_PACKAGE_VERSION_PLACEHOLDER?`0.0.0`:BUNDLED_FALLBACK_PACKAGE_VERSION}var FALLBACK_PACKAGE_INFO={name:ASH_PACKAGE_NAME,version:resolveFallbackPackageVersion()};function resolveCurrentModulePath(){return typeof __filename==`string`?__filename:resolveCurrentModulePathFromStack()}function resolveCurrentModulePathFromStack(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(e,t)=>t;let e=Error().stack?.[0]?.getFileName();if(typeof e!=`string`||e.length===0)throw Error(`Failed to resolve the current module path from the stack trace.`);return e.startsWith(`file:`)?fileURLToPath(e):e}finally{Error.prepareStackTrace=e}}var require=createRequire(resolveCurrentModulePath());function isBuildOutputPackageRoot(e){return basename(e)===`dist`&&existsSync(join(dirname(e),`package.json`))}function resolvePackageBuildRoot(){let e=dirname(realpathSync(resolveCurrentModulePath()));for(;;){if(isBuildOutputPackageRoot(e))return e;let t=dirname(e);if(t===e)return null;e=t}}function findNearestPackageRoot(e){let t=e;for(;;){if(existsSync(join(t,`package.json`))&&!isBuildOutputPackageRoot(t))return t;let r=dirname(t);if(r===t)throw Error(`Failed to resolve package root from "${e}".`);t=r}}function resolvePackageRoot(){return findNearestPackageRoot(dirname(realpathSync(resolveCurrentModulePath())))}function tryResolvePackageRoot(){try{return resolvePackageRoot()}catch{return}}function rewriteSourceFilePathForBuild(e){return e.replace(/\.[cm]?tsx?$/,`.js`)}function resolvePackageSourceFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),e):join(t,rewriteSourceFilePathForBuild(e))}function resolvePackageSourceDirectoryPath(e){let t=resolvePackageBuildRoot();return join(t===null?resolvePackageRoot():t,e)}function resolvePackageCompiledFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),`.generated`,`compiled`,e.replace(/^src\/compiled\//,``)):join(t,e)}function normalizeInstalledPackageInfo(e){let t=e;if(!(typeof t.name!=`string`||typeof t.version!=`string`))return{name:t.name,version:t.version}}function tryReadInstalledPackageInfo(e,t){let n=normalizeInstalledPackageInfo(JSON.parse(readFileSync(e,`utf8`)));if(n?.name===t)return n}function resolveInstalledPackageInfo(){if(cachedPackageInfo)return cachedPackageInfo;let e=tryResolvePackageRoot(),t=e===void 0?void 0:tryReadInstalledPackageInfo(join(e,`package.json`),ASH_PACKAGE_NAME);if(t)return cachedPackageInfo=t,cachedPackageInfo;try{let e=tryReadInstalledPackageInfo(require.resolve(`${ASH_PACKAGE_NAME}/package.json`),ASH_PACKAGE_NAME);if(e)return cachedPackageInfo=e,cachedPackageInfo}catch{}return cachedPackageInfo={...FALLBACK_PACKAGE_INFO},cachedPackageInfo}function resolveWorkflowModulePath(e){if(e===`workflow`)return resolvePackageSourceFilePath(`src/internal/workflow/index.ts`);if(e===`workflow/internal/builtins`)return resolvePackageSourceFilePath(`src/internal/workflow/builtins.ts`);let t=WORKFLOW_MODULE_ALIASES[e];return t===void 0?require.resolve(e):resolvePackageCompiledFilePath(t)}export{resolveInstalledPackageInfo,resolvePackageRoot,resolvePackageSourceDirectoryPath,resolvePackageSourceFilePath,resolveWorkflowModulePath};
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Minimal Microsoft Teams Bot Framework Connector REST wrapper.
3
+ *
4
+ * The native Teams channel talks directly to the Bot Framework Activity
5
+ * protocol instead of exposing BotBuilder or Teams SDK objects through
6
+ * Ash public APIs.
7
+ */
8
+ import { type JsonObject } from "#shared/json.js";
9
+ /** Microsoft application id, materialized directly or from an async secret provider. */
10
+ export type TeamsAppId = string | (() => string | Promise<string>);
11
+ /** Microsoft application password, materialized directly or from an async secret provider. */
12
+ export type TeamsAppPassword = string | (() => string | Promise<string>);
13
+ /** Microsoft tenant id, materialized directly or from an async secret provider. */
14
+ export type TeamsTenantId = string | (() => string | Promise<string>);
15
+ /** Fetch implementation override used by tests or non-standard runtimes. */
16
+ export type TeamsFetch = typeof fetch;
17
+ /** Result shape accepted from a caller-owned Bot Connector token provider. */
18
+ export type TeamsAccessTokenResult = string | {
19
+ readonly accessToken: string;
20
+ readonly expiresAt?: Date | number;
21
+ };
22
+ /** Caller-owned Bot Connector token provider. */
23
+ export type TeamsTokenProvider = () => TeamsAccessTokenResult | Promise<TeamsAccessTokenResult>;
24
+ /** Credentials used by the native Teams channel. */
25
+ export interface TeamsCredentials {
26
+ readonly appId?: TeamsAppId;
27
+ readonly appPassword?: TeamsAppPassword;
28
+ readonly tenantId?: TeamsTenantId;
29
+ readonly tokenProvider?: TeamsTokenProvider;
30
+ }
31
+ /** Shared Teams API options. */
32
+ export interface TeamsApiOptions {
33
+ readonly credentials?: TeamsCredentials;
34
+ readonly fetch?: TeamsFetch;
35
+ readonly loginBaseUrl?: string;
36
+ }
37
+ /** Raw Teams Connector API response body. */
38
+ export interface TeamsApiResponse {
39
+ readonly status: number;
40
+ readonly ok: boolean;
41
+ readonly body: unknown;
42
+ }
43
+ /** Minimal ResourceResponse object returned by Teams write operations. */
44
+ export interface TeamsPostedActivity {
45
+ /** Teams/Bot Framework activity id, when one was returned. */
46
+ readonly id: string;
47
+ /** Connector's raw JSON response. */
48
+ readonly raw: unknown;
49
+ }
50
+ /** Bot Framework channel account shape surfaced by the native Teams channel. */
51
+ export interface TeamsChannelAccount {
52
+ readonly aadObjectId?: string;
53
+ readonly id: string;
54
+ readonly name?: string;
55
+ readonly role?: string;
56
+ }
57
+ /** Bot Framework mention entity shape used by Teams messages. */
58
+ export interface TeamsMention {
59
+ readonly mentioned: TeamsChannelAccount;
60
+ readonly text: string;
61
+ readonly type: "mention";
62
+ }
63
+ /** Bot Framework/Teams attachment shape supported by Ash-owned APIs. */
64
+ export interface TeamsAttachment {
65
+ readonly content?: JsonObject;
66
+ readonly contentType: string;
67
+ readonly contentUrl?: string;
68
+ readonly name?: string;
69
+ }
70
+ /** JSON body supported by Teams message endpoints used by Ash. */
71
+ export interface TeamsMessageBody {
72
+ readonly attachments?: readonly TeamsAttachment[];
73
+ readonly channelData?: JsonObject;
74
+ readonly entities?: readonly TeamsMention[];
75
+ readonly inputHint?: string;
76
+ readonly speak?: string;
77
+ readonly suggestedActions?: JsonObject;
78
+ readonly text?: string;
79
+ readonly textFormat?: "markdown" | "plain" | "xml";
80
+ }
81
+ /** Outbound Bot Framework activity body sent through the Connector API. */
82
+ export interface TeamsOutboundActivity extends TeamsMessageBody {
83
+ readonly conversation?: JsonObject;
84
+ readonly from?: TeamsChannelAccount;
85
+ readonly recipient?: TeamsChannelAccount;
86
+ readonly replyToId?: string;
87
+ readonly type: "message" | "typing";
88
+ }
89
+ /** Conservative text budget for one Teams bot message. */
90
+ export declare const TEAMS_MESSAGE_TEXT_MAX_LENGTH: number;
91
+ /** Builds the channel-local continuation token for one Teams conversation/thread. */
92
+ export declare function teamsContinuationToken(input: {
93
+ readonly conversationId: string;
94
+ readonly replyToActivityId?: string | null;
95
+ readonly tenantId?: string | null;
96
+ }): string;
97
+ /** Resolves a Teams app id, falling back to `MICROSOFT_APP_ID` then `TEAMS_APP_ID`. */
98
+ export declare function resolveTeamsAppId(appId?: TeamsAppId): Promise<string>;
99
+ /** Resolves a Teams app password, falling back to `MICROSOFT_APP_PASSWORD` then `TEAMS_APP_PASSWORD`. */
100
+ export declare function resolveTeamsAppPassword(appPassword?: TeamsAppPassword): Promise<string>;
101
+ /** Resolves a Teams tenant id, falling back to `MICROSOFT_TENANT_ID` then `TEAMS_TENANT_ID`. */
102
+ export declare function resolveTeamsTenantId(tenantId?: TeamsTenantId): Promise<string | undefined>;
103
+ /** Resolves a Bot Connector access token, using a custom provider or client credentials. */
104
+ export declare function resolveTeamsAccessToken(options?: TeamsApiOptions): Promise<string>;
105
+ /** Low-level Bot Framework Connector API call. */
106
+ export declare function callTeamsConnectorApi(input: TeamsApiOptions & {
107
+ readonly body?: TeamsOutboundActivity | JsonObject;
108
+ readonly method?: "DELETE" | "GET" | "POST" | "PUT";
109
+ readonly path: string;
110
+ readonly serviceUrl: string;
111
+ }): Promise<TeamsApiResponse>;
112
+ /** Sends a non-reply activity to one Teams conversation. */
113
+ export declare function sendTeamsActivity(input: TeamsApiOptions & {
114
+ readonly body: TeamsOutboundActivity;
115
+ readonly conversationId: string;
116
+ readonly serviceUrl: string;
117
+ }): Promise<TeamsPostedActivity>;
118
+ /** Sends a reply activity to one Teams conversation activity. */
119
+ export declare function replyToTeamsActivity(input: TeamsApiOptions & {
120
+ readonly activityId: string;
121
+ readonly body: TeamsOutboundActivity;
122
+ readonly conversationId: string;
123
+ readonly serviceUrl: string;
124
+ }): Promise<TeamsPostedActivity>;
125
+ /** Updates an existing Teams bot activity. */
126
+ export declare function updateTeamsActivity(input: TeamsApiOptions & {
127
+ readonly activityId: string;
128
+ readonly body: TeamsOutboundActivity;
129
+ readonly conversationId: string;
130
+ readonly serviceUrl: string;
131
+ }): Promise<TeamsPostedActivity>;
132
+ /** Triggers Teams' typing indicator for one conversation. */
133
+ export declare function triggerTeamsTypingIndicator(input: TeamsApiOptions & {
134
+ readonly conversationId: string;
135
+ readonly serviceUrl: string;
136
+ }): Promise<void>;
137
+ /** Splits text into chunks that fit Teams' conservative message-size budget. */
138
+ export declare function splitTeamsMessageText(content: string): readonly string[];
139
+ /** Normalizes a string or message body into a Teams message activity body. */
140
+ export declare function normalizeTeamsPostInput(message: string | TeamsMessageBody): TeamsMessageBody;