Skip to main content
Agent Flow schema and MCP server are preview features

Agent flow schema and hosted MCP servers are preview features that require Prismatic Support to enable the feature for you. Please contact Prismatic Support if you'd like to opt-in to the MCP Preview.

Common Use Cases

This guide provides practical patterns for implementing AI capabilities in your Prismatic integrations. Each section explains the core concept, shows how to implement it, and provides real-world examples.

Data Enrichment

Enrich existing data by using AI agents equipped with tools. Agents gather information from multiple sources, analyze content, and augment records with AI-generated insights.

CRM Lead Enrichment

Automatically research incoming leads and enrich them with company information, scoring, and insights before adding to your CRM. This example demonstrates researching a lead's company, analyzing fit criteria, and creating an enriched record in Salesforce.

/**
* Enrich Incoming Lead Flow
*
* Researches incoming leads using web search, enriches them with company data,
* and creates them in Salesforce.
*/
export const enrichIncomingLead = flow({
name: "Enrich Incoming Lead",
description: "Research and enrich leads before creating them in Salesforce",
onExecution: async (context, params) => {
const { configVars } = context;
const triggerPayload = params.onTrigger.results.body as TriggerPayload;

// Step 1: Set up web search tool
const setupWebResearchTool =
await context.components.openai.createWebSearchTool<CreateToolResult>({
name: "Web Search",
searchContextSize: "high",
});

// Step 2: Create lead research agent
const createLeadResearchAgent =
await context.components.openai.createAgent<CreateAgentResult>({
instructions: `When provided a new lead, attempt to research them based on their domain.
Look for their industry, employee count, and the problem the company solves`,
modelName: "gpt-5-mini-2025-08-07",
name: "Lead Researcher",
outputSchema: {
type: "object",
properties: {
company: {
type: "string",
},
employeeCount: {
type: "number",
},
vertical: {
type: "string",
},
companyDescription: {
type: "string",
},
},
required: ["employeeCount", "vertical", "companyDescription"],
additionalProperties: false,
},
outputSchemaName: "output",
outputSchemaStrict: false,
tools: [setupWebResearchTool.data],
});

// Step 3: Research and enrich the lead
const researchAndEnrichLead =
await context.components.openai.runAgent<AgentExecutionResult>({
agentConfig: createLeadResearchAgent.data,
maxTurns: "10",
openaiConnection: configVars["OpenAI Connection"],
userInput: `Research this lead on the web:
Company: ${triggerPayload.data.company}
Email: ${triggerPayload.data.email}
Name: ${triggerPayload.data.firstName} ${triggerPayload.data.lastName}`,
});

const enrichedData = researchAndEnrichLead.data.finalOutput;

// Step 4: Create lead in Salesforce
const createLead = await context.components.salesforce.createLead({
company: triggerPayload.data.company,
connection: configVars["Salesforce Connection"],
description: enrichedData.companyDescription,
email: triggerPayload.data.email,
employeeCount: enrichedData.employeeCount.toString(),
firstName: triggerPayload.data.firstName,
lastName: triggerPayload.data.lastName,
leadStatus: "Open",
version: "63.0",
});

return { data: createLead };
},
});

AI Routing and Classification

Use AI to analyze incoming data and make intelligent decisions. The AI evaluates content against defined criteria, classifies it, and produces a structured output to enable branching and intelligent routing.

Duplicate Record Detection

Prevent duplicate records by using AI to analyze and compare incoming data against existing records. This example queries for potential matches and uses AI classification to determine if an account already exists, with confidence thresholds to ensure accuracy.

