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,4 @@
1
+ import{isObject}from"#shared/guards.js";import{parseJsonObject}from"#shared/json.js";var TEAMS_MESSAGE_TEXT_MAX_LENGTH=80*1024,DEFAULT_LOGIN_BASE_URL=`https://login.microsoftonline.com`,BOT_FRAMEWORK_TENANT=`botframework.com`,BOT_FRAMEWORK_SCOPE=`https://api.botframework.com/.default`,accessTokenCache=new Map;function teamsContinuationToken(e){return[e.tenantId??`_`,e.conversationId,e.replyToActivityId??``].map(e=>encodeURIComponent(e)).join(`:`)}async function resolveTeamsAppId(e){let t=e??process.env.MICROSOFT_APP_ID??process.env.TEAMS_APP_ID;if(!t)throw Error(`MICROSOFT_APP_ID or TEAMS_APP_ID is required.`);return typeof t==`function`?await t():t}async function resolveTeamsAppPassword(e){let t=e??process.env.MICROSOFT_APP_PASSWORD??process.env.TEAMS_APP_PASSWORD;if(!t)throw Error(`MICROSOFT_APP_PASSWORD or TEAMS_APP_PASSWORD is required.`);return typeof t==`function`?await t():t}async function resolveTeamsTenantId(e){let t=e??process.env.MICROSOFT_TENANT_ID??process.env.TEAMS_TENANT_ID;if(t)return typeof t==`function`?await t():t}async function resolveTeamsAccessToken(t={}){let n=t.credentials;if(n?.tokenProvider!==void 0)return normalizeAccessTokenResult(await n.tokenProvider()).accessToken;let o=await resolveTeamsAppId(n?.appId),s=await resolveTeamsAppPassword(n?.appPassword),c=await resolveTeamsTenantId(n?.tenantId),l=trimTrailingSlash(t.loginBaseUrl??DEFAULT_LOGIN_BASE_URL),u=`${l}:${c??BOT_FRAMEWORK_TENANT}:${o}`,d=accessTokenCache.get(u),f=Date.now();if(d!==void 0&&d.expiresAt-6e4>f)return d.accessToken;let p=`${l}/${encodeURIComponent(c??BOT_FRAMEWORK_TENANT)}/oauth2/v2.0/token`,m=new URLSearchParams({client_id:o,client_secret:s,grant_type:`client_credentials`,scope:BOT_FRAMEWORK_SCOPE}),h=await(t.fetch??fetch)(p,{body:m,headers:{"content-type":`application/x-www-form-urlencoded`},method:`POST`}),g=await parseResponseBody(h);if(!h.ok)throw Error(`Teams access-token request failed with HTTP ${h.status}.`);let _=isObject(g)?g:{},v=typeof _.access_token==`string`?_.access_token:``;if(!v)throw Error(`Teams access-token response did not include access_token.`);let y=typeof _.expires_in==`number`?_.expires_in:3600;return accessTokenCache.set(u,{accessToken:v,expiresAt:f+y*1e3}),v}async function callTeamsConnectorApi(e){let n=e.fetch??fetch,r=new Headers;r.set(`authorization`,`Bearer ${await resolveTeamsAccessToken(e)}`),r.set(`content-type`,`application/json; charset=utf-8`);let i={headers:r,method:e.method??`POST`};e.body!==void 0&&(i.body=JSON.stringify(parseJsonObject(e.body)));let a=await n(`${trimTrailingSlash(e.serviceUrl)}${e.path}`,i);return{body:await parseResponseBody(a),ok:a.ok,status:a.status}}async function sendTeamsActivity(e){let t=await callTeamsConnectorApi({...e,body:e.body,path:`/v3/conversations/${encodeURIComponent(e.conversationId)}/activities`});if(!t.ok)throw Error(`Teams send activity failed with HTTP ${t.status}.`);return toPostedActivity(t.body)}async function replyToTeamsActivity(e){let t=await callTeamsConnectorApi({...e,body:e.body,path:`/v3/conversations/${encodeURIComponent(e.conversationId)}/activities/${encodeURIComponent(e.activityId)}`});if(!t.ok)throw Error(`Teams reply activity failed with HTTP ${t.status}.`);return toPostedActivity(t.body)}async function updateTeamsActivity(e){let t=await callTeamsConnectorApi({...e,body:e.body,method:`PUT`,path:`/v3/conversations/${encodeURIComponent(e.conversationId)}/activities/${encodeURIComponent(e.activityId)}`});if(!t.ok)throw Error(`Teams update activity failed with HTTP ${t.status}.`);return toPostedActivity(t.body)}async function triggerTeamsTypingIndicator(e){let t=await callTeamsConnectorApi({...e,body:{type:`typing`},path:`/v3/conversations/${encodeURIComponent(e.conversationId)}/activities`});if(!t.ok)throw Error(`Teams typing indicator failed with HTTP ${t.status}.`)}function splitTeamsMessageText(e){if(e.length<=81920)return[e];let t=[],r=e;for(;r.length>TEAMS_MESSAGE_TEXT_MAX_LENGTH;){let e=r.lastIndexOf(`
2
+
3
+ `,TEAMS_MESSAGE_TEXT_MAX_LENGTH);e<=0&&(e=r.lastIndexOf(`
4
+ `,TEAMS_MESSAGE_TEXT_MAX_LENGTH)),e<=0&&(e=r.lastIndexOf(` `,TEAMS_MESSAGE_TEXT_MAX_LENGTH)),e<=0&&(e=TEAMS_MESSAGE_TEXT_MAX_LENGTH),t.push(r.slice(0,e).trimEnd()),r=r.slice(e).trimStart()}return t.push(r),t}function normalizeTeamsPostInput(e){return typeof e==`string`?{text:e,textFormat:`markdown`}:e}function normalizeAccessTokenResult(e){if(typeof e==`string`)return{accessToken:e};let t=e.expiresAt instanceof Date?e.expiresAt.getTime():e.expiresAt;return{accessToken:e.accessToken,expiresAt:t}}function toPostedActivity(t){let n=isObject(t)?t:{};return{id:typeof n.id==`string`?n.id:``,raw:t}}function trimTrailingSlash(e){return e.replace(/\/+$/,``)}async function parseResponseBody(e){let t=await e.text();if(!t)return null;try{return JSON.parse(t)}catch{return t}}export{TEAMS_MESSAGE_TEXT_MAX_LENGTH,callTeamsConnectorApi,normalizeTeamsPostInput,replyToTeamsActivity,resolveTeamsAccessToken,resolveTeamsAppId,resolveTeamsAppPassword,resolveTeamsTenantId,sendTeamsActivity,splitTeamsMessageText,teamsContinuationToken,triggerTeamsTypingIndicator,updateTeamsActivity};
@@ -0,0 +1,25 @@
1
+ import type { FilePart, UserContent } from "ai";
2
+ import type { FetchFileResult } from "#channel/adapter.js";
3
+ import type { TeamsAttachment } from "#public/channels/teams/api.js";
4
+ import { type UploadPolicyInput } from "#public/channels/upload-policy.js";
5
+ import type { UploadPolicy } from "#public/channels/upload-policy.js";
6
+ /** File handling options for the native Teams channel. */
7
+ export interface TeamsFilesConfig {
8
+ readonly allowedHosts?: readonly string[] | "*";
9
+ readonly enabled?: boolean;
10
+ readonly uploadPolicy?: UploadPolicyInput;
11
+ }
12
+ /** Normalized file handling policy used by the Teams channel. */
13
+ export interface TeamsFilesPolicy {
14
+ readonly allowedHosts: readonly string[] | "*";
15
+ readonly enabled: boolean;
16
+ readonly uploadPolicy: UploadPolicy;
17
+ }
18
+ /** Normalizes author-provided Teams file options. */
19
+ export declare function normalizeTeamsFilesPolicy(config: TeamsFilesConfig | undefined): TeamsFilesPolicy;
20
+ /** Collects Teams attachment file parts when file support is explicitly enabled. */
21
+ export declare function collectTeamsFileParts(attachments: readonly TeamsAttachment[], policy: TeamsFilesPolicy): FilePart[];
22
+ /** Combines text + file parts into the UserContent shape expected by the harness. */
23
+ export declare function buildTeamsTurnMessage(text: string, fileParts: readonly FilePart[]): string | UserContent;
24
+ /** Creates a fetchFile function for Teams URL file parts. */
25
+ export declare function createTeamsFetchFile(policy: TeamsFilesPolicy): (url: string) => Promise<FetchFileResult | null>;
@@ -0,0 +1 @@
1
+ import{createLogger}from"#internal/logging.js";import{isObject}from"#shared/guards.js";import{evaluateFilePart,formatUploadPolicyViolation,isUploadsDisabled,mergeUploadPolicy}from"#public/channels/upload-policy.js";var log=createLogger(`teams.attachments`);function normalizeTeamsFilesPolicy(e){return{allowedHosts:e?.allowedHosts??[],enabled:e?.enabled===!0,uploadPolicy:mergeUploadPolicy(e?.uploadPolicy)}}function collectTeamsFileParts(e,t){if(!t.enabled||isUploadsDisabled(t.uploadPolicy))return[];let a=[];for(let i of e){let e=toTeamsFilePart(i,a.length,t);if(e===null)continue;let o=evaluateFilePart(e,t.uploadPolicy);if(o!==null){log.warn(`dropped Teams attachment — ${formatUploadPolicyViolation(o)}`,{name:i.name});continue}a.push(e)}return a}function buildTeamsTurnMessage(e,t){return t.length===0?e:e.trim().length===0?[...t]:[{type:`text`,text:e},...t]}function createTeamsFetchFile(e){return async t=>{if(!e.enabled||!isAllowedUrl(t,e.allowedHosts))return null;let n=await fetch(t);if(!n.ok)throw Error(`Teams file fetch returned HTTP ${n.status} for ${t}.`);return{bytes:Buffer.from(await n.arrayBuffer()),mediaType:n.headers.get(`content-type`)??void 0}}}function toTeamsFilePart(e,t,n){let r=readAttachmentUrl(e);return!r||!isAllowedUrl(r,n.allowedHosts)?null:{data:new URL(r),filename:e.name??`teams-attachment-${t}`,mediaType:inferMediaType(e),type:`file`}}function readAttachmentUrl(e){return e.contentType===`application/vnd.microsoft.teams.file.download.info`&&isObject(e.content)&&typeof e.content.downloadUrl==`string`?e.content.downloadUrl:e.contentUrl&&!e.contentType.startsWith(`application/vnd.microsoft.card.`)?e.contentUrl:null}function inferMediaType(e){return e.contentType===`application/vnd.microsoft.teams.file.download.info`&&(isObject(e.content)&&typeof e.content.fileType==`string`?e.content.fileType:void 0)===`txt`?`text/plain`:e.contentType||`application/octet-stream`}function isAllowedUrl(e,t){if(t===`*`)return!0;let n;try{n=new URL(e)}catch{return!1}return t.some(e=>n.host===e||n.hostname===e)}export{buildTeamsTurnMessage,collectTeamsFileParts,createTeamsFetchFile,normalizeTeamsFilesPolicy};
@@ -0,0 +1,24 @@
1
+ import type { SessionAuthContext } from "#channel/types.js";
2
+ import type { ConnectionAuthorizationOutcome } from "#protocol/message.js";
3
+ import { type TeamsMention } from "#public/channels/teams/api.js";
4
+ import type { TeamsMessageActivity } from "#public/channels/teams/inbound.js";
5
+ import type { TeamsChannelEvents, TeamsContext, TeamsInboundResult } from "#public/channels/teams/teamsChannel.js";
6
+ /** Default auth projection for Teams message actors. */
7
+ export declare function defaultTeamsAuth(message: TeamsMessageActivity): SessionAuthContext;
8
+ /** Default message hook: mention-gated dispatch with Teams user auth. */
9
+ export declare function defaultOnMessage(ctx: TeamsContext, message: TeamsMessageActivity): Promise<TeamsInboundResult>;
10
+ /** Built-in Teams event handlers for typing, replies, HITL, auth cards, and terminal errors. */
11
+ export declare const defaultEvents: TeamsChannelEvents;
12
+ /** Title-cases a connection name for Teams auth card display. */
13
+ export declare function formatConnectionDisplayName(connectionName: string): string;
14
+ /** Builds final-state text for a completed connection authorization attempt. */
15
+ export declare function buildAuthCompletedText(input: {
16
+ readonly displayName: string;
17
+ readonly outcome: ConnectionAuthorizationOutcome;
18
+ readonly reason?: string;
19
+ }): string;
20
+ /** Builds a Teams mention entity and matching text for one channel account. */
21
+ export declare function teamsMentionUser(user: {
22
+ readonly id: string;
23
+ readonly name?: string;
24
+ }): TeamsMention;
@@ -0,0 +1,3 @@
1
+ import{extractErrorId,formatErrorHint}from"#internal/logging.js";import{parseJsonObject}from"#shared/json.js";import{splitTeamsMessageText}from"#public/channels/teams/api.js";import{renderAnsweredInputRequestMessage,renderInputRequestMessage}from"#public/channels/teams/hitl.js";function defaultTeamsAuth(e){let t=e.tenantId,n={activity_id:e.id,conversation_id:e.conversation.id,scope:e.scope,user_id:e.from.id};e.from.name!==void 0&&(n.user_name=e.from.name),e.from.aadObjectId!==void 0&&(n.aad_object_id=e.from.aadObjectId),t!==void 0&&(n.tenant_id=t),e.teamId!==void 0&&(n.team_id=e.teamId),e.teamsChannelId!==void 0&&(n.channel_id=e.teamsChannelId);let r=t?`teams:${t}:${e.from.id}`:`teams:${e.from.id}`;return{attributes:n,authenticator:`teams-activity`,issuer:t?`teams:${t}`:`teams`,principalId:r,principalType:e.from.role===`bot`?`service`:`user`,subject:e.from.aadObjectId}}async function defaultOnMessage(e,t){return t.scope!==`personal`&&!t.isBotMentioned?null:(await e.thread.startTyping(),{auth:defaultTeamsAuth(t)})}var defaultEvents={async"turn.started"(e,t){await t.thread.startTyping()},async"actions.requested"(e,t){await t.thread.startTyping()},async"input.requested"(e,t){for(let n of e.requests)await t.thread.post(renderInputRequestMessage(n,{adaptiveCardVersion:t.adaptiveCardVersion}))},async"message.completed"(e,t){if(!(e.finishReason===`tool-calls`||!e.message))for(let n of splitTeamsMessageText(e.message))await t.thread.post(n)},async"session.failed"(n,r){let i=formatErrorHint(n),a=extractErrorId(n.details);await r.thread.post([`This session could not recover from an error${i}.`,``,`Start a new Teams conversation to continue.`,...a?[``,`Error id: ${a}`]:[]].join(`
2
+ `))},async"turn.failed"(n,r){let i=formatErrorHint(n),a=extractErrorId(n.details);await r.thread.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(`
3
+ `))},async"connection.authorization_required"(e,t){let r=formatConnectionDisplayName(e.connectionName),i=e.authorization?.url,a=i?`Authorization required for ${r}: ${i}`:`Authorization required for ${r}.`,o=await t.thread.post({attachments:[{content:parseJsonObject({$schema:`http://adaptivecards.io/schemas/adaptive-card.json`,actions:i?[{title:`Sign in with ${r}`,type:`Action.OpenUrl`,url:i}]:[],body:[{text:`Authorization required for ${r}`,type:`TextBlock`,weight:`Bolder`,wrap:!0},{text:t.state.triggeringUser?`Requested by ${t.state.triggeringUser.name??t.state.triggeringUser.id}.`:`No triggering user is available for a private prompt.`,type:`TextBlock`,wrap:!0}],type:`AdaptiveCard`,version:t.adaptiveCardVersion}),contentType:`application/vnd.microsoft.card.adaptive`}],text:a});o.id&&(t.state.pendingAuthActivityId=o.id)},async"connection.authorization_completed"(e,t){let n=t.state.pendingAuthActivityId;if(!n)return;let r=buildAuthCompletedText({displayName:formatConnectionDisplayName(e.connectionName),outcome:e.outcome,reason:e.reason});await t.thread.update(n,renderAnsweredInputRequestMessage({prompt:r})),t.state.pendingAuthActivityId=null}};function formatConnectionDisplayName(e){return e.length===0?e:e.charAt(0).toUpperCase()+e.slice(1)}function buildAuthCompletedText(e){if(e.outcome===`authorized`)return`${e.displayName} connected.`;let t=e.reason===void 0?``:` (${e.reason})`;return`${e.displayName} authorization ${e.outcome}${t}.`}function teamsMentionUser(e){let t=e.name??e.id;return{mentioned:{id:e.id,name:e.name},text:`<at>${t}</at>`,type:`mention`}}export{buildAuthCompletedText,defaultEvents,defaultOnMessage,defaultTeamsAuth,formatConnectionDisplayName,teamsMentionUser};
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Teams Adaptive Card rendering and decode helpers for Ash HITL prompts.
3
+ */
4
+ import type { InputRequest, InputResponse } from "#runtime/input/types.js";
5
+ import type { TeamsActivity } from "#public/channels/teams/inbound.js";
6
+ import type { TeamsAttachment, TeamsMessageBody } from "#public/channels/teams/api.js";
7
+ /** Adaptive Card attachment content type used by Teams. */
8
+ export declare const TEAMS_ADAPTIVE_CARD_CONTENT_TYPE = "application/vnd.microsoft.card.adaptive";
9
+ /** Hidden data property used by Ash HITL Adaptive Card actions. */
10
+ export declare const TEAMS_HITL_DATA_KEY = "ash_input";
11
+ /** ChoiceSet input id used for select-style HITL requests. */
12
+ export declare const TEAMS_HITL_CHOICE_INPUT_ID = "ash_option";
13
+ /** Text input id used for freeform HITL requests. */
14
+ export declare const TEAMS_HITL_FREEFORM_INPUT_ID = "ash_freeform_text";
15
+ /** Renders one input request as a Teams message body containing an Adaptive Card. */
16
+ export declare function renderInputRequestMessage(request: InputRequest, options?: {
17
+ readonly adaptiveCardVersion?: string;
18
+ }): TeamsMessageBody;
19
+ /** Renders one input request as a Teams Adaptive Card attachment. */
20
+ export declare function renderInputRequestAttachment(request: InputRequest, options?: {
21
+ readonly adaptiveCardVersion?: string;
22
+ }): TeamsAttachment;
23
+ /** Renders an answered Teams card that replaces a pending HITL prompt. */
24
+ export declare function renderAnsweredInputRequestMessage(input: {
25
+ readonly label?: string;
26
+ readonly prompt: string;
27
+ }): TeamsMessageBody;
28
+ /** Returns true when a Teams activity carries an Ash HITL submit payload. */
29
+ export declare function isTeamsInputResponseActivity(activity: TeamsActivity): boolean;
30
+ /** Decodes one Teams activity into HITL input responses. */
31
+ export declare function deriveTeamsInputResponses(activity: TeamsActivity): readonly InputResponse[];
32
+ /** Builds the HTTP body Teams expects after an Adaptive Card invoke action. */
33
+ export declare function teamsInvokeResponse(input?: {
34
+ readonly message?: string;
35
+ readonly statusCode?: number;
36
+ }): Record<string, unknown>;
@@ -0,0 +1 @@
1
+ import{isObject}from"#shared/guards.js";import{parseJsonObject}from"#shared/json.js";import{TEAMS_ADAPTIVE_CARD_ACTION_LIMIT,TEAMS_ADAPTIVE_CARD_ACTION_TITLE_MAX_LENGTH,TEAMS_ADAPTIVE_CARD_CHOICE_TITLE_MAX_LENGTH,TEAMS_ADAPTIVE_CARD_TEXT_MAX_LENGTH}from"#public/channels/teams/limits.js";var TEAMS_ADAPTIVE_CARD_CONTENT_TYPE=`application/vnd.microsoft.card.adaptive`,TEAMS_HITL_DATA_KEY=`ash_input`,TEAMS_HITL_CHOICE_INPUT_ID=`ash_option`,TEAMS_HITL_FREEFORM_INPUT_ID=`ash_freeform_text`;function renderInputRequestMessage(e,t={}){return{attachments:[renderInputRequestAttachment(e,t)],text:e.prompt}}function renderInputRequestAttachment(e,n={}){return{content:parseJsonObject({$schema:`http://adaptivecards.io/schemas/adaptive-card.json`,actions:renderActions(e),body:[{text:truncate(e.prompt,TEAMS_ADAPTIVE_CARD_TEXT_MAX_LENGTH),type:`TextBlock`,wrap:!0},...renderInputs(e)],type:`AdaptiveCard`,version:n.adaptiveCardVersion??`1.5`}),contentType:TEAMS_ADAPTIVE_CARD_CONTENT_TYPE}}function renderAnsweredInputRequestMessage(e){return{attachments:[{content:parseJsonObject({$schema:`http://adaptivecards.io/schemas/adaptive-card.json`,body:[{text:truncate(e.prompt,TEAMS_ADAPTIVE_CARD_TEXT_MAX_LENGTH),type:`TextBlock`,wrap:!0},{color:`good`,text:e.label?`Answered: ${e.label}`:`Answered`,type:`TextBlock`,wrap:!0}],type:`AdaptiveCard`,version:`1.5`}),contentType:TEAMS_ADAPTIVE_CARD_CONTENT_TYPE}],text:e.label?`Answered: ${e.label}`:`Answered`}}function isTeamsInputResponseActivity(e){return deriveTeamsInputResponses(e).length>0}function deriveTeamsInputResponses(e){let t=readActivityValue(e);if(!t)return[];let n=readHitlPayload(t);if(!n)return[];let r=typeof n.requestId==`string`?n.requestId:``;if(!r)return[];let i=typeof n.optionId==`string`?n.optionId:typeof t.ash_option==`string`?t[TEAMS_HITL_CHOICE_INPUT_ID]:void 0,a=typeof t.ash_freeform_text==`string`?t[TEAMS_HITL_FREEFORM_INPUT_ID]:void 0;return i===void 0?a===void 0?[{requestId:r}]:[{requestId:r,text:a}]:[{optionId:i,requestId:r}]}function teamsInvokeResponse(e={}){return{statusCode:e.statusCode??200,type:`application/vnd.microsoft.activity.message`,value:e.message??`Answer received.`}}function renderInputs(e){return e.display===`select`&&e.options&&e.options.length>0?[{choices:e.options.map(e=>({title:truncate(e.label,TEAMS_ADAPTIVE_CARD_CHOICE_TITLE_MAX_LENGTH),value:e.id})),id:TEAMS_HITL_CHOICE_INPUT_ID,isMultiSelect:!1,style:`compact`,type:`Input.ChoiceSet`}]:e.allowFreeform===!0||!e.options||e.options.length===0?[{id:TEAMS_HITL_FREEFORM_INPUT_ID,isMultiline:!0,placeholder:`Type your answer`,type:`Input.Text`}]:[]}function renderActions(e){let t=e.options;return t&&t.length>0&&e.display!==`select`?t.slice(0,TEAMS_ADAPTIVE_CARD_ACTION_LIMIT).map(t=>({data:{[TEAMS_HITL_DATA_KEY]:{optionId:t.id,requestId:e.requestId}},title:truncate(t.label,TEAMS_ADAPTIVE_CARD_ACTION_TITLE_MAX_LENGTH),type:`Action.Submit`})):[{data:{[TEAMS_HITL_DATA_KEY]:{requestId:e.requestId}},title:`Submit`,type:`Action.Submit`}]}function readActivityValue(e){return e.type===`message`?readMessageValue(e):e.type===`invoke`?readInvokeValue(e):null}function readMessageValue(e){return e.value??null}function readInvokeValue(t){let n=t.value;if(!n)return null;let r=isObject(n.action)?n.action:null;return(r&&isObject(r.data)?r.data:null)??n}function readHitlPayload(t){let n=t[TEAMS_HITL_DATA_KEY];if(isObject(n))return n;let r=isObject(t.action)?t.action:null,i=(r&&isObject(r.data)?r.data:null)?.[TEAMS_HITL_DATA_KEY];return isObject(i)?i:null}function truncate(e,t){if(e.length<=t)return e;let n=Math.max(0,t-3);return`${e.slice(0,n).trimEnd()}...`}export{TEAMS_ADAPTIVE_CARD_CONTENT_TYPE,TEAMS_HITL_CHOICE_INPUT_ID,TEAMS_HITL_DATA_KEY,TEAMS_HITL_FREEFORM_INPUT_ID,deriveTeamsInputResponses,isTeamsInputResponseActivity,renderAnsweredInputRequestMessage,renderInputRequestAttachment,renderInputRequestMessage,teamsInvokeResponse};
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Inbound Microsoft Teams activity parsing and prompt shaping.
3
+ *
4
+ * The channel owns small, documented data shapes instead of exposing the
5
+ * full Bot Framework SDK Activity model as the primary public API.
6
+ */
7
+ import type { UserContent } from "ai";
8
+ import type { TeamsAttachment, TeamsChannelAccount, TeamsMention } from "#public/channels/teams/api.js";
9
+ /** Teams conversation scopes handled by the native channel. */
10
+ export type TeamsConversationScope = "channel" | "groupChat" | "personal" | "unknown";
11
+ /** Bot Framework conversation account fields used by the Teams channel. */
12
+ export interface TeamsConversationAccount {
13
+ readonly conversationType?: string;
14
+ readonly id: string;
15
+ readonly isGroup?: boolean;
16
+ readonly name?: string;
17
+ readonly tenantId?: string;
18
+ }
19
+ /** Common fields shared by parsed Teams activities. */
20
+ export interface TeamsActivityBase {
21
+ readonly channelData: Record<string, unknown>;
22
+ readonly conversation: TeamsConversationAccount;
23
+ readonly conversationType?: string;
24
+ readonly from: TeamsChannelAccount;
25
+ readonly id: string;
26
+ readonly raw: Record<string, unknown>;
27
+ readonly recipient: TeamsChannelAccount;
28
+ readonly serviceUrl: string;
29
+ readonly tenantId?: string;
30
+ readonly teamId?: string;
31
+ readonly teamsChannelId?: string;
32
+ }
33
+ /** Parsed Teams message activity. */
34
+ export interface TeamsMessageActivity extends TeamsActivityBase {
35
+ readonly attachments: readonly TeamsAttachment[];
36
+ readonly isBotMentioned: boolean;
37
+ readonly mentions: readonly TeamsMention[];
38
+ readonly replyToId?: string;
39
+ readonly scope: TeamsConversationScope;
40
+ readonly text: string;
41
+ readonly textFormat?: string;
42
+ readonly type: "message";
43
+ readonly value?: Record<string, unknown>;
44
+ }
45
+ /** Parsed Teams invoke activity. */
46
+ export interface TeamsInvokeActivity extends TeamsActivityBase {
47
+ readonly name: string;
48
+ readonly replyToId?: string;
49
+ readonly scope: TeamsConversationScope;
50
+ readonly type: "invoke";
51
+ readonly value?: Record<string, unknown>;
52
+ }
53
+ /** Parsed Teams conversation-update activity. */
54
+ export interface TeamsConversationUpdateActivity extends TeamsActivityBase {
55
+ readonly type: "conversationUpdate";
56
+ }
57
+ /** Parsed Teams activity variants handled by the native channel. */
58
+ export type TeamsActivity = TeamsConversationUpdateActivity | TeamsInvokeActivity | TeamsMessageActivity;
59
+ /** Inbound context rendered into the model-visible `<teams_context>` block. */
60
+ export interface TeamsInboundContext {
61
+ readonly activityId: string;
62
+ readonly channelId?: string;
63
+ readonly conversationId: string;
64
+ readonly conversationType?: string;
65
+ readonly scope: TeamsConversationScope;
66
+ readonly teamId?: string;
67
+ readonly tenantId?: string;
68
+ readonly userId: string;
69
+ readonly userName?: string;
70
+ }
71
+ /** Parses one JSON-decoded Teams activity payload. */
72
+ export declare function parseTeamsActivity(value: unknown): TeamsActivity | null;
73
+ /** Returns whether the message's scope behaves like a Teams personal chat. */
74
+ export declare function isTeamsPersonalMessage(activity: TeamsMessageActivity): boolean;
75
+ /** Returns the root activity id that should anchor a Teams thread, when any. */
76
+ export declare function teamsThreadRootActivityId(activity: TeamsMessageActivity | TeamsInvokeActivity): string | null;
77
+ /** Renders one {@link TeamsInboundContext} as a deterministic context block. */
78
+ export declare function formatTeamsContextBlock(context: TeamsInboundContext): string;
79
+ /** Prepends a `<teams_context>` block to the inbound turn message. */
80
+ export declare function prependTeamsContext(message: string | UserContent, context: TeamsInboundContext): string | UserContent;
@@ -0,0 +1,3 @@
1
+ import{isNonEmptyString,isObject}from"#shared/guards.js";import{parseJsonObject}from"#shared/json.js";import TurndownService from"#compiled/turndown/index.js";var TEAMS_RESPONSE_INSTRUCTIONS=`Reply for Microsoft Teams in concise Markdown. Avoid broad mentions, large tables, and messages that need more than a few short posts.`,HTML_ENTITY_MAP={"&amp;":`&`,"&gt;":`>`,"&lt;":`<`,"&nbsp;":` `,"&quot;":`"`,"&#39;":`'`,"&#x27;":`'`},HTML_ENTITY_PATTERN=new RegExp(Object.keys(HTML_ENTITY_MAP).join(`|`),`gi`),turndownService=null;function parseTeamsActivity(e){return isObject(e)?e.type===`message`?parseMessageActivity(e):e.type===`invoke`?parseInvokeActivity(e):e.type===`conversationUpdate`?parseConversationUpdateActivity(e):null:null}function isTeamsPersonalMessage(e){return e.scope===`personal`}function teamsThreadRootActivityId(e){return e.scope===`personal`?null:e.replyToId??e.id}function formatTeamsContextBlock(e){return[`<teams_context>`,`response_medium: microsoft_teams`,`response_instructions: ${TEAMS_RESPONSE_INSTRUCTIONS}`,`user_id: ${e.userId}`,...e.userName?[`user_name: ${e.userName}`]:[],`conversation_id: ${e.conversationId}`,`scope: ${e.scope}`,...e.conversationType?[`conversation_type: ${e.conversationType}`]:[],...e.tenantId?[`tenant_id: ${e.tenantId}`]:[],...e.teamId?[`team_id: ${e.teamId}`]:[],...e.channelId?[`channel_id: ${e.channelId}`]:[],`activity_id: ${e.activityId}`,`</teams_context>`].join(`
2
+ `)}function prependTeamsContext(e,t){let n=formatTeamsContextBlock(t);return typeof e==`string`?e.length>0?`${n}\n\n${e}`:n:[{type:`text`,text:n},...e]}function parseMessageActivity(n){let r=parseActivityBase(n);if(!r)return null;let i=parseMentions(n.entities),a=normalizeTeamsText(stripBotMention(readText(n),i,r.recipient.id));return{...r,attachments:parseAttachments(n.attachments),isBotMentioned:i.some(e=>e.mentioned.id===r.recipient.id),mentions:i,replyToId:isNonEmptyString(n.replyToId)?n.replyToId:void 0,scope:inferScope(r.conversation),text:a,textFormat:isNonEmptyString(n.textFormat)?n.textFormat:void 0,type:`message`,value:isObject(n.value)?n.value:void 0}}function parseInvokeActivity(n){let r=parseActivityBase(n);return!r||!isNonEmptyString(n.name)?null:{...r,name:n.name,replyToId:isNonEmptyString(n.replyToId)?n.replyToId:void 0,scope:inferScope(r.conversation),type:`invoke`,value:isObject(n.value)?n.value:void 0}}function parseConversationUpdateActivity(e){let t=parseActivityBase(e);return t?{...t,type:`conversationUpdate`}:null}function parseActivityBase(n){if(!isNonEmptyString(n.serviceUrl))return null;let r=parseConversation(n.conversation),i=parseChannelAccount(n.from),a=parseChannelAccount(n.recipient);if(!r||!i||!a)return null;let o=isObject(n.channelData)?n.channelData:{},s=readNestedString(o,[`tenant`,`id`])??r.tenantId,c=readNestedString(o,[`team`,`id`]),l=readNestedString(o,[`channel`,`id`]);return{channelData:o,conversation:r,conversationType:r.conversationType,from:i,id:isNonEmptyString(n.id)?n.id:``,raw:n,recipient:a,serviceUrl:n.serviceUrl,tenantId:s,teamId:c,teamsChannelId:l}}function parseConversation(n){return!isObject(n)||!isNonEmptyString(n.id)?null:{conversationType:isNonEmptyString(n.conversationType)?n.conversationType:void 0,id:n.id,isGroup:typeof n.isGroup==`boolean`?n.isGroup:void 0,name:isNonEmptyString(n.name)?n.name:void 0,tenantId:isNonEmptyString(n.tenantId)?n.tenantId:void 0}}function parseChannelAccount(n){return!isObject(n)||!isNonEmptyString(n.id)?null:{aadObjectId:isNonEmptyString(n.aadObjectId)?n.aadObjectId:void 0,id:n.id,name:isNonEmptyString(n.name)?n.name:void 0,role:isNonEmptyString(n.role)?n.role:void 0}}function parseMentions(n){if(!Array.isArray(n))return[];let r=[];for(let i of n){if(!isObject(i)||i.type!==`mention`||!isNonEmptyString(i.text))continue;let n=parseChannelAccount(i.mentioned);n&&r.push({mentioned:n,text:i.text,type:`mention`})}return r}function parseAttachments(r){if(!Array.isArray(r))return[];let i=[];for(let a of r){if(!isObject(a)||!isNonEmptyString(a.contentType))continue;let r={content:isObject(a.content)?parseJsonObject(a.content):void 0,contentType:a.contentType,contentUrl:isNonEmptyString(a.contentUrl)?a.contentUrl:void 0,name:isNonEmptyString(a.name)?a.name:void 0};i.push(r)}return i}function inferScope(e){let t=e.conversationType;return t===`personal`||t===`groupChat`||t===`channel`?t:e.isGroup===!0?`groupChat`:e.isGroup===!1?`personal`:`unknown`}function readText(e){return typeof e.text==`string`?e.text:``}function stripBotMention(e,t,n){let r=e;for(let e of t)e.mentioned.id===n&&(r=r.replace(e.text,``));return r.trim()}function normalizeTeamsText(e){let t=e.trim();return looksLikeHtml(t)?getTurndownService().turndown(t.replace(/<at>(.*?)<\/at>/gi,`@$1`)).replace(/ {2}\n/g,`
3
+ `).trim():decodeHtmlEntities(t)}function readNestedString(n,r){let i=n;for(let e of r){if(!isObject(i))return;i=i[e]}return isNonEmptyString(i)?i:void 0}function looksLikeHtml(e){return/<\/?[a-z][\s\S]*>/i.test(e)}function decodeHtmlEntities(e){return e.replace(HTML_ENTITY_PATTERN,e=>HTML_ENTITY_MAP[e.toLowerCase()]??e)}function getTurndownService(){return turndownService??=new TurndownService({bulletListMarker:`-`,codeBlockStyle:`fenced`,emDelimiter:`*`,headingStyle:`atx`,hr:`---`}),turndownService}export{formatTeamsContextBlock,isTeamsPersonalMessage,parseTeamsActivity,prependTeamsContext,teamsThreadRootActivityId};
@@ -0,0 +1,8 @@
1
+ export type { ModelMessage } from "ai";
2
+ export { teamsChannel, type TeamsChannel, type TeamsChannelConfig, type TeamsChannelCredentials, type TeamsChannelEvents, type TeamsChannelState, type TeamsContext, type TeamsEventContext, type TeamsHandle, type TeamsInboundResult, type TeamsInboundResultOrPromise, type TeamsInvokeResult, type TeamsInvokeResultOrPromise, type TeamsReceiveArgs, type TeamsRequestOptions, type TeamsThread, } from "#public/channels/teams/teamsChannel.js";
3
+ export { callTeamsConnectorApi, normalizeTeamsPostInput, replyToTeamsActivity, resolveTeamsAccessToken, resolveTeamsAppId, resolveTeamsAppPassword, resolveTeamsTenantId, sendTeamsActivity, splitTeamsMessageText, teamsContinuationToken, triggerTeamsTypingIndicator, updateTeamsActivity, TEAMS_MESSAGE_TEXT_MAX_LENGTH, type TeamsAccessTokenResult, type TeamsApiOptions, type TeamsApiResponse, type TeamsAppId, type TeamsAppPassword, type TeamsAttachment, type TeamsChannelAccount, type TeamsCredentials, type TeamsFetch, type TeamsMention, type TeamsMessageBody, type TeamsOutboundActivity, type TeamsPostedActivity, type TeamsTenantId, type TeamsTokenProvider, } from "#public/channels/teams/api.js";
4
+ export { formatTeamsContextBlock, isTeamsPersonalMessage, parseTeamsActivity, prependTeamsContext, teamsThreadRootActivityId, type TeamsActivity, type TeamsActivityBase, type TeamsConversationAccount, type TeamsConversationScope, type TeamsConversationUpdateActivity, type TeamsInboundContext, type TeamsInvokeActivity, type TeamsMessageActivity, } from "#public/channels/teams/inbound.js";
5
+ export { deriveTeamsInputResponses, isTeamsInputResponseActivity, renderAnsweredInputRequestMessage, renderInputRequestAttachment, renderInputRequestMessage, teamsInvokeResponse, TEAMS_ADAPTIVE_CARD_CONTENT_TYPE, TEAMS_HITL_CHOICE_INPUT_ID, TEAMS_HITL_DATA_KEY, TEAMS_HITL_FREEFORM_INPUT_ID, } from "#public/channels/teams/hitl.js";
6
+ export { collectTeamsFileParts, buildTeamsTurnMessage, createTeamsFetchFile, normalizeTeamsFilesPolicy, type TeamsFilesConfig, type TeamsFilesPolicy, } from "#public/channels/teams/attachments.js";
7
+ export { buildAuthCompletedText, defaultTeamsAuth, formatConnectionDisplayName, teamsMentionUser, } from "#public/channels/teams/defaults.js";
8
+ export { verifyTeamsJwt, verifyTeamsRequest, type TeamsJwtVerifyOptions, type TeamsVerifyOptions, type TeamsWebhookVerifier, } from "#public/channels/teams/verify.js";
@@ -0,0 +1 @@
1
+ import{TEAMS_MESSAGE_TEXT_MAX_LENGTH,callTeamsConnectorApi,normalizeTeamsPostInput,replyToTeamsActivity,resolveTeamsAccessToken,resolveTeamsAppId,resolveTeamsAppPassword,resolveTeamsTenantId,sendTeamsActivity,splitTeamsMessageText,teamsContinuationToken,triggerTeamsTypingIndicator,updateTeamsActivity}from"#public/channels/teams/api.js";import{TEAMS_ADAPTIVE_CARD_CONTENT_TYPE,TEAMS_HITL_CHOICE_INPUT_ID,TEAMS_HITL_DATA_KEY,TEAMS_HITL_FREEFORM_INPUT_ID,deriveTeamsInputResponses,isTeamsInputResponseActivity,renderAnsweredInputRequestMessage,renderInputRequestAttachment,renderInputRequestMessage,teamsInvokeResponse}from"#public/channels/teams/hitl.js";import{teamsChannel}from"#public/channels/teams/teamsChannel.js";import{formatTeamsContextBlock,isTeamsPersonalMessage,parseTeamsActivity,prependTeamsContext,teamsThreadRootActivityId}from"#public/channels/teams/inbound.js";import{buildTeamsTurnMessage,collectTeamsFileParts,createTeamsFetchFile,normalizeTeamsFilesPolicy}from"#public/channels/teams/attachments.js";import{buildAuthCompletedText,defaultTeamsAuth,formatConnectionDisplayName,teamsMentionUser}from"#public/channels/teams/defaults.js";import{verifyTeamsJwt,verifyTeamsRequest}from"#public/channels/teams/verify.js";export{TEAMS_ADAPTIVE_CARD_CONTENT_TYPE,TEAMS_HITL_CHOICE_INPUT_ID,TEAMS_HITL_DATA_KEY,TEAMS_HITL_FREEFORM_INPUT_ID,TEAMS_MESSAGE_TEXT_MAX_LENGTH,buildAuthCompletedText,buildTeamsTurnMessage,callTeamsConnectorApi,collectTeamsFileParts,createTeamsFetchFile,defaultTeamsAuth,deriveTeamsInputResponses,formatConnectionDisplayName,formatTeamsContextBlock,isTeamsInputResponseActivity,isTeamsPersonalMessage,normalizeTeamsFilesPolicy,normalizeTeamsPostInput,parseTeamsActivity,prependTeamsContext,renderAnsweredInputRequestMessage,renderInputRequestAttachment,renderInputRequestMessage,replyToTeamsActivity,resolveTeamsAccessToken,resolveTeamsAppId,resolveTeamsAppPassword,resolveTeamsTenantId,sendTeamsActivity,splitTeamsMessageText,teamsChannel,teamsContinuationToken,teamsInvokeResponse,teamsMentionUser,teamsThreadRootActivityId,triggerTeamsTypingIndicator,updateTeamsActivity,verifyTeamsJwt,verifyTeamsRequest};
@@ -0,0 +1,8 @@
1
+ /** Maximum Teams Adaptive Card text block length used by Ash renderers. */
2
+ export declare const TEAMS_ADAPTIVE_CARD_TEXT_MAX_LENGTH = 4000;
3
+ /** Maximum Teams Adaptive Card button title length used by Ash renderers. */
4
+ export declare const TEAMS_ADAPTIVE_CARD_ACTION_TITLE_MAX_LENGTH = 80;
5
+ /** Maximum Teams Adaptive Card choice title length used by Ash renderers. */
6
+ export declare const TEAMS_ADAPTIVE_CARD_CHOICE_TITLE_MAX_LENGTH = 100;
7
+ /** Maximum number of direct Adaptive Card actions rendered for one request. */
8
+ export declare const TEAMS_ADAPTIVE_CARD_ACTION_LIMIT = 6;
@@ -0,0 +1 @@
1
+ var TEAMS_ADAPTIVE_CARD_TEXT_MAX_LENGTH=4e3,TEAMS_ADAPTIVE_CARD_ACTION_TITLE_MAX_LENGTH=80,TEAMS_ADAPTIVE_CARD_CHOICE_TITLE_MAX_LENGTH=100,TEAMS_ADAPTIVE_CARD_ACTION_LIMIT=6;export{TEAMS_ADAPTIVE_CARD_ACTION_LIMIT,TEAMS_ADAPTIVE_CARD_ACTION_TITLE_MAX_LENGTH,TEAMS_ADAPTIVE_CARD_CHOICE_TITLE_MAX_LENGTH,TEAMS_ADAPTIVE_CARD_TEXT_MAX_LENGTH};
@@ -0,0 +1,162 @@
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 TeamsFilesConfig } from "#public/channels/teams/attachments.js";
7
+ import { type TeamsApiOptions, type TeamsApiResponse, type TeamsChannelAccount, type TeamsCredentials, type TeamsMention, type TeamsMessageBody, type TeamsOutboundActivity, type TeamsPostedActivity } from "#public/channels/teams/api.js";
8
+ import { type TeamsInvokeActivity, type TeamsMessageActivity } from "#public/channels/teams/inbound.js";
9
+ import { type TeamsWebhookVerifier } from "#public/channels/teams/verify.js";
10
+ import { type JsonObject } from "#shared/json.js";
11
+ import { type Channel } from "#public/definitions/defineChannel.js";
12
+ type EventData<T extends HandleMessageStreamEvent["type"]> = Extract<HandleMessageStreamEvent, {
13
+ type: T;
14
+ }> extends {
15
+ data: infer D;
16
+ } ? D : undefined;
17
+ /** Pre-dispatch Teams context passed to inbound message and invoke hooks. */
18
+ export interface TeamsContext {
19
+ readonly teams: TeamsHandle;
20
+ readonly thread: TeamsThread;
21
+ }
22
+ /** Event-handler Teams context, including mutable per-conversation state. */
23
+ export interface TeamsEventContext extends TeamsContext {
24
+ readonly adaptiveCardVersion: string;
25
+ readonly session: SessionHandle;
26
+ state: TeamsChannelState;
27
+ }
28
+ /** JSON-serializable Teams channel state. */
29
+ export interface TeamsChannelState {
30
+ /** Bot account captured from the inbound activity recipient. */
31
+ bot: TeamsChannelAccount | null;
32
+ /** Teams team/channel id for channel conversations, when present. */
33
+ channelId: string | null;
34
+ /** Bot Framework conversation id. */
35
+ conversationId: string | null;
36
+ /** Teams conversation type (`personal`, `groupChat`, `channel`, or platform value). */
37
+ conversationType: string | null;
38
+ /** Activity id used for thread replies in channel/group contexts. */
39
+ replyToActivityId: string | null;
40
+ /** Bot Framework serviceUrl for outbound Connector calls. */
41
+ serviceUrl: string | null;
42
+ /** Teams team id, when present. */
43
+ teamId: string | null;
44
+ /** Teams tenant id, when present. */
45
+ tenantId: string | null;
46
+ /** User account that triggered the latest turn/session. */
47
+ triggeringUser: TeamsChannelAccount | null;
48
+ /** Activity id for the default connection-auth card, when posted. */
49
+ pendingAuthActivityId?: string | null;
50
+ }
51
+ /** Teams channel credentials. */
52
+ export interface TeamsChannelCredentials extends TeamsCredentials {
53
+ /**
54
+ * Custom inbound webhook verifier. When supplied, Ash delegates
55
+ * verification to this function instead of validating Bot Connector JWTs.
56
+ */
57
+ readonly webhookVerifier?: TeamsWebhookVerifier;
58
+ }
59
+ /** Arguments accepted by `receive(teams, args)` for proactive sessions. */
60
+ export interface TeamsReceiveArgs {
61
+ readonly channelId?: string;
62
+ readonly conversationId: string;
63
+ readonly conversationType?: string;
64
+ readonly initialMessage?: string | TeamsMessageBody;
65
+ readonly replyToActivityId?: string;
66
+ readonly serviceUrl: string;
67
+ readonly teamId?: string;
68
+ readonly tenantId?: string;
69
+ }
70
+ /** Result of an inbound Teams message hook. Return `null` to acknowledge without dispatching. */
71
+ export type TeamsInboundResult = {
72
+ readonly auth: SessionAuthContext | null;
73
+ readonly modelContext?: readonly ModelMessage[];
74
+ } | null;
75
+ /** Sync or async {@link TeamsInboundResult}. */
76
+ export type TeamsInboundResultOrPromise = TeamsInboundResult | Promise<TeamsInboundResult>;
77
+ /** Result of an inbound non-HITL Teams invoke hook. */
78
+ export type TeamsInvokeResult = Record<string, unknown> | Response | null | undefined;
79
+ /** Sync or async {@link TeamsInvokeResult}. */
80
+ export type TeamsInvokeResultOrPromise = TeamsInvokeResult | Promise<TeamsInvokeResult>;
81
+ type TeamsEventHandler<T extends HandleMessageStreamEvent["type"]> = (data: EventData<T>, ctx: TeamsEventContext) => void | Promise<void>;
82
+ /** Event handlers supported by `teamsChannel({ events })`. */
83
+ export interface TeamsChannelEvents {
84
+ readonly "turn.started"?: TeamsEventHandler<"turn.started">;
85
+ readonly "actions.requested"?: TeamsEventHandler<"actions.requested">;
86
+ readonly "action.result"?: TeamsEventHandler<"action.result">;
87
+ readonly "message.completed"?: TeamsEventHandler<"message.completed">;
88
+ readonly "message.appended"?: TeamsEventHandler<"message.appended">;
89
+ readonly "input.requested"?: TeamsEventHandler<"input.requested">;
90
+ readonly "turn.failed"?: TeamsEventHandler<"turn.failed">;
91
+ readonly "turn.completed"?: TeamsEventHandler<"turn.completed">;
92
+ readonly "session.failed"?: TeamsEventHandler<"session.failed">;
93
+ readonly "session.completed"?: TeamsEventHandler<"session.completed">;
94
+ readonly "session.waiting"?: TeamsEventHandler<"session.waiting">;
95
+ readonly "connection.authorization_required"?: TeamsEventHandler<"connection.authorization_required">;
96
+ readonly "connection.authorization_pending"?: TeamsEventHandler<"connection.authorization_pending">;
97
+ readonly "connection.authorization_completed"?: TeamsEventHandler<"connection.authorization_completed">;
98
+ }
99
+ /** Configuration for {@link teamsChannel}. */
100
+ export interface TeamsChannelConfig {
101
+ readonly adaptiveCardVersion?: string;
102
+ readonly api?: Omit<TeamsApiOptions, "credentials">;
103
+ readonly credentials?: TeamsChannelCredentials;
104
+ readonly events?: TeamsChannelEvents;
105
+ readonly files?: TeamsFilesConfig;
106
+ /** Override the default webhook route path (`/ash/v1/teams`). */
107
+ readonly route?: string;
108
+ /**
109
+ * Inbound message hook. Defaults to user-scoped Teams auth and
110
+ * mention-gated dispatch outside personal chats.
111
+ */
112
+ onMessage?(ctx: TeamsContext, message: TeamsMessageActivity): TeamsInboundResultOrPromise;
113
+ /**
114
+ * Handler for non-HITL Teams invoke activities. Return a response body,
115
+ * a full Response, or `null`/`undefined` for a generic 200 OK.
116
+ */
117
+ onInvoke?(ctx: TeamsContext, activity: TeamsInvokeActivity): TeamsInvokeResultOrPromise;
118
+ }
119
+ /** Low-level Teams handle exposed to hooks and event handlers. */
120
+ export interface TeamsHandle {
121
+ readonly channelId: string | undefined;
122
+ readonly conversationId: string;
123
+ readonly conversationType: string | undefined;
124
+ readonly replyToActivityId: string | undefined;
125
+ readonly serviceUrl: string;
126
+ readonly teamId: string | undefined;
127
+ readonly tenantId: string | undefined;
128
+ /** Raw Bot Connector API escape hatch. */
129
+ request(path: string, body: TeamsOutboundActivity | JsonObject, options?: TeamsRequestOptions): Promise<TeamsApiResponse>;
130
+ /** Sends a Bot Framework activity to the current conversation. */
131
+ sendActivity(activity: TeamsMessageBody | TeamsOutboundActivity | string): Promise<TeamsPostedActivity>;
132
+ /** Sends a Bot Framework reply to the current thread anchor. */
133
+ replyToActivity(activity: TeamsMessageBody | TeamsOutboundActivity | string): Promise<TeamsPostedActivity>;
134
+ /** Updates a previously sent bot activity. */
135
+ updateActivity(activityId: string, activity: TeamsMessageBody | TeamsOutboundActivity | string): Promise<TeamsPostedActivity>;
136
+ /** Triggers Teams' typing indicator. Failures are swallowed by the thread helper. */
137
+ startTyping(): Promise<void>;
138
+ }
139
+ /** Conversation-scoped Teams operations. */
140
+ export interface TeamsThread {
141
+ /** Builds a Teams mention entity and matching text for one user. */
142
+ mentionUser(user: {
143
+ readonly id: string;
144
+ readonly name?: string;
145
+ }): TeamsMention;
146
+ /** Posts a message in the current Teams conversation/thread. */
147
+ post(message: string | TeamsMessageBody): Promise<TeamsPostedActivity>;
148
+ /** Starts Teams' typing indicator. Failures are swallowed. */
149
+ startTyping(): Promise<void>;
150
+ /** Updates a previously sent bot activity. */
151
+ update(activityId: string, message: string | TeamsMessageBody): Promise<TeamsPostedActivity>;
152
+ }
153
+ /** Options for {@link TeamsHandle.request}. */
154
+ export interface TeamsRequestOptions {
155
+ readonly method?: "DELETE" | "GET" | "POST" | "PUT";
156
+ }
157
+ /** Concrete return type of {@link teamsChannel}. */
158
+ export interface TeamsChannel extends Channel<TeamsChannelState>, TypedReceiveRoute<TeamsReceiveArgs> {
159
+ }
160
+ /** Teams channel factory for Bot Framework Activities and proactive messages. */
161
+ export declare function teamsChannel(config?: TeamsChannelConfig): TeamsChannel;
162
+ export {};
@@ -0,0 +1 @@
1
+ import{createLogger}from"#internal/logging.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{callTeamsConnectorApi,normalizeTeamsPostInput,replyToTeamsActivity,sendTeamsActivity,teamsContinuationToken,triggerTeamsTypingIndicator,updateTeamsActivity}from"#public/channels/teams/api.js";import{deriveTeamsInputResponses,isTeamsInputResponseActivity,teamsInvokeResponse}from"#public/channels/teams/hitl.js";import{parseTeamsActivity,prependTeamsContext,teamsThreadRootActivityId}from"#public/channels/teams/inbound.js";import{buildTeamsTurnMessage,collectTeamsFileParts,createTeamsFetchFile,normalizeTeamsFilesPolicy}from"#public/channels/teams/attachments.js";import{defaultEvents,defaultOnMessage,teamsMentionUser}from"#public/channels/teams/defaults.js";import{verifyTeamsRequest}from"#public/channels/teams/verify.js";var log=createLogger(`teams.channel`);function teamsChannel(e={}){let i=normalizeTeamsFilesPolicy(e.files),a=e.onMessage??defaultOnMessage,o={...defaultEvents,...e.events};return defineChannel({kindHint:`teams`,state:initialTeamsState(),fetchFile:createTeamsFetchFile(i),context(t,n){return rebuildTeamsContext(t,n,e)},routes:[POST(e.route??`/ash/v1/teams`,async(n,{send:r,waitUntil:o})=>{let s=await verifyInbound(n,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 Teams body is not valid JSON`,{error:e}),teamsOk()}let l=parseTeamsActivity(c);return l===null?teamsOk():l.type===`message`?(o(dispatchMessage({activity:l,config:e,filesPolicy:i,onMessage:a,send:r})),teamsOk()):l.type===`invoke`?handleInvoke({activity:l,config:e,send:r,waitUntil:o}):teamsOk()})],async receive(t,{send:n}){let r=t.args,i=readString(r.serviceUrl),a=readString(r.conversationId);if(!i||!a)throw Error(`teamsChannel().receive requires args.serviceUrl and args.conversationId.`);let o=readString(r.conversationType)??null,s=readString(r.replyToActivityId)??null,l=r.initialMessage;if(l!==void 0&&s!==null)throw Error("teamsChannel().receive: `replyToActivityId` and `initialMessage` are mutually exclusive.");let u={...initialTeamsState(),channelId:readString(r.channelId)??null,conversationId:a,conversationType:o,replyToActivityId:s,serviceUrl:i,teamId:readString(r.teamId)??null,tenantId:readString(r.tenantId)??null};if(l!==void 0){let t=await buildTeamsBinding({config:e,state:u}).thread.post(l);o!==`personal`&&t.id&&(s=t.id,u.replyToActivityId=t.id)}return n(t.message,{auth:t.auth,continuationToken:teamsContinuationToken({conversationId:a,replyToActivityId:s,tenantId:u.tenantId}),state:u})},events:o})}function rebuildTeamsContext(e,t,n){return{...buildTeamsBinding({config:n,session:t,state:e}),adaptiveCardVersion:n.adaptiveCardVersion??`1.5`,session:t,state:e}}function buildTeamsBinding(e){let t=buildTeamsHandle(e);return{teams:t,thread:{mentionUser:teamsMentionUser,post(e){return t.sendActivity(e)},async startTyping(){try{await t.startTyping()}catch(e){log.debug(`Teams typing indicator failed — swallowed`,{error:e})}},update(e,n){return t.updateActivity(e,n)}}}}function buildTeamsHandle(e){let t=e.state,n=e.config.api,r=e.config.credentials;function requireAddress(){let e=t.conversationId??``,n=t.serviceUrl??``;if(!e||!n)throw Error(`teamsChannel: missing serviceUrl or conversationId for outbound message.`);return{conversationId:e,serviceUrl:n}}function anchor(n){if(!n.id||t.replyToActivityId||t.conversationType===`personal`)return;t.replyToActivityId=n.id;let r=t.conversationId;r&&e.session?.setContinuationToken(teamsContinuationToken({conversationId:r,replyToActivityId:n.id,tenantId:t.tenantId}))}async function send(e){let i=requireAddress(),a=buildOutboundActivity(t,e),c=t.replyToActivityId===null?await sendTeamsActivity({...n,body:a,credentials:r,conversationId:i.conversationId,serviceUrl:i.serviceUrl}):await replyToTeamsActivity({...n,body:a,credentials:r,activityId:t.replyToActivityId,conversationId:i.conversationId,serviceUrl:i.serviceUrl});return anchor(c),c}return{channelId:t.channelId??void 0,conversationId:t.conversationId??``,conversationType:t.conversationType??void 0,replyToActivityId:t.replyToActivityId??void 0,serviceUrl:t.serviceUrl??``,teamId:t.teamId??void 0,tenantId:t.tenantId??void 0,request(e,t,a){let o=requireAddress();return callTeamsConnectorApi({...n,body:t,credentials:r,method:a?.method,path:e,serviceUrl:o.serviceUrl})},sendActivity:send,replyToActivity(e){let i=requireAddress(),a=t.replyToActivityId??``;if(!a)throw Error(`teamsChannel: missing reply activity id.`);return replyToTeamsActivity({...n,body:buildOutboundActivity(t,e),credentials:r,activityId:a,conversationId:i.conversationId,serviceUrl:i.serviceUrl})},updateActivity(e,i){let a=requireAddress();return updateTeamsActivity({...n,body:buildOutboundActivity(t,i),credentials:r,activityId:e,conversationId:a.conversationId,serviceUrl:a.serviceUrl})},async startTyping(){let e=requireAddress();await triggerTeamsTypingIndicator({...n,credentials:r,conversationId:e.conversationId,serviceUrl:e.serviceUrl})}}}async function verifyInbound(e,t){try{return await verifyTeamsRequest(e,{appId:t?.webhookVerifier?void 0:t?.appId,webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`teams inbound verification failed`,{error:e}),null}}async function dispatchMessage(e){let t=stateFromActivity(e.activity),n=buildTeamsBinding({config:e.config,state:t}),r;try{r=await e.onMessage(n,e.activity)}catch(e){log.error(`Teams message handler failed`,{error:e});return}if(r==null)return;let i=collectTeamsFileParts(e.activity.attachments,e.filesPolicy),a=prependTeamsContext(buildTeamsTurnMessage(e.activity.text,i),{activityId:e.activity.id,channelId:e.activity.teamsChannelId,conversationId:e.activity.conversation.id,conversationType:e.activity.conversationType,scope:e.activity.scope,teamId:e.activity.teamId,tenantId:e.activity.tenantId,userId:e.activity.from.id,userName:e.activity.from.name});try{await e.send({message:a,modelContext:r.modelContext},{auth:r.auth,continuationToken:stateToken(t),state:t})}catch(e){log.error(`Teams message delivery failed`,{error:e})}}async function handleInvoke(e){if(isTeamsInputResponseActivity(e.activity))return e.waitUntil(dispatchInputResponses({activity:e.activity,send:e.send})),Response.json(teamsInvokeResponse());if(e.config.onInvoke===void 0)return teamsOk();let t=buildTeamsBinding({config:e.config,state:stateFromActivity(e.activity)}),n=await e.config.onInvoke(t,e.activity);return n instanceof Response?n:n&&typeof n==`object`?Response.json(n):teamsOk()}async function dispatchInputResponses(e){let t=deriveTeamsInputResponses(e.activity);if(t.length===0)return;let n=stateFromActivity(e.activity);try{await e.send({inputResponses:t},{auth:null,continuationToken:stateToken(n),state:n})}catch(e){log.error(`Teams input response delivery failed`,{error:e})}}function stateFromActivity(e){return{bot:e.recipient,channelId:e.teamsChannelId??null,conversationId:e.conversation.id,conversationType:e.conversationType??e.scope,pendingAuthActivityId:null,replyToActivityId:teamsThreadRootActivityId(e),serviceUrl:e.serviceUrl,teamId:e.teamId??null,tenantId:e.tenantId??null,triggeringUser:e.from}}function initialTeamsState(){return{bot:null,channelId:null,conversationId:null,conversationType:null,pendingAuthActivityId:null,replyToActivityId:null,serviceUrl:null,teamId:null,tenantId:null,triggeringUser:null}}function stateToken(e){let t=e.conversationId??``;if(!t)throw Error(`teamsChannel: missing conversation id.`);return teamsContinuationToken({conversationId:t,replyToActivityId:e.replyToActivityId,tenantId:e.tenantId})}function buildOutboundActivity(e,t){if(typeof t!=`string`&&`type`in t&&t.type===`typing`)return t;let n=normalizeTeamsPostInput(t),r=mergeChannelData(e,n.channelData);return{...n,channelData:r,conversation:e.conversationId?{id:e.conversationId}:void 0,from:e.bot??void 0,replyToId:e.replyToActivityId??void 0,type:`message`}}function mergeChannelData(e,n){let r={...n};return e.tenantId&&(r.tenant={id:e.tenantId}),e.teamId&&(r.team={id:e.teamId}),e.channelId&&(r.channel={id:e.channelId}),Object.keys(r).length>0?parseJsonObject(r):void 0}function teamsOk(){return new Response(`ok`,{status:200})}function readString(e){return typeof e==`string`&&e.length>0?e:void 0}export{teamsChannel};
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Microsoft Teams inbound Bot Framework request verification.
3
+ *
4
+ * Teams sends bot activities through the Bot Connector service. Ash verifies
5
+ * the bearer JWT against Bot Connector OpenID keys and the bot's Microsoft
6
+ * app id before dispatching any activity to the runtime.
7
+ */
8
+ import type { JWTPayload } from "#compiled/jose/index.js";
9
+ import { type TeamsAppId, type TeamsFetch } from "#public/channels/teams/api.js";
10
+ /**
11
+ * Caller-supplied inbound webhook verifier. Used as an alternative to
12
+ * Bot Connector JWT verification when a trusted integration authenticates
13
+ * forwarded requests before they reach Ash.
14
+ */
15
+ export type TeamsWebhookVerifier = (request: Request, body: string) => unknown | Promise<unknown>;
16
+ /** Options for {@link verifyTeamsRequest}. */
17
+ export interface TeamsVerifyOptions {
18
+ readonly appId?: TeamsAppId;
19
+ readonly fetch?: TeamsFetch;
20
+ readonly jwksUrl?: string;
21
+ /** Max allowed clock skew, in seconds. Defaults to 5 minutes. */
22
+ readonly maxSkewSeconds?: number;
23
+ readonly openIdMetadataUrl?: string;
24
+ readonly webhookVerifier?: TeamsWebhookVerifier;
25
+ }
26
+ /** Options for {@link verifyTeamsJwt}. */
27
+ export interface TeamsJwtVerifyOptions {
28
+ readonly appId?: TeamsAppId;
29
+ readonly fetch?: TeamsFetch;
30
+ readonly jwksUrl?: string;
31
+ /** Max allowed clock skew, in seconds. Defaults to 5 minutes. */
32
+ readonly maxSkewSeconds?: number;
33
+ readonly openIdMetadataUrl?: string;
34
+ }
35
+ /**
36
+ * Verifies an inbound Teams request and returns the raw body.
37
+ *
38
+ * Throws when no verifier/app id is configured, the bearer token is missing,
39
+ * metadata or keys cannot be loaded, or JWT verification fails.
40
+ */
41
+ export declare function verifyTeamsRequest(request: Request, options: TeamsVerifyOptions): Promise<string>;
42
+ /** Verifies one Bot Connector JWT and returns its payload. */
43
+ export declare function verifyTeamsJwt(token: string, options: TeamsJwtVerifyOptions): Promise<JWTPayload>;
@@ -0,0 +1 @@
1
+ import{createLogger}from"#internal/logging.js";import{isObject}from"#shared/guards.js";import{importJWK,jwtVerify}from"#compiled/jose/index.js";import{resolveTeamsAppId}from"#public/channels/teams/api.js";var log=createLogger(`teams.verify`),DEFAULT_BOT_CONNECTOR_OPENID_METADATA_URL=`https://login.botframework.com/v1/.well-known/openidconfiguration`,BOT_CONNECTOR_ISSUER=`https://api.botframework.com`;async function verifyTeamsRequest(e,t){let n=await e.text();if(t.webhookVerifier!==void 0){let r=await t.webhookVerifier(e,n);if(!r)throw Error(`teamsChannel: inbound webhook verifier rejected the request.`);return typeof r==`string`?r:n}let r=readBearerToken(e.headers.get(`authorization`)??``);if(!r)throw Error(`teamsChannel: inbound request missing bearer token.`);return await verifyTeamsJwt(r,t),n}async function verifyTeamsJwt(e,t){let a=await resolveTeamsAppId(t.appId),o=await loadJwks(t);return(await jwtVerify(e,async e=>importJWK(selectJwk(o,e),typeof e.alg==`string`?e.alg:void 0),{audience:a,clockTolerance:t.maxSkewSeconds??300,issuer:BOT_CONNECTOR_ISSUER})).payload}function readBearerToken(e){let[t,n]=e.split(/\s+/,2);return t?.toLowerCase()!==`bearer`||!n?null:n}async function loadJwks(e){let n=e.fetch??fetch,r=await n(e.jwksUrl??await loadJwksUrl(e,n),{headers:{accept:`application/json`}});if(!r.ok)throw Error(`Teams JWKS route returned HTTP ${r.status}.`);let i=await r.json();if(!isObject(i)||!Array.isArray(i.keys))throw Error(`Teams JWKS response is malformed.`);return i.keys.filter(isObject)}async function loadJwksUrl(e,n){let r=await n(e.openIdMetadataUrl??DEFAULT_BOT_CONNECTOR_OPENID_METADATA_URL,{headers:{accept:`application/json`}});if(!r.ok)throw Error(`Teams OpenID metadata route returned HTTP ${r.status}.`);let i=await r.json();if(!isObject(i)||typeof i.jwks_uri!=`string`)throw Error(`Teams OpenID metadata response is missing jwks_uri.`);return i.jwks_uri}function selectJwk(e,t){let n=typeof t.kid==`string`?t.kid:void 0,r=typeof t.x5t==`string`?t.x5t:void 0,i=e.find(e=>n!==void 0&&e.kid===n||r!==void 0&&e.x5t===r)??(e.length===1?e[0]:void 0);if(i===void 0)throw log.debug(`Teams JWT key not found`,{kid:n,x5t:r}),Error(`teamsChannel: inbound JWT key was not found in JWKS.`);return i}export{verifyTeamsJwt,verifyTeamsRequest};
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Minimal Telegram Bot API wrapper used by the Telegram channel.
3
+ *
4
+ * The channel talks directly to Telegram's JSON HTTP API instead of
5
+ * exposing a third-party SDK through Ash public surfaces.
6
+ */
7
+ import { type JsonObject } from "#shared/json.js";
8
+ /** Telegram bot token, materialized directly or from an async secret provider. */
9
+ export type TelegramBotToken = string | (() => string | Promise<string>);
10
+ /** Fetch implementation override used by tests or non-standard runtimes. */
11
+ export type TelegramFetch = typeof fetch;
12
+ /** Credentials used by the native Telegram channel. */
13
+ export interface TelegramCredentials {
14
+ readonly botToken?: TelegramBotToken;
15
+ }
16
+ /** Shared Telegram API options. */
17
+ export interface TelegramApiOptions {
18
+ readonly apiBaseUrl?: string;
19
+ readonly credentials?: TelegramCredentials;
20
+ readonly fetch?: TelegramFetch;
21
+ readonly fileBaseUrl?: string;
22
+ }
23
+ /** Raw Telegram API response body. */
24
+ export interface TelegramApiResponse {
25
+ readonly body: unknown;
26
+ readonly ok: boolean;
27
+ readonly status: number;
28
+ }
29
+ /** Minimal Telegram message object returned by channel write operations. */
30
+ export interface TelegramMessageResult {
31
+ /** Telegram message id, when Telegram returned one. */
32
+ readonly id: string;
33
+ /** Telegram chat id associated with the message, when Telegram returned one. */
34
+ readonly chatId?: string;
35
+ /** Telegram's raw JSON response. */
36
+ readonly raw: unknown;
37
+ }
38
+ /** JSON body supported by Telegram sendMessage endpoints used by Ash. */
39
+ export interface TelegramMessageBody {
40
+ readonly disable_notification?: boolean;
41
+ readonly link_preview_options?: Readonly<Record<string, unknown>>;
42
+ readonly message_thread_id?: number;
43
+ readonly protect_content?: boolean;
44
+ readonly reply_markup?: Readonly<Record<string, unknown>>;
45
+ readonly reply_parameters?: Readonly<Record<string, unknown>>;
46
+ readonly text: string;
47
+ }
48
+ /** Telegram's documented text-message cap. */
49
+ export declare const TELEGRAM_MESSAGE_TEXT_MAX_LENGTH = 4096;
50
+ /**
51
+ * Builds the channel-local continuation token.
52
+ *
53
+ * Private chats normally use only `chatId`; group and forum-topic
54
+ * sessions include `messageThreadId` and `conversationId` so multiple
55
+ * bot conversations in the same chat do not collapse into one session.
56
+ */
57
+ export declare function telegramContinuationToken(input: {
58
+ readonly chatId: number | string;
59
+ readonly conversationId?: number | string;
60
+ readonly messageThreadId?: number;
61
+ }): string;
62
+ /** Resolves a Telegram bot token, falling back to `TELEGRAM_BOT_TOKEN`. */
63
+ export declare function resolveTelegramBotToken(token?: TelegramBotToken): Promise<string>;
64
+ /** Low-level Telegram JSON API call. */
65
+ export declare function callTelegramApi(input: {
66
+ readonly apiBaseUrl?: string;
67
+ readonly body?: JsonObject;
68
+ readonly botToken?: TelegramBotToken;
69
+ readonly fetch?: TelegramFetch;
70
+ readonly method: string;
71
+ }): Promise<TelegramApiResponse>;
72
+ /** Sends a text message through Telegram's sendMessage method. */
73
+ export declare function sendTelegramMessage(input: TelegramApiOptions & {
74
+ readonly body: TelegramMessageBody;
75
+ readonly chatId: number | string;
76
+ }): Promise<TelegramMessageResult>;
77
+ /** Triggers Telegram's chat action indicator, usually `typing`. */
78
+ export declare function sendTelegramChatAction(input: TelegramApiOptions & {
79
+ readonly action: string;
80
+ readonly chatId: number | string;
81
+ readonly messageThreadId?: number;
82
+ }): Promise<TelegramApiResponse>;
83
+ /** Answers a Telegram callback query so the user's client clears the spinner. */
84
+ export declare function answerTelegramCallbackQuery(input: TelegramApiOptions & {
85
+ readonly callbackQueryId: string;
86
+ readonly showAlert?: boolean;
87
+ readonly text?: string;
88
+ }): Promise<TelegramApiResponse>;
89
+ /** Edits only the reply markup for one Telegram message. */
90
+ export declare function editTelegramMessageReplyMarkup(input: TelegramApiOptions & {
91
+ readonly chatId: number | string;
92
+ readonly messageId: number | string;
93
+ readonly replyMarkup?: Readonly<Record<string, unknown>>;
94
+ }): Promise<TelegramApiResponse>;
95
+ /** Resolves Telegram file metadata through `getFile`. */
96
+ export declare function getTelegramFile(input: TelegramApiOptions & {
97
+ readonly fileId: string;
98
+ }): Promise<{
99
+ readonly filePath: string;
100
+ readonly raw: unknown;
101
+ }>;
102
+ /** Downloads file bytes from Telegram's file endpoint. */
103
+ export declare function downloadTelegramFile(input: TelegramApiOptions & {
104
+ readonly filePath: string;
105
+ }): Promise<Response>;
106
+ /** Splits text into chunks Telegram will accept as individual sendMessage calls. */
107
+ export declare function splitTelegramMessageText(text: string): readonly string[];
@@ -0,0 +1,2 @@
1
+ import{isObject}from"#shared/guards.js";import{parseJsonObject}from"#shared/json.js";var TELEGRAM_MESSAGE_TEXT_MAX_LENGTH=4096;function telegramContinuationToken(e){let t=e.messageThreadId===void 0?``:String(e.messageThreadId),n=e.conversationId===void 0?``:String(e.conversationId);return`${String(e.chatId)}:${t}:${n}`}async function resolveTelegramBotToken(e){let t=e??process.env.TELEGRAM_BOT_TOKEN;if(!t)throw Error(`TELEGRAM_BOT_TOKEN is required.`);return typeof t==`function`?await t():t}async function callTelegramApi(e){let n=e.fetch??fetch,r=await resolveTelegramBotToken(e.botToken),i={headers:{"content-type":`application/json; charset=utf-8`},method:`POST`};e.body!==void 0&&(i.body=JSON.stringify(parseJsonObject(e.body)));let a=await n(`${e.apiBaseUrl??`https://api.telegram.org`}/bot${r}/${encodeURIComponent(e.method)}`,i);return{body:await parseResponseBody(a),ok:a.ok,status:a.status}}async function sendTelegramMessage(e){let t=await callTelegramApi({apiBaseUrl:e.apiBaseUrl,body:normalizeTelegramMessageBody(e.body,e.chatId),botToken:e.credentials?.botToken,fetch:e.fetch,method:`sendMessage`});if(!t.ok)throw Error(`Telegram sendMessage failed with HTTP ${t.status}.`);return toTelegramMessageResult(t.body)}async function sendTelegramChatAction(e){return callTelegramApi({apiBaseUrl:e.apiBaseUrl,body:parseJsonObject({action:e.action,chat_id:e.chatId,message_thread_id:e.messageThreadId}),botToken:e.credentials?.botToken,fetch:e.fetch,method:`sendChatAction`})}async function answerTelegramCallbackQuery(e){return callTelegramApi({apiBaseUrl:e.apiBaseUrl,body:parseJsonObject({callback_query_id:e.callbackQueryId,show_alert:e.showAlert,text:e.text}),botToken:e.credentials?.botToken,fetch:e.fetch,method:`answerCallbackQuery`})}async function editTelegramMessageReplyMarkup(e){return callTelegramApi({apiBaseUrl:e.apiBaseUrl,body:parseJsonObject({chat_id:e.chatId,message_id:Number(e.messageId),reply_markup:e.replyMarkup}),botToken:e.credentials?.botToken,fetch:e.fetch,method:`editMessageReplyMarkup`})}async function getTelegramFile(t){let n=await callTelegramApi({apiBaseUrl:t.apiBaseUrl,body:{file_id:t.fileId},botToken:t.credentials?.botToken,fetch:t.fetch,method:`getFile`});if(!n.ok)throw Error(`Telegram getFile failed with HTTP ${n.status}.`);let r=isObject(n.body)?n.body:{},i=isObject(r.result)?r.result:{};if(typeof i.file_path!=`string`||i.file_path.length===0)throw Error(`Telegram getFile response did not include result.file_path.`);return{filePath:i.file_path,raw:n.body}}async function downloadTelegramFile(e){let t=e.fetch??fetch,n=await resolveTelegramBotToken(e.credentials?.botToken);return t(`${e.fileBaseUrl??e.apiBaseUrl??`https://api.telegram.org`}/file/bot${n}/${e.filePath}`)}function splitTelegramMessageText(e){if(e.length<=4096)return[e];let t=[],r=e;for(;r.length>TELEGRAM_MESSAGE_TEXT_MAX_LENGTH;){let e=r.lastIndexOf(`
2
+ `,TELEGRAM_MESSAGE_TEXT_MAX_LENGTH);e<=0&&(e=r.lastIndexOf(` `,TELEGRAM_MESSAGE_TEXT_MAX_LENGTH)),e<=0&&(e=TELEGRAM_MESSAGE_TEXT_MAX_LENGTH),t.push(r.slice(0,e).trimEnd()),r=r.slice(e).trimStart()}return t.push(r),t}function normalizeTelegramMessageBody(e,n){return parseJsonObject({...e,chat_id:n})}function toTelegramMessageResult(t){let n=isObject(t)?t:{},r=isObject(n.result)?n.result:{},i=isObject(r.chat)?r.chat:{};return{chatId:typeof i.id==`number`||typeof i.id==`string`?String(i.id):void 0,id:typeof r.message_id==`number`||typeof r.message_id==`string`?String(r.message_id):``,raw:t}}async function parseResponseBody(e){let t=await e.text();if(!t)return null;try{return JSON.parse(t)}catch{return t}}export{TELEGRAM_MESSAGE_TEXT_MAX_LENGTH,answerTelegramCallbackQuery,callTelegramApi,downloadTelegramFile,editTelegramMessageReplyMarkup,getTelegramFile,resolveTelegramBotToken,sendTelegramChatAction,sendTelegramMessage,splitTelegramMessageText,telegramContinuationToken};