UNPKG

eve

Version:

Filesystem-first framework for durable backend AI agents that run anywhere.

1 lines 8.48 kB
import{createLogger,logError}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{formatTeamsContextBlock,parseTeamsActivity,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";const log=createLogger(`teams.channel`);function teamsChannel(e={}){let t=normalizeTeamsFilesPolicy(e.files),a=e.onMessage??defaultOnMessage,o={...defaultEvents,...e.events};return defineChannel({kindHint:`teams`,state:initialTeamsState(),fetchFile:createTeamsFetchFile(t),metadata:e=>({channelId:e.channelId,conversationType:e.conversationType,teamId:e.teamId}),context(t,n){return rebuildTeamsContext(t,n,e)},routes:[POST(e.route??`/eve/v1/teams`,async(r,{send:i,waitUntil:o})=>{let s=await verifyInbound(r,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:t,onMessage:a,send:i})),teamsOk()):l.type===`invoke`?handleInvoke({activity:l,config:e,send:i,waitUntil:o}):teamsOk()})],async receive(t,{send:n}){let r=t.target,i=readString(r.serviceUrl),a=readString(r.conversationId);if(!i||!a)throw Error(`teamsChannel().receive requires target.serviceUrl and target.conversationId.`);let o=readString(r.conversationType)??null,s=readString(r.replyToActivityId)??null,c=r.initialMessage;if(c!==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(c!==void 0){let t=await buildTeamsBinding({config:e,state:u}).thread.post(c);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`,state:e}}function buildTeamsBinding(e){let n=buildTeamsHandle(e);return{teams:n,thread:{mentionUser:teamsMentionUser,post(e){return n.sendActivity(e)},async startTyping(){try{await n.startTyping()}catch(e){logError(log,`Teams typing indicator failed — swallowed`,e)}},update(e,t){return n.updateActivity(e,t)}}}}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),o=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(o),o}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,i){let o=requireAddress();return callTeamsConnectorApi({...n,body:t,credentials:r,method:i?.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=buildTeamsTurnMessage(e.activity.text,i),o={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},s=r.context??[];try{await e.send({message:a,context:[formatTeamsContextBlock(o),...s]},{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,t){let r={...t};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};