Advanced Features
FlowKit includes powerful features for building sophisticated conversational flows.
Inline Actions
Execute code directly within a step without needing to register a tool.
typescript
flow("order-flow", agent)
.ask("get_email", "What's your email?", email(), "user_email")
.do(async (ctx) => {
// Inline action - runs after extraction
console.log(`New lead: ${ctx.slots.user_email}`);
await analytics.track("email_captured", { email: ctx.slots.user_email });
})
.then("next_step")Use Cases
- Quick logging/analytics
- Simple data validation
- Triggering side effects
- Modifying slots programmatically
typescript
.do(async (ctx) => {
// Modify slots
ctx.slots.normalized_name = ctx.slots.name.trim().toLowerCase();
// Fetch external data
const user = await db.findByEmail(ctx.slots.email);
if (user) {
ctx.slots.existing_customer = true;
ctx.slots.customer_tier = user.tier;
}
})Conditional Steps
Skip or include steps based on conditions.
.skipIf() - Skip a step when condition is true
typescript
flow("checkout", agent)
.ask("get_email", "What's your email?", email(), "email")
.then("ask_address")
// Skip if we already have the address from a previous interaction
.ask("ask_address", "What's your shipping address?", text(), "address")
.skipIf((ctx) => !!ctx.slots.saved_address)
.then("confirm_order").onlyIf() - Include step only when condition is true
typescript
flow("survey", agent)
.ask("age", "How old are you?", number(), "age")
.then("ask_job")
// Only ask about job if user is 18+
.ask("ask_job", "What's your occupation?", text(), "job")
.onlyIf((ctx) => ctx.slots.age >= 18)
.then("thanks")Complex Conditions
typescript
// Multiple conditions
.skipIf((ctx) =>
ctx.slots.is_vip ||
ctx.slots.skip_verification === true
)
// Based on previous answers
.onlyIf((ctx) =>
ctx.slots.issue_type === "technical" &&
ctx.slots.severity === "high"
)Slot Transformations
Transform extracted values before storing them.
Built-in Transforms
typescript
flow("registration", agent)
.ask("get_name", "What's your name?", name(), "user_name")
.transform("capitalize") // "john doe" -> "John Doe"
.then("get_email")
.ask("get_email", "What's your email?", email(), "user_email")
.transform("lowercase") // "John@Email.COM" -> "john@email.com"
.then("get_code")
.ask("get_code", "Enter your code:", text(), "code")
.transform("uppercase") // "abc123" -> "ABC123"
.then("get_phone")
.ask("get_phone", "Phone number?", text(), "phone")
.transform("digits") // "(123) 456-7890" -> "1234567890"
.then("done")Available Transforms
| Transform | Description | Example |
|---|---|---|
lowercase | Convert to lowercase | "ABC" → "abc" |
uppercase | Convert to uppercase | "abc" → "ABC" |
capitalize | Capitalize words | "john doe" → "John Doe" |
trim | Remove whitespace | " hello " → "hello" |
digits | Extract only digits | "(123) 456" → "123456" |
Custom Transforms
typescript
.ask("get_price", "What's your budget?", text(), "budget")
.transform((value) => {
// Parse price and normalize
const num = parseFloat(value.replace(/[^0-9.]/g, ""));
return Math.round(num * 100) / 100; // Round to 2 decimals
})
.then("next")
// Async transforms
.ask("get_sku", "Enter product SKU:", text(), "sku")
.transform(async (value, ctx) => {
const product = await db.findBySku(value);
ctx.slots.product_name = product?.name;
return value.toUpperCase();
})Yes/No Shortcuts
Simplify branching for yes/no questions.
Basic Usage
typescript
flow("confirmation", agent)
.ask("confirm", "Do you want to proceed?", yesNo(), "confirmed")
.ifYes("process_order")
.ifNo("cancel_order")
.say("process_order", "Processing your order...")
.done()
.say("cancel_order", "Order cancelled.")
.done()Equivalent to
typescript
.ask("confirm", "Do you want to proceed?", yesNo(), "confirmed")
.when({ yes: "process_order", no: "cancel_order" })Retry Messages
Customize the retry message when extraction fails.
typescript
flow("email-collector", agent)
.ask("get_email", "Please enter your email address:", email(), "email")
.retry("Hmm, that doesn't look like a valid email. Please try again:")
.then("done")Default Behavior
Without .retry(), the engine uses a generic retry message based on the extract type.
New Extractors
url() - Extract URLs
typescript
import { url } from "@andresaya/flowkit";
flow("link-collector", agent)
.ask("get_website", "What's your website?", url(), "website")
.then("done")Extracts valid URLs from user messages:
https://example.comhttp://localhost:3000www.example.com(adds https://)
time() - Extract Time Values
typescript
import { time } from "@andresaya/flowkit";
flow("scheduler", agent)
.ask("get_time", "What time works for you?", time(), "appointment_time")
.then("confirm")Extracts time in various formats:
- "3pm", "3:30pm", "15:30"
- "morning", "afternoon", "evening"
- "noon", "midnight"
multiSelect() - Multiple Choice Selection
typescript
import { multiSelect } from "@andresaya/flowkit";
flow("preferences", agent)
.ask(
"select_toppings",
"What toppings would you like? (choose multiple)",
multiSelect(["cheese", "pepperoni", "mushrooms", "olives", "peppers"]),
"toppings"
)
.then("confirm")Returns an array of selected options:
- User: "I want cheese and pepperoni"
- Slot:
["cheese", "pepperoni"]
Confirm Steps
Add confirmation before proceeding (skeleton - full implementation coming).
typescript
flow("order", agent)
.ask("get_email", "Email?", email(), "email")
.then("get_address")
.ask("get_address", "Address?", text(), "address")
.confirm("Is this correct? Email: {{email}}, Address: {{address}}")
.then("process")Combining Features
All these features can be combined:
typescript
flow("smart-order", agent)
.ask("get_email", "What's your email?", email(), "email")
.transform("lowercase")
.do(async (ctx) => {
const user = await db.findByEmail(ctx.slots.email);
if (user) {
ctx.slots.has_account = true;
ctx.slots.saved_address = user.address;
}
})
.then("ask_address")
.ask("ask_address", "Shipping address?", text(), "address")
.skipIf((ctx) => ctx.slots.saved_address)
.transform("capitalize")
.then("ask_express")
.ask("ask_express", "Express shipping (+$10)?", yesNo(), "express")
.onlyIf((ctx) => ctx.slots.has_account) // Only VIP customers
.ifYes("express_checkout")
.ifNo("standard_checkout")