@botpress/adk-cli
Version:
Command-line interface for the Botpress Agent Development Kit (ADK)
468 lines (329 loc) • 11.5 kB
Markdown
# Context API Reference
Access runtime services like the Botpress client and citations manager within your ADK handlers.
## Quick Start
```typescript
import { context } from '@botpress/runtime'
// Get Botpress client
const client = context.get('client')
// Get citations manager
const citations = context.get('citations')
// Use services
const { passages } = await client.searchFiles({
query: 'search term',
withContext: true,
})
```
## Overview
The `context` API provides access to runtime services within your ADK handlers (actions, tools, workflows, conversations, triggers). The ADK automatically sets up context for you - you just need to call `context.get()` to access services.
**Available in:**
- Action handlers
- Tool handlers
- Workflow handlers
- Conversation handlers
- Trigger handlers
## Primary Context Keys
These are the context keys you'll commonly use as an ADK developer:
### client
The Botpress API client for your bot. Use this to interact with Botpress services like files, tables, messages, etc.
```typescript
const client = context.get('client')
// Search files/knowledge base
const { passages } = await client.searchFiles({
query: 'search query',
tags: { type: 'knowledge' },
withContext: true,
})
// Create message
await client.createMessage({
conversationId,
type: 'text',
payload: { text: 'Hello!' },
})
// Query tables (low-level client API)
const { rows } = await client.tables.findMany({
table: 'users',
filter: { email: { eq: 'user@example.com' } },
})
```
> **Note:** For table operations, prefer using the direct Table class methods (e.g., `UsersTable.findRows()`) over `client.tables.*`. The Table class provides better type safety and a cleaner API. See [Tables documentation](./tables.md) for details.
**Type:** `BotSpecificClient<TBot>`
### citations
Citation manager for tracking sources in AI responses. Essential for RAG implementations.
```typescript
const citations = context.get('citations')
// Register a source
const { tag } = citations.registerSource({
url: 'https://docs.example.com/page',
title: 'Documentation Page',
favicon: 'https://docs.example.com/favicon.ico',
})
// Use in response
const response = `According to the documentation${tag}, the answer is...`
```
**Type:** `CitationsManager`
See **[Knowledge Bases](./knowledge-bases.md)** for complete citation examples with RAG.
## Optional Context Keys
These keys may or may not be available depending on the handler context. Always use `{ optional: true }` when accessing them.
### user
Current user object (available in conversation handlers).
```typescript
const user = context.get('user', { optional: true })
if (user) {
console.log(user.id)
console.log(user.name)
console.log(user.tags)
}
```
**Type:** `User | null`
### conversation
Current conversation object. Available optionally in Actions, Tools, and Triggers when they are invoked within a conversation context.
> **Note:** In `Conversation` handlers, the `conversation` object is already provided as a handler parameter — use that directly instead of `context.get("conversation")`.
```typescript
// In Actions, Tools, or Triggers — use context.get() with { optional: true }
const conversation = context.get('conversation', { optional: true })
if (conversation) {
console.log(conversation.id)
console.log(conversation.tags)
}
// In Conversation handlers — use the handler parameter directly
// async handler({ conversation }) { ... }
```
**Type:** `Conversation | null`
> Tools and Actions **cannot send messages** — the `conversation` here is read-only (no `.send()`). Return data from the Tool/Action and let the `Conversation` handler send it.
### message
Incoming message (when triggered by a message event).
```typescript
const message = context.get('message', { optional: true })
if (message?.type === 'text') {
console.log(message.payload.text)
}
```
**Type:** `AnyIncomingMessage<TBot> | null`
### event
Incoming event (available in trigger handlers).
```typescript
const event = context.get('event', { optional: true })
if (event) {
console.log(`Event type: ${event.type}`)
console.log(event.payload)
}
```
**Type:** `AnyIncomingEvent<TBot> | null`
See **[Triggers](./triggers.md)** for event handling examples.
### workflow
Current workflow instance (available in workflow handlers).
```typescript
const workflow = context.get('workflow', { optional: true })
if (workflow) {
console.log(`Workflow ID: ${workflow.id}`)
console.log(`Conversation ID: ${workflow.conversationId}`)
}
```
**Type:** `Workflow | null`
See **[Workflows](./workflows.md)** for workflow context examples.
### workflowControlContext
Workflow control operations (available in workflow handlers).
```typescript
const control = context.get('workflowControlContext', { optional: true })
if (control) {
// Access workflow controls
await control.ack()
control.complete({ result: 'success' })
}
```
**Type:** `WorkflowControlContext | null`
### chat
Chat instance for accessing conversation transcripts (available in conversation handlers).
```typescript
const chat = context.get('chat', { optional: true })
if (chat) {
// Fetch conversation transcript
const transcript = await chat.fetchTranscript()
console.log(`Messages: ${transcript.length}`)
}
```
**Type:** `BotpressChat | null`
See **[Conversations](./conversations.md)** for chat context examples.
## Less Common Keys
These are available but less frequently needed:
### cognitive
AI model client for direct LLM operations. Most of the time you'll use `execute()` instead.
```typescript
const cognitive = context.get('cognitive')
const response = await cognitive.generateText({
model: 'openai:gpt-4o',
messages: [{ role: 'user', content: 'Hello' }],
})
```
**Type:** `Cognitive`
**Note:** Usually you'll use `execute()` for AI interactions rather than calling cognitive directly.
### logger
Structured logger for the current bot.
```typescript
const logger = context.get('logger')
logger.info('Processing request')
logger.error('Failed to process', { error })
logger.debug('Debug info', { data })
```
**Type:** `BotLogger`
### botId
Current bot identifier.
```typescript
const botId = context.get('botId')
console.log(`Running in bot: ${botId}`)
```
**Type:** `string`
## Using context.get()
### Basic Syntax
```typescript
context.get<T extends keyof BotContext>(
key: T,
opts?: { optional?: boolean }
): Required<BotContext>[T]
```
### Required Keys
```typescript
// Will throw if not available (use for keys that should always exist)
const client = context.get('client')
const citations = context.get('citations')
```
### Optional Keys
```typescript
// Returns null if not available (use for conditional keys)
const user = context.get('user', { optional: true })
const conversation = context.get('conversation', { optional: true })
if (user && conversation) {
// Both available
} else {
// Handle missing context
}
```
## Common Patterns
### Conditional User Context
```typescript
export const myAction = new Action({
async handler({ input }) {
const client = context.get('client')
const user = context.get('user', { optional: true })
if (user) {
// Personalized response
console.log(`Processing for user ${user.id}`)
} else {
// Generic response
console.log('Processing without user context')
}
return await client.doSomething()
},
})
```
### Using Context Across Handler Types
The `context.get()` syntax is identical across all handler types (Actions, Tools, Workflows, Conversations, Triggers):
```typescript
// Same pattern everywhere
const client = context.get('client')
const citations = context.get('citations')
const user = context.get('user', { optional: true })
```
See individual documentation for handler-specific examples: **[Actions](./actions.md)**, **[Tools](./tools.md)**, **[Workflows](./workflows.md)**, **[Conversations](./conversations.md)**.
## Error Handling
### Required Keys
```typescript
// ❌ WRONG - Will throw if key doesn't exist
try {
const user = context.get('user')
} catch (error) {
// Hard to handle gracefully
}
// ✅ CORRECT - Use optional for conditional keys
const user = context.get('user', { optional: true })
if (!user) {
console.log('No user context available')
return defaultResponse
}
```
### Always-Available Keys
Keys like `client`, `citations` should always be available. If they're not, it's a framework error:
```typescript
// These should never throw in normal operation
const client = context.get('client')
const citations = context.get('citations')
```
## Best Practices
### 1. Know Which Keys Are Always Available
**Always available:**
- `client` - Botpress API client
- `citations` - Citation manager
- `cognitive` - AI model client
- `logger` - Structured logger
- `botId` - Current bot ID
**Conditionally available (use `{ optional: true }`):**
- `user` - Only in conversation context
- `conversation` - Only in conversation context
- `message` - Only when triggered by message
- `event` - Only in trigger handlers
- `workflow` - Only in workflow handlers
- `workflowControlContext` - Only in workflow handlers
- `chat` - Only in conversation handlers
### 2. Access Context at Call Time
```typescript
// ✅ GOOD - Get context when needed
export const myAction = new Action({
async handler({ input }) {
const client = context.get('client') // Get when you need it
const result = await client.searchFiles({ query: input.query })
return result
},
})
// ❌ BAD - Don't store context globally
let globalClient // Don't do this!
export const badAction = new Action({
async handler({ input }) {
globalClient = context.get('client') // Context might change
},
})
```
### 3. Use TypeScript's Type Safety
```typescript
// TypeScript knows the types
const client = context.get('client')
// client is typed as BotSpecificClient<TBot>
const user = context.get('user', { optional: true })
// user is typed as User | null
```
## Direct Imports vs Context API
For `bot`, `user`, and `adk`, you can use direct imports instead of `context.get()`:
```typescript
import { bot, user, adk } from '@botpress/runtime'
bot.state.version // Instead of context.get("bot").state
user.state.preferredLanguage // Instead of context.get("user").state
```
See **[Actions](./actions.md)** for comparison of both approaches.
## Troubleshooting
### "Context key not found" Error
```typescript
// Error: Context key "user" not found
const user = context.get('user')
```
**Solution:** Use `{ optional: true }` for conditional keys:
```typescript
const user = context.get('user', { optional: true })
```
### "Cannot read property of undefined"
```typescript
// Error: Cannot read property 'id' of undefined
const userId = context.get('user', { optional: true }).id
```
**Solution:** Check if key exists before accessing properties:
```typescript
const user = context.get('user', { optional: true })
if (user) {
const userId = user.id
}
```
## See Also
- **[Actions](./actions.md)** - Using context in actions
- **[Tools](./tools.md)** - Using context in tools
- **[Integration Actions](./integration-actions.md)** - Using the client for integration actions
- **[Workflows](./workflows.md)** - Workflow context
- **[Conversations](./conversations.md)** - Conversation context
- **[Knowledge Bases](./knowledge-bases.md)** - Using citations for RAG