/**
* Check for Duplicates Flow
*
* Analyzes incoming leads against existing Salesforce accounts to identify and prevent
* duplicate entries. Uses AI classification to determine similarity with high confidence.
*/
export const checkForDuplicates = flow({
name: "Check for Duplicates",
description:
"Prevent duplicate lead creation by checking against existing Salesforce accounts",
onExecution: async (context, params) => {
const { configVars } = context;
const incomingLead = params.onTrigger.results.body.data as IncomingLeadData;

// Step 1: Query Salesforce for potential duplicate accounts
const findAccounts =
await context.components.salesforce.query<SalesforceQueryResult>({
connection: configVars["Salesforce Connection"],
queryString: `SELECT Id, Name, Website FROM Account
WHERE Name like '${incomingLead.company}%'`,
version: "63.0",
});

// Step 2: Use AI to classify if the lead is a duplicate
const classification =
await context.components.openai.classifyAndBranch<ClassificationResult>({
openaiConnection: configVars["OpenAI Connection"],
model: "gpt-5-mini-2025-08-07",
branches: {
Duplicate:
"The name, domain, or firmographics suggest it is a duplicate",
"Not a Duplicate":
"The account appears to be unique based on the provided information.",
Else: "You are unable to determine if it is a duplicate.",
},
classificationInstructions: `Analyze the account and possible duplicates.
Use all available information to determine if this is a duplicate account.`,
inputText: `New Account: ${JSON.stringify(incomingLead)}
Possible Duplicates: ${JSON.stringify(findAccounts.data.records)}`,
});

// Step 3: Route based on classification result
if (classification.data.selectedBranch === "Not a Duplicate") {
// Create new lead in Salesforce
const createLead =
await context.components.salesforce.createLead<CreateLeadResult>({
connection: configVars["Salesforce Connection"],
company: incomingLead.company,
email: incomingLead.email,
firstName: incomingLead.firstName,
lastName: incomingLead.lastName,
leadStatus: "Open",
version: "63.0",
});

return { data: createLead };
}
return { data: null };
},
});

Smart Data Extraction

Transform unstructured content into structured data by defining JSON schemas that enforce consistent output formats. AI agents use these schemas to parse documents, logs, and other content into predictable, validated structures

Error Logs to Jira Issues

Convert application logs into structured Jira issues by extracting error details, severity, and priority. This example uses a JSON schema to enforce output structure, ensuring the AI returns data in the exact format needed for ticket creation.

/**
* Create Jira Issue for Error Logs Flow
*
* This flow automatically analyzes system error logs and creates Jira issues
* for significant errors that require attention. It uses AI to intelligently
* classify errors, determine their severity, and generate appropriate ticket
* descriptions.
*/
export const createJiraIssueForErrorLogs = flow({
name: "Create Jira Issue for Error Logs",
description:
"Automatically analyze error logs and create Jira issues for critical errors using AI",
onExecution: async (context, params) => {
const { configVars } = context;

const logData = params.onTrigger.results.body.data as TriggerPayload;

// Step 1: Create AI agent for log analysis
const createLogAnalyzer =
await context.components.openai.createAgent<AgentCreationResult>({
instructions: `You are a log analyzer that creates Jira issues from system errors.
## Your Task
1. Identify the main error in the logs
2. Extract the details necessary to create a Jira issue

## Priority Rules
- CRITICAL logs or customer-facing errors → High
- ERROR logs → Medium
- WARN logs → Low

## Severity Scale
1. **Minimal** - Cosmetic issue, no functional impact
2. **Minor** - Small feature affected, easy workaround exists
3. **Moderate** - Feature degraded, some users impacted
4. **Major** - Feature broken, many users affected
5. **Critical** - System down, data loss, or security issue

## Confidence Score
Rate 0.0 to 1.0 based on:
- Clear error with stack trace → 0.8-1.0
- Timeout or connection issue → 0.6-0.8
- Warning that might be transient → 0.3-0.5
- Unclear if action needed → 0.0-0.3

## Important Guidelines
- Keep the title clear and actionable`,
mcpServers: [],
modelName: "gpt-5-mini-2025-08-07",
name: "Log Analysis Expert",
outputSchema: JSON.stringify(JIRA_ISSUE_SCHEMA),
outputSchemaName: "jira_issue_output",
outputSchemaStrict: false,
tools: [],
});

// Step 2: Analyze logs and extract Jira issue data
const extractJiraIssueInputs =
await context.components.openai.runAgent<AgentExecutionResult>({
agentConfig: createLogAnalyzer.data,
maxTurns: "10",
openaiConnection: configVars["OpenAI Connection"],
previousResponseId: "",
userInput: `Analyze the following logs and attempt to extract the necessary fields to create a Jira issue:\n\n${JSON.stringify(
logData,
)}`,
});

const issueData = extractJiraIssueInputs.data.finalOutput;

// Step 3: Check confidence threshold before creating issue
if (issueData.confidence < 0.3) {
return {
data: {
message: "No significant errors requiring Jira ticket",
confidence: issueData.confidence,
analysis: issueData,
success: true,
},
};
}

// Step 4: Create Jira issue
const createIssue =
await context.components.atlassianJira.createIssue<JiraIssueCreationResult>(
{
issueTypeId: configVars["Issue Type"],
projectId: configVars["Project"],
summary: issueData.title,
description: issueData.description,
jiraConnection: configVars["Jira Connection"],
},
);

// Step 5: Return comprehensive result
return {
data: {
jiraIssue: createIssue.data,
analysis: {
title: issueData.title,
priority: issueData.priority,
severity: issueData.severity,
confidence: issueData.confidence,
},
success: true,
},
};
},
});

