UNPKG

eve

Version:

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

1 lines 6.04 kB
import{markEventHandled}from"./utils.js";import{createLogger,logError}from"#internal/logging.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{buildSlackBinding,slackContinuationToken}from"#public/channels/slack/api.js";import{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage}from"#public/channels/slack/defaults.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{formatSlackContextBlock,parseAppMentionEvent,parseDirectMessageEvent}from"#public/channels/slack/inbound.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n){let{thread:r,slack:i}=buildSlackBinding({botToken:n?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.setContinuationToken(slackContinuationToken(e.channelId,n))}});return{thread:r,slack:i,state:e}}function slackChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=createSlackFetchFile({botToken:e.credentials?.botToken}),l=e.onAppMention??defaultOnAppMention,u=e.onDirectMessage??defaultOnDirectMessage,d=e.events?.[`authorization.required`],f={...defaultEvents,...e.events,"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler(),"authorization.required":d===void 0?defaultEvents[`authorization.required`]:constrainAuthorizationRequired(d)},p=new Set;return defineChannel({kindHint:`slack`,state:{channelId:null,threadTs:null,teamId:null,triggeringUserId:null,pendingToolCallMessage:null,lastReasoningTypingAtMs:null,lastReasoningTypingStatus:null,pendingAuthMessageTs:{}},fetchFile:n,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},context(t,n){return rebuildSlackContext(t,n,e.credentials)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,{send:r,waitUntil:i})=>{let a=await verifyInbound(n,e.credentials);return a===null?new Response(`unauthorized`,{status:401}):shouldDropSlackHttpTimeoutRetry(n.headers)?new Response(`ok`):(n.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(a,{send:r,waitUntil:i},{config:e}):handleEventPost({body:a,send:r,waitUntil:i,onAppMention:l,onDirectMessage:u,uploadPolicy:t,handledEvents:p,headers:n.headers,credentials:e.credentials})})],async receive(t,{send:n}){let r=t.target,i=r.channelId;if(!i||typeof i!=`string`)throw Error(`slackChannel().receive requires target.channelId.`);let a=typeof r.threadTs==`string`?r.threadTs:``,s=r.initialMessage;if(s&&a.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");let c=a;if(s){let{thread:t}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:i,threadTs:``,teamId:void 0}),n={card:s.card};s.fallbackText!==void 0&&(n.fallbackText=s.fallbackText),c=(await t.post(n)).id}return n(t.message,{auth:t.auth,continuationToken:slackContinuationToken(i,c),state:{channelId:i,threadTs:c||null,teamId:null,triggeringUserId:null}})},events:f})}function constrainAuthorizationRequired(e){return(t,n,r)=>e(t,{postEphemeral:(e,t)=>n.thread.postEphemeral(e,t),postDirectMessage:(e,t)=>n.thread.postDirectMessage(e,t),state:n.state},r)}function shouldDropSlackHttpTimeoutRetry(e){return Number(e.get(`x-slack-retry-num`)??`0`)>=1&&e.get(`x-slack-retry-reason`)===`http_timeout`}async function handleEventPost(t){let n;try{n=JSON.parse(t.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(typeof n.challenge==`string`)return new Response(n.challenge,{status:200,headers:{"content-type":`text/plain`}});if(n.event_id){if(t.handledEvents.has(n.event_id))return log.warn(`received a duplicate event`,{event_id:n.event_id,event_time:n.event_time,retry_num:t.headers.get(`x-slack-retry-num`)||`(null)`,retry_reason:t.headers.get(`x-slack-retry-reason`)||`(null)`}),new Response(`ok`);markEventHandled(n.event_id,t.handledEvents)}let r=parseAppMentionEvent(n);if(r)return t.waitUntil(dispatchInboundMessage({kind:`app_mention`,message:r,handler:t.onAppMention,send:t.send,uploadPolicy:t.uploadPolicy,credentials:t.credentials})),new Response(`ok`);let i=parseDirectMessageEvent(n);return i&&t.waitUntil(dispatchInboundMessage({kind:`direct_message`,message:i,handler:t.onDirectMessage,send:t.send,uploadPolicy:t.uploadPolicy,credentials:t.credentials})),new Response(`ok`)}async function verifyInbound(e,t){try{return await verifySlackRequest(e,{signingSecret:t?.signingSecret??(t?.webhookVerifier?void 0:process.env.SLACK_SIGNING_SECRET),webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`slack inbound verification failed`,{error:e}),null}}async function dispatchInboundMessage(e){let{message:t,kind:r}=e,{thread:i,slack:a}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:t.channelId,threadTs:t.threadTs,teamId:t.teamId}),s={thread:i,slack:a},c;try{c=await e.handler(s,t)}catch(e){logError(log,`${r} handler failed`,e,{channelId:t.channelId});return}if(c!=null)try{let n=await collectInboundFileParts({mention:t,thread:i,policy:e.uploadPolicy}),r=buildSlackTurnMessage(t.markdown,n),a={channelId:t.channelId,fullName:t.author?.fullName,teamId:t.teamId,threadTs:t.threadTs,userId:t.author?.userId??``,userName:t.author?.userName},o=c.context??[];await e.send({message:r,context:[formatSlackContextBlock(a),...o]},{auth:c.auth,continuationToken:slackContinuationToken(t.channelId,t.threadTs),state:{channelId:t.channelId,threadTs:t.threadTs,teamId:t.teamId??null,triggeringUserId:a.userId||null}})}catch(e){logError(log,`${r} delivery failed`,e,{channelId:t.channelId})}}export{constrainAuthorizationRequired,slackChannel};