@botpress/adk-cli
Version:
Command-line interface for the Botpress Agent Development Kit (ADK)
657 lines (518 loc) • 15.6 kB
Markdown
# Integration Actions
Call actions from installed integrations using a simple, type-safe API.
## Quick Start
```typescript
import { actions } from '@botpress/runtime'
// Call integration action
const { items } = await actions.linear.issueList({
first: 10,
filter: { state: { name: { eq: 'In Progress' } } },
})
```
## Overview
Integration actions allow you to call functionality from installed integrations (Slack, Linear, GitHub, etc.) using a unified, type-safe API. Each integration exposes actions that map to its capabilities.
**Pattern:**
```typescript
await actions.{integration}.{actionName}(params);
```
**Available in:**
- Actions
- Tools
- Workflows
- Conversations
- Triggers
## Installation
Before using integration actions, add the integration to your project:
```bash
# Add integration
adk integrations add slack@2.5.5
# Or with custom alias
adk integrations add linear@2.0.0 --alias my-linear
# Start dev to configure
adk dev
```
Then configure the integration in the Dev Console at `localhost:3001` or via `adk integrations configure`.
## Basic Usage
### Importing Actions
```typescript
import { actions } from '@botpress/runtime'
```
### Calling Actions
```typescript
// Slack - Send message
await actions.slack.sendMessage({
channel: '#general',
text: 'Hello from bot!',
})
// Linear - List issues
const { items } = await actions.linear.issueList({
first: 10,
filter: { state: { name: { eq: 'In Progress' } } },
})
// GitHub - Create issue
await actions.github.createIssue({
owner: 'myorg',
repo: 'myrepo',
title: 'Bug report',
body: 'Issue description',
})
```
### Alternative: Bracket Notation
Use bracket notation when integration names contain special characters or when accessing them dynamically:
```typescript
// Useful for dynamic integration names
const integrationName = 'intercom'
await actions[integrationName].getConversation({
intercomConversationId: 'conv_123',
})
// Or when the integration name has special characters
await actions['my-integration'].someAction({
param: 'value',
})
```
### Type Safety
All integration actions are fully typed with TypeScript:
```typescript
type LinearIssue = Awaited<ReturnType<typeof actions.linear.getIssue>>
// TypeScript knows the exact shape of the result
const issue = await actions.linear.getIssue({ id: 'ISS-123' })
// issue.title, issue.description, issue.state, etc. are all typed
```
## Converting to AI Tools
Integration actions can be converted to AI-callable tools using `.asTool()`:
```typescript
await execute({
instructions: 'Help the user browse the web',
tools: [
// Convert integration action to tool
actions.browser.captureScreenshot.asTool(),
// Mix with custom tools
customSearchTool,
],
})
```
**How it works:**
- The action's input/output schemas become the tool's parameters
- The action's description (if any) helps the AI understand when to use it
- The AI can call the tool during autonomous execution
## Common Integration Actions
### Slack Integration
```typescript
// Send message
await actions.slack.sendMessage({
channel: '#general',
text: 'Hello!',
blocks: [
{
type: 'section',
text: { type: 'mrkdwn', text: '*Hello* from bot!' },
},
],
})
// Add reaction
await actions.slack.addReaction({
channel: 'C123456',
timestamp: '1234567890.123456',
name: 'thumbsup',
})
// Upload file
await actions.slack.uploadFile({
channels: ['#general'],
file: fileBuffer,
filename: 'report.pdf',
title: 'Monthly Report',
})
```
### Linear Integration
```typescript
// List issues with filters
const { items, meta } = await actions.linear.issueList({
first: 50,
filter: {
state: { name: { eq: 'In Progress' } },
assignee: { email: { eq: 'user@example.com' } },
},
orderBy: { createdAt: 'desc' },
})
// Get specific issue
const issue = await actions.linear.getIssue({
id: 'ISS-123',
})
// Create issue
await actions.linear.issueCreate({
teamId: 'team_123',
title: 'New feature request',
description: 'Detailed description',
priority: 1,
assigneeId: 'user_456',
})
// Update issue
await actions.linear.issueUpdate({
id: 'ISS-123',
stateId: 'state_done',
})
```
### Browser Integration
```typescript
// Browse pages (web scraping)
const { results } = await actions.browser.browsePages({
urls: ['https://docs.example.com'],
extractType: 'markdown',
maxPages: 10,
})
// Web search
const output = await actions.browser.webSearch({
query: 'Botpress ADK documentation',
maxResults: 5,
})
// Capture screenshot
const screenshot = await actions.browser.captureScreenshot({
url: 'https://example.com',
fullPage: true,
format: 'png',
})
```
### GitHub Integration
```typescript
// Create issue
await actions.github.createIssue({
owner: 'myorg',
repo: 'myrepo',
title: 'Bug: Login not working',
body: 'Steps to reproduce:\n1. ...',
labels: ['bug', 'priority-high'],
})
// Get repository
const repo = await actions.github.getRepository({
owner: 'myorg',
repo: 'myrepo',
})
// Create pull request
await actions.github.createPullRequest({
owner: 'myorg',
repo: 'myrepo',
title: 'Fix login issue',
head: 'feature-branch',
base: 'main',
body: 'This PR fixes the login issue',
})
```
### Discord Integration
```typescript
// Send message
await actions.discord.sendMessage({
channelId: '123456789',
content: 'Hello from bot!',
embeds: [
{
title: 'Notification',
description: 'Your task is complete',
color: 0x00ff00,
},
],
})
// Add reaction
await actions.discord.addReaction({
channelId: '123456789',
messageId: '987654321',
emoji: '✅',
})
```
### WhatsApp Integration
```typescript
// Send text message
await actions.whatsapp.sendMessage({
to: '+1234567890',
type: 'text',
text: { body: 'Hello from bot!' },
})
// Send template message
await actions.whatsapp.sendTemplate({
to: '+1234567890',
template: {
name: 'order_confirmation',
language: { code: 'en' },
components: [
{
type: 'body',
parameters: [{ type: 'text', text: '12345' }],
},
],
},
})
```
### Intercom Integration
```typescript
// Send admin message with attachments
await actions.intercom.sendAdminMessage({
adminId: 'admin_123',
intercomConversationId: 'conv_456',
messageType: 'comment',
body: 'Thank you for contacting us!',
attachment_urls: ['https://example.com/image.png'],
})
// Close/open conversation
await actions.intercom.closeConversation({
intercomConversationId: 'conv_456',
adminId: 'admin_123',
})
// Manage tags
await actions.intercom.updateConversationTags({
intercomConversationId: 'conv_456',
adminId: 'admin_123',
tagsToAdd: ['priority'],
tagsToRemove: ['pending'],
})
```
## Advanced: Building Custom Integration Actions
> **Note**: This section is for integration developers building custom integrations. If you're building a bot and using existing integrations (Slack, Linear, etc.), you can skip this section.
When building a custom integration, you define actions that bot developers can call via `actions.{yourIntegration}.{actionName}()`.
### Integration Action Structure
Here's how integration actions are implemented (example from Intercom integration):
```typescript
// In integrations/intercom/src/actions/send-admin-message.ts
import { RuntimeError } from '@botpress/client'
import * as bp from '../../.botpress'
export const sendAdminMessage: bp.IntegrationProps['actions']['sendAdminMessage'] = async ({ input }) => {
const url = new URL(`https://api.intercom.io/conversations/${input.intercomConversationId}/reply`)
const requestBody: Record<string, unknown> = {
message_type: input.messageType ?? 'comment',
type: 'admin',
body: input.body,
admin_id: input.adminId,
}
// Add optional parameters
if (input.attachment_urls && input.attachment_urls.length > 0) {
requestBody.attachment_urls = input.attachment_urls
}
// Call external API
const response = await fetch(url.toString(), {
method: 'POST',
headers: {
Authorization: `Bearer ${bp.secrets.TOKEN}`,
'Intercom-Version': '2.14',
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
})
// Error handling
if (!response.ok) {
const errorText = await response.text()
throw new RuntimeError(`Failed to send admin message: ${response.status} - ${errorText}`)
}
return await response.json()
}
```
### Integration Action Definition
Actions are defined in the integration's definition file:
```typescript
// In integrations/intercom/integration.definition.ts
import { Integration, z } from '@botpress/sdk'
export default new Integration({
actions: {
sendAdminMessage: {
title: 'Send Admin Message',
description: 'Send a message as an admin with optional attachments',
input: {
schema: z.object({
adminId: z.string(),
intercomConversationId: z.string(),
body: z.string(),
messageType: z.enum(['comment', 'note']).optional(),
attachment_urls: z.array(z.string().url()).max(10).optional(),
}),
},
output: {
schema: z.object({
id: z.string(),
body: z.string(),
createdAt: z.string(),
}),
},
},
// ... more actions
},
})
```
### Key Integration Action Patterns
1. **Use RuntimeError for failures** - Provides better error context to bot developers
2. **Validate external API responses** - Always check `response.ok` and handle errors
3. **Transform external data** - Map external API schemas to your action output schema
4. **Use bp.secrets for credentials** - Never hardcode tokens or API keys
5. **Handle optional parameters** - Check before adding to request body
6. **Type external API responses** - Use TypeScript types from SDK if available
## Using Integration Actions
### Composing Actions
```typescript
// In a workflow step
await step('sync-to-slack', async () => {
// Get data from Linear
const { items } = await actions.linear.issueList({
first: 10,
filter: { state: { name: { eq: 'Done' } } },
})
// Send to Slack
for (const issue of items) {
await actions.slack.sendMessage({
channel: '#updates',
text: `✅ Completed: ${issue.title}`,
})
}
})
```
### Using in Workflows
```typescript
export const SyncLinearWorkflow = new Workflow({
name: 'syncLinear',
async handler({ step }) {
// Step 1: Fetch from Linear
const issues = await step('fetch-issues', async () => {
const { items } = await actions.linear.issueList({
first: 50,
orderBy: { updatedAt: 'desc' },
})
return items
})
// Step 2: Process and update
await step('update-database', async () => {
for (const issue of issues) {
await updateDatabase(issue)
}
})
},
})
```
### Using in Tools
```typescript
import { Autonomous, z } from '@botpress/runtime'
export const searchWebTool = new Autonomous.Tool({
name: 'searchWeb',
description: 'Search the web for information',
input: z.object({
query: z.string().describe('Search query'),
}),
output: z.string(),
handler: async ({ query }) => {
const results = await actions.browser.webSearch({
query,
maxResults: 5,
})
return results.map((r, i) => `${i + 1}. ${r.title}\n${r.snippet}`).join('\n\n')
},
})
```
## Type Inference
TypeScript automatically infers types from integration actions:
```typescript
// Infer return type
type SearchResult = Awaited<ReturnType<typeof actions.browser.webSearch>>
// SearchResult is { title: string; url: string; snippet: string }[]
// Infer parameter type
type IssueListParams = Parameters<typeof actions.linear.issueList>[0]
// IssueListParams is { first?: number; filter?: {...}; ... }
// Use in function signatures
async function processIssues(params: Parameters<typeof actions.linear.issueList>[0]) {
const { items } = await actions.linear.issueList(params)
// ...
}
```
## Bot Actions vs Integration Actions
The `actions` proxy handles both:
**Bot Actions** (from `src/actions/*.ts`):
```typescript
// Direct access to your bot's actions
await actions.myCustomAction({ param: 'value' })
```
**Integration Actions** (from installed integrations):
```typescript
// Nested under integration name
await actions.slack.sendMessage({ channel: '#general', text: 'Hi' })
```
The actions proxy automatically determines whether you're calling a bot action (top-level) or an integration action (nested under integration name).
## Troubleshooting
### Integration Not Found
```typescript
// Error: Could not find integration "slack" and action "sendMessage"
```
**Solutions:**
1. Add integration: `adk integrations add slack@2.5.5`
2. Enable: `adk integrations enable slack`
3. Configure credentials in Dev Console (localhost:3001) or via `adk integrations configure`
4. Restart dev server: `adk dev`
### Action Not Found
```typescript
// Error: Action "nonexistentAction" not found in integration "slack"
```
**Solutions:**
1. Check integration documentation for available actions
2. Verify integration version supports the action
3. Check for typos in action name
### Type Errors
```typescript
// Error: Property 'wrongParam' does not exist on type ...
```
**Solutions:**
1. Check IntelliSense for correct parameter names
2. Verify integration version matches your usage
3. Run `adk dev` to regenerate types
### Authentication Errors
```typescript
// Error: Integration not authenticated
```
**Solutions:**
1. Configure credentials in Dev Console (localhost:3001 during dev)
2. Set credentials via `adk integrations configure <alias> --set key=value`
3. Check environment variables are set
4. Verify API keys/tokens are valid
## Best Practices
### 1. Handle Errors Gracefully
```typescript
try {
await actions.slack.sendMessage({ channel: '#general', text: 'Hi' })
} catch (error) {
console.error('Failed to send Slack message:', error)
// Fallback or retry logic
}
```
### 2. Use Type Inference
```typescript
// ✅ Good - Let TypeScript infer types
const issue = await actions.linear.getIssue({ id: "ISS-123" });
// ❌ Bad - Manual typing (can get out of sync)
const issue: { id: string; title: string } = await actions.linear.getIssue(...);
```
### 3. Check Integration Availability
```typescript
// For optional integrations
const slack = adk.project.integrations.get("slack");
if (slack) {
await actions.slack.sendMessage(...);
} else {
console.log("Slack not configured, skipping notification");
}
```
### 4. Batch When Possible
```typescript
// ✅ Good - Parallel requests
await Promise.all([
actions.slack.sendMessage({ channel: '#team1', text: 'Update' }),
actions.slack.sendMessage({ channel: '#team2', text: 'Update' }),
])
// ❌ Bad - Sequential (slower)
await actions.slack.sendMessage({ channel: '#team1', text: 'Update' })
await actions.slack.sendMessage({ channel: '#team2', text: 'Update' })
```
### 5. Use .asTool() for AI
```typescript
// Make integration actions available to AI
await execute({
instructions: 'Help the user',
tools: [actions.linear.issueCreate.asTool(), actions.slack.sendMessage.asTool()],
})
```
## See Also
- **[Agent Configuration](./agent-config.md)** - Installing and configuring integrations
- **[Actions](./actions.md)** - Creating custom actions
- **[Tools](./tools.md)** - Creating AI-callable tools
- **[Workflows](./workflows.md)** - Using actions in workflows
- **[Context API](./context-api.md)** - Accessing integrations via context