/**
* Jira issue output schema for structured data extraction
*/
const JIRA_ISSUE_SCHEMA = {
$schema: "http://json-schema.org/draft-07/schema#",
title: "JiraIssueOutput",
type: "object",
required: [
"title",
"description",
"priority",
"issue_type",
"severity",
"confidence",
],
properties: {
title: {
type: "string",
maxLength: 100,
description: "Brief description of the error",
},
description: {
type: "string",
description:
"Detailed description including what happened, when, and error details",
},
priority: {
type: "string",
enum: ["High", "Medium", "Low"],
description: "Issue priority level",
},
issue_type: {
type: "string",
enum: ["Bug"],
description: "Type of Jira issue. Always capitalized",
},
severity: {
type: "integer",
minimum: 1,
maximum: 5,
description: "Impact severity (1=minimal, 5=critical)",
},
confidence: {
type: "number",
minimum: 0.0,
maximum: 1.0,
description: "Confidence score that this needs a Jira ticket",
},
},
additionalProperties: false,
};

Extract Invoices from PDFs in Dropbox

Automatically process PDF invoices from a Dropbox folder by extracting structured data and creating records in your accounting system. This example shows how to combine file monitoring, PDF parsing, and AI extraction with schema validation.

/* Import Receipts from PDFs Flow
This flow automatically processes PDF files from a Dropbox folder,
identifies receipts/invoices using AI classification, and extracts
structured data from valid documents.
The flow runs every 5 minutes and performs the following steps:
1. Lists all files in the configured Dropbox import folder
2. Downloads each PDF file
3. Uploads files to OpenAI for processing
4. Classifies documents to identify receipts/invoices
5. Extracts structured data from valid receipts
@returns Extracted receipt data or empty object if no valid receipts found
*/
export const importReceiptsFromPdFs = flow({
name: "Import Receipts from PDFs",
description:
"Automatically process PDF receipts from Dropbox, classify documents, and extract structured receipt data using AI",
onExecution: async (context) => {
const { configVars } = context;
const processedReceipts: ExtractedReceipt[] = [];

const listImportFolder =
await context.components.dropbox.listFolder<ListImportFolderResult>({
dropboxConnection: configVars["Dropbox Connection"],
path: configVars["Import Folder"],
});

// Step 1: Process each file
for (const file of listImportFolder.data.result.entries) {
// Step 2: Download the file from Dropbox
const downloadFile = await context.components.dropbox.downloadFile({
dropboxConnection: configVars["Dropbox Connection"],
path: file.path_lower,
});

// Step 3: Upload file to OpenAI for processing
const uploadFile =
await context.components.openai.uploadFile<OpenAIFileUploadResult>({
connection: configVars["OpenAI Connection"],
file: downloadFile.data as any,
filename: file.name,
purpose: "assistants",
timeout: 10000,
});

// Step 4: Classify the document to determine if it's a receipt/invoice
const agentClassifyAndBranch =
await context.components.openai.classifyAndBranch<ClassificationResult>(
{
agentMcpServers: [],
agentTools: [],
branches: {
"Needs Processing":
"The analyzed file is an invoice or receipt that contains transaction data.",
},
classificationInstructions: `Analyze the provided file carefully. Determine if it is an invoice or receipt that should be processed.
A document should be classified as "Needs Processing" if it contains:

- Transaction details (items, prices, totals)
- Vendor/store information
- Date of transaction
- Receipt or invoice number

If the document doesn't contain these elements or you cannot determine its type, return the "Else" branch.
Always return the required output schema with confidence and reasoning.`,
fileIds: [uploadFile.data.id],
inputText: `Analyze this PDF file and determine if it's a receipt or invoice that contains extractable transaction data.`,
model: "gpt-5-2025-08-07",
openaiConnection: configVars["OpenAI Connection"],
},
);

// Step 5: Extract structured data from the receipt/invoice
if (agentClassifyAndBranch.branch === "Needs Processing") {
// Create pdf extraction ai agent
const pdfExtractionAgent = await context.components.openai.createAgent({
instructions: `You are an expert at analyzing PDFs and extracting receipt and invoice information.
Your task is to:
1. Carefully read and analyze the entire document
2. Extract all transaction details including items, prices, and totals
3. Identify store/vendor information
4. Extract dates in ISO format
5. Ensure all numeric values are accurate
6. If a receipt ID is not visible, generate one based on the store name and date
Be thorough and accurate in your extraction.`,
mcpServers: [],
modelName: "gpt-5-mini-2025-08-07",
name: "PDF Receipt Data Extractor",
outputSchema: JSON.stringify(RECEIPT_SCHEMA),
outputSchemaName: "receipt_data",
outputSchemaStrict: false,
tools: [],
});

// Run the extraction agent against the uploaded pdf
const extractedReceipt = await context.components.openai.runAgent<{
data: { finalOutput: ExtractedReceipt };
}>({
agentConfig: pdfExtractionAgent.data,
maxTurns: "10",
openaiConnection: configVars["OpenAI Connection"],
fileIds: [uploadFile.data.id],
userInput: `Please analyze this PDF document and extract all receipt/invoice information according to the provided schema.
Be thorough in identifying all line items, calculating totals, and extracting vendor information.`,
});
processedReceipts.push(extractedReceipt.data.finalOutput);
}
}

// Return summary of processed receipts
return {
data: {
processedReceipts,
summary: {
totalProcessed: processedReceipts.length,
totalFiles: listImportFolder.data.result.entries.length,
success: true,
},
},
};
},
});

