eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
1 lines • 6.54 kB
JavaScript
import{createLogger}from"#internal/logging.js";import{buildSlackBinding,resolveSlackBotToken,slackContinuationToken}from"#public/channels/slack/api.js";import{buildSlackAuthContext}from"#public/channels/slack/auth.js";import{HITL_FREEFORM_MODAL_ACTION_ID,HITL_FREEFORM_MODAL_BLOCK_ID,HITL_FREEFORM_MODAL_CALLBACK_ID,buildAnsweredBlocks,buildFreeformModalView,deriveHitlResponse,freeformRequestIdFromActionId,isFreeformAction,isHitlAction}from"#public/channels/slack/hitl.js";const log=createLogger(`slack.interactions`);function parseBlockActionsPayload(e){let t=e.actions;if(!Array.isArray(t))return null;let n=e.channel?.id,r=e.message,i=r?.thread_ts??r?.ts;if(!n||!i)return null;let a=e.team,o=e.user,s=a?.id??o.team_id,c={id:o.id,username:o.username,name:o.name},l=r?.blocks??[];return{actions:t.map(e=>({actionId:String(e.action_id??``),value:e.value==null?void 0:String(e.value),blockId:e.block_id==null?void 0:String(e.block_id),selectedOptionValue:extractSelectedOptionValue(e),messageTs:r?.ts,label:extractActionLabel(e),user:c})),channelId:n,threadTs:i,teamId:s,messageBlocks:l}}function extractSelectedOptionValue(e){let t=e.selected_option;return typeof t?.value==`string`?t.value:void 0}function extractActionLabel(e){let t=e.selected_option?.text?.text;if(typeof t==`string`&&t.length>0)return t;let n=e.text?.text;if(typeof n==`string`&&n.length>0)return n}function findPromptBlock(e){for(let t of e)if(typeof t==`object`&&t&&t.type===`section`)return t}function readPromptTextFromBlocks(e){let t=findPromptBlock(e)?.text?.text;return typeof t==`string`&&t.length>0?t:void 0}async function handleInteractionPost(e,n,a){let o=new Response(`ok`,{status:200}),s=new URLSearchParams(e).get(`payload`);if(!s)return o;let c;try{c=JSON.parse(s)}catch{return log.warn(`failed to parse Slack interaction payload`),o}if(c?.type===`view_submission`)return handleViewSubmission(c,n,a);let l=parseBlockActionsPayload(c);if(!l)return o;let d=l.actions.find(e=>isFreeformAction(e.actionId));if(d)return await openFreeformModal({payload:c,interaction:l,freeformAction:d,deps:a}),o;let m=slackContinuationToken(l.channelId,l.threadTs),h=l.actions.map(deriveHitlResponse).filter(e=>e!==null);if(h.length>0){let e=l.actions[0]?.user;if(!e)return o;n.waitUntil(n.send({inputResponses:h},{auth:buildSlackAuthContext({channelId:l.channelId,teamId:l.teamId,threadTs:l.threadTs,userId:e.id,userName:e.username??e.name}),continuationToken:m,state:{channelId:l.channelId,threadTs:l.threadTs,teamId:l.teamId??null,triggeringUserId:e.id}}).catch(e=>{log.error(`HITL interaction delivery failed`,{error:e})})),n.waitUntil(updateAnsweredHitlCard(l,a).catch(e=>{log.error(`HITL answered-card update failed`,{error:e})}))}let g=a.config.onInteraction;if(g){let e=l.actions.filter(e=>!isHitlAction(e.actionId));if(e.length>0){let{thread:r,slack:i}=buildSlackBinding({botToken:a.config.credentials?.botToken,channelId:l.channelId,threadTs:l.threadTs,teamId:l.teamId}),o={thread:r,slack:i};for(let t of e)n.waitUntil(Promise.resolve(g(t,o)).catch(e=>{log.error(`custom interaction handler failed`,{error:e})}))}}return o}async function openFreeformModal(e){let t=e.payload.trigger_id;if(typeof t!=`string`||t.length===0){log.warn(`freeform button click missing trigger_id — cannot open modal`);return}let i=freeformRequestIdFromActionId(e.freeformAction.actionId)??e.freeformAction.value;if(!i){log.warn(`freeform button click missing requestId`);return}let a=e.freeformAction.messageTs;if(!a){log.warn(`freeform button click missing messageTs`);return}let o=buildFreeformModalView({metadata:{continuationToken:slackContinuationToken(e.interaction.channelId,e.interaction.threadTs),channelId:e.interaction.channelId,threadTs:e.interaction.threadTs,messageTs:a,requestId:i},prompt:readPromptTextFromBlocks(e.interaction.messageBlocks)}),s=await resolveSlackBotToken(e.deps.config.credentials?.botToken),c=await fetch(`https://slack.com/api/views.open`,{method:`POST`,headers:{authorization:`Bearer ${s}`,"content-type":`application/json; charset=utf-8`},body:JSON.stringify({trigger_id:t,view:o})});c.ok||log.error(`Slack views.open returned non-2xx`,{status:c.status})}async function handleViewSubmission(e,t,n){let r=new Response(`ok`,{status:200}),c=e.view;if(c?.callback_id!==HITL_FREEFORM_MODAL_CALLBACK_ID)return r;let l;try{l=JSON.parse(c.private_metadata??``)}catch{return log.warn(`freeform view_submission carries invalid private_metadata`),r}if(!l.continuationToken||!l.requestId||!l.messageTs||!l.channelId||!l.threadTs)return r;let u=c.state?.values?.[HITL_FREEFORM_MODAL_BLOCK_ID]?.[HITL_FREEFORM_MODAL_ACTION_ID]?.value,d=typeof u==`string`?u:``;if(d.length===0)return r;let f=e.team,p=e.user,m=p.id,h=p.team_id??f?.id??null;return t.waitUntil(t.send({inputResponses:[{requestId:l.requestId,text:d}]},{auth:buildSlackAuthContext({channelId:l.channelId,teamId:h,threadTs:l.threadTs,userId:p.id,userName:p.username??p.name}),continuationToken:l.continuationToken,state:{channelId:l.channelId,threadTs:l.threadTs,teamId:h,triggeringUserId:m}}).catch(e=>{log.error(`freeform answer delivery failed`,{error:e})})),t.waitUntil(updateAnsweredFreeformCard({channelId:l.channelId,messageTs:l.messageTs,answerLabel:d,userId:m??void 0,deps:n}).catch(e=>{log.error(`freeform answered-card update failed`,{error:e})})),r}async function updateAnsweredHitlCard(e,t){let r=e.actions.find(e=>isHitlAction(e.actionId));if(!r||!r.messageTs)return;let i=r.label??r.selectedOptionValue??r.value;if(!i)return;let a=buildAnsweredBlocks({promptBlock:findPromptBlock(e.messageBlocks),answerLabel:i,userId:r.user.id}),o=await resolveSlackBotToken(t.config.credentials?.botToken),s=await fetch(`https://slack.com/api/chat.update`,{method:`POST`,headers:{authorization:`Bearer ${o}`,"content-type":`application/json; charset=utf-8`},body:JSON.stringify({channel:e.channelId,ts:r.messageTs,blocks:a,text:`Answered: ${i}`})});if(!s.ok)throw Error(`Slack chat.update returned HTTP ${s.status}`)}async function updateAnsweredFreeformCard(e){let t=buildAnsweredBlocks({promptBlock:void 0,answerLabel:e.answerLabel,userId:e.userId}),r=await resolveSlackBotToken(e.deps.config.credentials?.botToken),i=await fetch(`https://slack.com/api/chat.update`,{method:`POST`,headers:{authorization:`Bearer ${r}`,"content-type":`application/json; charset=utf-8`},body:JSON.stringify({channel:e.channelId,ts:e.messageTs,blocks:t,text:`Answered: ${e.answerLabel}`})});if(!i.ok)throw Error(`Slack chat.update returned HTTP ${i.status}`)}export{handleInteractionPost,parseBlockActionsPayload};