Conversational Interfaces

Build AI-powered chat interfaces that maintain conversation context across multiple interactions. Agents store and retrieve chat history to understand context, process natural language, and execute workflows based on user intent.

Slack Assistant

Create an AI assistant that responds to user messages in Slack threads, providing application intelligence directly in the customer's Slack.

export const eventHandler = flow({
name: "Slack Message Handler",
description:
"Handles Slack Events and generates responses with OpenAI Assistant SDK",
onExecution: async (context, params) => {
const { configVars, customer, integration, instanceState } = context;

const connection = configVars["Slack Connection"];
const openaiKey = util.types.toString(
configVars.OPENAI_API_KEY.fields.apiKey,
);
const prismaticRefreshToken = util.types.toString(
configVars.PRISMATIC_REFRESH_TOKEN,
);

// Set OpenAI API key globally
setDefaultOpenAIKey(openaiKey);

// Build agent tools
const tools = await buildTools(
customer.externalId !== "testCustomerExternalId" ? customer : undefined,
prismaticRefreshToken,
integration.id,
);

const agent = new Agent({
name: "Slack Assistant",
instructions: configVars.SYSTEM_PROMPT,
tools,
});

const executionId = params.onTrigger.results.executionId;

// Create slack assistant
const assistant = new Assistant({
userMessage: async (args) => {
const { client, message, logger, setStatus } = args;
if (
!("text" in message) ||
!("thread_ts" in message) ||
!message.text ||
!message.thread_ts
) {
return;
}

setStatus("is typing...");

const conversationId = message.thread_ts;
const userInput = message.text;

try {
// Get stored state for this conversation
const convState = instanceState[conversationId];
const lastResponseId = convState.lastResponseId;

// Run the agent with the message
const result = await run(agent, [user(userInput)], {
previousResponseId: lastResponseId,
});

// Handle interruptions
if (result.interruptions && result.interruptions.length > 0) {
const firstInterruption = result.interruptions[0];

// Store state in instanceState
instanceState[conversationId] = {
state: result.state.toString(),
lastResponseId: result.lastResponseId,
pendingInterruption: {
functionId: firstInterruption.rawItem.id!,
name: firstInterruption.rawItem.name,
arguments: firstInterruption.rawItem.arguments,
},
};

// Post approval block
await client.chat.postMessage({
channel: message.channel,
thread_ts: message.thread_ts,
blocks: createApprovalBlocks(
firstInterruption.rawItem.name,
firstInterruption.rawItem.arguments,
executionId,
),
text: `Approval required for tool: ${firstInterruption.rawItem.name}`,
metadata: {
event_type: "tool_approval",
event_payload: { conversationId },
},
});
} else {
// Store lastResponseId for next message
instanceState[conversationId] = {
lastResponseId: result.lastResponseId,
};

// Post response
await client.chat.postMessage({
channel: message.channel,
thread_ts: message.thread_ts,
text: result.finalOutput || "I couldn't generate a response.",
metadata: {
event_type: "execution_id",
event_payload: { execution_id: executionId },
},
});
}
} catch (e) {
await args.say({
text: "I encountered an error processing your request. Please try again.",
});
}
},

threadStarted: async (args) => {
await args.say("Hi! I'm your AI assistant. How can I help you today?");
await args.saveThreadContext();
},
});

const actionHandlers: ActionHandlers = {
onToolApproval: async ({
approved,
previousExecutionId,
userId,
conversationId,
channelId,
client,
updateMessage,
}) => {
// Get stored state for this conversation
const convState = instanceState[conversationId] as ConversationState;

// Deserialize and apply users decision
let agentState = await RunState.fromString(agent, convState.state);
const interrupts = agentState.getInterruptions();
const interrupt = interrupts[0];

if (approved) {
agentState.approve(interrupt);
} else {
agentState.reject(interrupt);
}

// Update message to show decision
await updateMessage(
approved
? `✅ Tool execution approved by <@${userId}>`
: `❌ Tool execution denied by <@${userId}>`,
);

// Continue execution
const result = await run(agent, agentState);

instanceState[conversationId] = {
lastResponseId: result.lastResponseId,
} as ConversationState;

// Post final response
await client.chat.postMessage({
channel: channelId,
thread_ts: conversationId,
text: result.finalOutput || "Task completed.",
metadata: {
event_type: "execution_id",
event_payload: {
execution_id: executionId,
},
},
});
},
};

const app = App(connection, { assistant, actionHandlers });
const handler = await app.start();
await handler(params.onTrigger.results);

return {
data: {
result: "Event processed successfully",
},
};

},
});

Next.js Chatbot

Expose your Prismatic integrations as tools for external AI applications using the Model Context Protocol (MCP). This Next.js example demonstrates how to discover and invoke your deployed integration flows from a standalone AI chat interface.

Try the example →

import { generateToken } from "@/util/token";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { experimental_createMCPClient as createMCPClient } from "ai";
import { getTools } from "@/util/tools";
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";
// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

// Utility function to get available tools from Prismatic MCP
export const getTools = async () => {
const prismaticAccessToken = generateToken();

const MCP_URL = process.env.MCP_URL || "https://mcp.prismatic.io/mcp";

const transport = new StreamableHTTPClientTransport(new URL(MCP_URL), {
requestInit: {
headers: {
Authorization: `Bearer ${prismaticAccessToken}`,
},
},
});

const mcpClient = await createMCPClient({
transport: transport,
onUncaughtError(error) {
console.error("Error in MCP client:", error);
throw error;
},
});

const tools = await mcpClient.tools();
return tools;
};

// Chat API Endpoint using Prismatic flows as tools
export async function POST(req: Request) {
const { messages } = await req.json();
// Fetch prismatic tools
const mcpTools = await getTools();

const result = streamText({
model: openai("gpt-5"),
messages,
tools: { ...mcpTools },
maxSteps: 20,
onError: (error) => {
console.error("Error in AI response:", error);
throw error;
},
});

return result.toDataStreamResponse();
}

Human-in-the-Loop Approval Flows

Combine AI automation with human oversight by restricting tool access and implementing approval gates for sensitive operations. Define permission levels (read vs. write), create approval workflows that pause execution for human review, and maintain audit logs.

API Operation Tools Requiring Approval

Gate sensitive API operations behind human approval workflows. This example shows how to differentiate between read-only operations (no approval needed) and write operations (approval required), with a mechanism to pause execution and resume after human review.

export const approvalFlow = flow({
name: "Approval Flow",
description: "Demonstrates wrapping REST APIs as AI tools for interaction",
onExecution: async ({ configVars }, params) => {
const openaiKey = util.types.toString(
configVars.OPENAI_API_KEY.fields.apiKey,
);

// Set the OpenAI API key
setDefaultOpenAIKey(openaiKey);

// Create agent with API tools
const agent = new Agent({
name: "API Assistant",
instructions: `You are an API assistant that helps users interact with their data.
Use the available tools to fulfill user requests.`,
tools: [
// Read-only tools
apiTools.getCurrentUserInfo,
apiTools.getPosts,
apiTools.getPost,
apiTools.getPostComments,

// Write tools
apiTools.createPost, //needsApproval: true
apiTools.updatePost, //needsApproval: true
],
});

// Get the message from the payload
const {
message,
conversationId,
lastResponseId,
state,
interruptions: userResponses,
} = params.onTrigger.results.body.data as ChatRequest;

if (userResponses && state) {
let agentState = await RunState.fromString(agent, state);
agentState = updateStateWithUserResponse(
agentState,
agentState.getInterruptions(),
userResponses,
);

const result = await run(agent, agentState);
const interruptions: Interruption[] = handleInterrupt(
result.interruptions,
);
return {
data: {
response: interruptions.length > 0 ? undefined : result.finalOutput,
interruptions,
lastResponseId: result.lastResponseId,
conversationId,
state: result.state.toString(),
},
};
} else {
if (!message) {
throw new Error("Message is required to run the agent");
}

// Run the agent with the message
const result = await run(agent, [user(message)], {
previousResponseId: lastResponseId,
});

const interruptions: Interruption[] = handleInterrupt(
result.interruptions,
);
return {
data: {
response: interruptions.length > 0 ? undefined : result.finalOutput,
interruptions,
lastResponseId: result.lastResponseId,
conversationId,
state: result.state.toString(),
},
};
}
},
});

function updateStateWithUserResponse(
state: RunState<unknown, Agent<unknown, "text">>,
interrupts: RunToolApprovalItem[],
userResponses: Interruption[],
) {
for (const userResponse of userResponses) {
const interrupt = interrupts.find(
(i) => i.rawItem.id === userResponse.functionId,
);
if (interrupt) {
if (userResponse.approved) {
state.approve(interrupt);
} else {
state.reject(interrupt);
}
}
}
return state;
}

function handleInterrupt(interrupts: RunToolApprovalItem[]): Interruption[] {
if (interrupts.length === 0) {
return [];
}
const userApprovalItems = interrupts.map((intr) => ({
functionId: intr.rawItem.id!,
name: intr.rawItem.name,
approved: false,
arguments: intr.rawItem.arguments,
}));
return userApprovalItems;
}

Incident Monitoring Slackbot

Monitor backend services for errors and automatically alert your team via Slack with AI-generated summaries. Enable operators to trigger deeper investigation or create incidents directly from Slack, combining automated detection with human decision-making.

Incident Monitoring
/*
Flow for processing new incident alerts with AI agent assistance.
This flow:
1. Creates tools for the AI agent (get on-call staff, create incident)
2. Configures an AI agent with incident response capabilities
3. Runs the agent to process the alert
4. If approval is needed, posts an interactive message to Slack
5. Stores agent state for resumption when approval is received */
export const newIncidentAlert = flow({
name: "New Incident Alert",
description: "Create a new incident from an incoming alert",
onExecution: async (context, params) => {
const { configVars } = context;
// Setup tools for the agent
const agentCreateIncidentTool =
await context.components.openai.createFlowTool<FlowToolResult>({
flowName: "Create Incident",
requiresApproval: true,
strictMode: false,
toolDescription: "Create a new incident using the provided description",
});

const agentGetOnCallStaffTool =
await context.components.openai.createFlowTool<FlowToolResult>({
flowName: "Get On Call Staff",
requiresApproval: false,
strictMode: false,
toolDescription: "Get On Call Staff",
});

// Create the AI agent with our configuration
const agentCreateAssistantAgent =
await context.components.openai.createAgent<AgentConfig>({
instructions: AGENT_INSTRUCTIONS,
mcpServers: [],
modelName: "gpt-5-2025-08-07",
name: "Acme SaaS Assistant",
outputSchema: JSON.stringify(INCIDENT_RESPONSE_SCHEMA),
outputSchemaName: "output",
outputSchemaStrict: false,
tools: [
agentCreateIncidentTool.data,
agentGetOnCallStaffTool.data,
],
});

// Prepare the alert input for the agent
const setupAlertInputPrompt = `You must create a new incident from the provided alert for the on-call user. First, use a tool to get the on call staff, second create an incident using the create incident tool from the following alert. \nAlert Detected: ${JSON.stringify(
params.onTrigger.results.body.data,
)}`;

// Run the agent to process the alert
const runAgentCreateIncident =
await context.components.openai.runAgent<AgentRunResult>({
agentConfig: agentCreateAssistantAgent.data,
fileIds: [],
handoffs: [],
history: "",
maxTurns: "10",
openaiConnection: configVars["OpenAI Connection"],
previousResponseId: "",
userInput: setupAlertInputPrompt,
});

// Handle approval interruptions
if (runAgentCreateIncident.data.hasInterruptions) {
const approvalRequest =
runAgentCreateIncident.data.pendingApprovals?.[0].approvalRequest;

const approvalArgs = {
...JSON.parse(runAgentCreateIncident.data.pendingApprovals[0].arguments),
approvalRequest,
};

// Build approval message blocks
const createApprovalBlocks = buildApprovalMessage(approvalArgs);

// Post approval request to Slack
await context.components.slack.postBlockMessage({
channelName: configVars["Alert Channel"],
connection: configVars["slackConnection"],
blocks: createApprovalBlocks as any,
message: "An approval is required to create a new incident",
});

// Store agent state for resumption after approval
const crossFlowState = context.crossFlowState;
crossFlowState[approvalArgs.anomaly_id] = {
...runAgentCreateIncident.data,
agentConfig: agentCreateAssistantAgent.data,
};
return {
data: { interrupted: true },
crossFlowState,
};
}

},
});

/*
Handles Slack events and interactions for the incident management system.
This flow processes approval actions from Slack buttons when users decide
whether to create an incident from an anomaly alert.

Integration flow:
1. newIncidentAlert flow detects anomaly and requests approval
2. User clicks approve/investigate/ignore button in Slack
3. This flow processes the interaction and resumes the AI agent
4. Agent completes the incident creation or rejection
5. Result is posted back to Slack
*/

export const handleSlackEventsAndInteractions = flow({
name: "Handle Slack Events and Interactions",
onExecution: async (context, params) => {
const triggerResults = params.onTrigger.results.body;

// Decode the URL-encoded payload from Slack
const rawBody = util.types.toString(triggerResults.data);
const formData = new URLSearchParams(rawBody);
const payloadString = formData.get("payload");

if (!payloadString) {
console.log("No payload found in request body");
return { data: { error: "No payload found" } };
}

// Parse the JSON payload
const interactionPayload = JSON.parse(payloadString) as any;

// Build response data
const responseData = {
type: interactionPayload.type,
};
// Add common fields
if (interactionPayload.trigger_id) {
responseData.trigger_id = interactionPayload.trigger_id;
}

if (interactionPayload.user) {
responseData.user = interactionPayload.user;
}

// Process different interaction types
switch (interactionPayload.type) {
case "block_actions": {
const blockAction = interactionPayload as BlockAction;
responseData.actions = blockAction.actions;
responseData.response_url = blockAction.response_url;
responseData.container = blockAction.container;

// Process approval action
const action = blockAction.actions[0];

// Parse the action value
const approvalAction = parseApprovalAction(action);
const { anomalyId, functionId, approved } = approvalAction;


try {
const storedState = retrieveStoredAgentState(context, anomalyId);
const pendingApprovals = storedState.pendingApprovals || [];

const matchingApproval = findMatchingApproval(
pendingApprovals,
functionId,
);

// Create approval response
const approvalResponses = createApprovalResponse(
functionId,
approved,
action.action_id,
);

// Resume the agent with approval response
const resumeResult = await resumeAgent(
context,
storedState,
approvalResponses,
);

// Handle the agent's final output
const finalOutput = resumeResult.data.finalOutput;
await postIncidentResult(context, finalOutput);

// Update the original approval message
await updateApprovalMessage(
context,
blockAction,
approved,
action.action_id,
);

// Clean up stored state
await cleanupStoredState(context, anomalyId);

responseData.handled = true;
responseData.anomalyId = anomalyId;
responseData.finalOutput = finalOutput;
} catch (error) {
console.error("Error handling approval:", error);
responseData.error =
error instanceof Error ? error.message : String(error);
}

break;
}

default:
console.log("Unsupported interaction type:", interactionPayload);
responseData.raw = interactionPayload;
}

return { data: responseData };
},
});