@OnACPEvent
Handle Agentic Commerce Protocol events like search, quote, and checkout.
Overview
The @OnACPEvent decorator marks methods that handle incoming ACP protocol requests. Each event type corresponds to a step in the agent commerce flow.
import { HyperfoldAgent, OnACPEvent } from '@hyperfold/actions-sdk';
@HyperfoldAgent({ name: 'sales-bot', type: 'negotiator' })
export class SalesBot {
@OnACPEvent('search')
async handleSearch(query: string, filters: SearchFilters): Promise<SearchResponse> { }
@OnACPEvent('quote')
async handleQuote(productId: string, offer: number, context: BuyerContext): Promise<QuoteResponse> { }
@OnACPEvent('checkout')
async handleCheckout(sessionId: string, items: CartItem[]): Promise<CheckoutResponse> { }
@OnACPEvent('finalize')
async handleFinalize(checkoutId: string, paymentToken: string): Promise<FinalizeResponse> { }
}
Available Events
| Event | ACP Endpoint | Description |
|---|---|---|
search | POST /acp/search | Semantic product discovery |
quote | POST /acp/quote | Price quote and negotiation |
checkout | POST /acp/checkout/init | Initialize checkout session |
finalize | POST /acp/checkout/finalize | Process payment and create order |
Search Event
Handle semantic product search queries:
@OnACPEvent('search')
async handleSearch(
query: string,
filters: SearchFilters,
context: RequestContext
): Promise<SearchResponse> {
const products = await this.catalog.semanticSearch(query, {
limit: filters.limit || 10,
minConfidence: 0.7,
priceMax: filters.price_max,
category: filters.category,
});
const boosted = products.map(p => ({
...p,
confidence: p.in_stock ? p.confidence * 1.1 : p.confidence,
}));
return {
results: boosted.slice(0, filters.limit || 10),
total_count: products.length,
semantic_confidence: boosted[0]?.confidence || 0,
facets: this.generateFacets(products),
};
}
Quote Event
Handle price quotes and negotiation:
@OnACPEvent('quote')
async handleQuote(
productId: string,
offer: number | null,
context: BuyerContext
): Promise<QuoteResponse> {
const product = await getProduct(productId);
const inventory = await checkInventory(productId);
const pricing = await calculateDynamicPrice(product, {
buyerTier: context.loyalty_tier,
cartValue: context.cart_value,
inventoryLevel: inventory.status,
competitorPrice: await getCompetitorPrice(productId),
});
if (offer === null) {
return {
status: 'quote',
list_price: product.list_price,
offered_price: pricing.suggested,
valid_until: new Date(Date.now() + 3600000).toISOString(),
};
}
if (offer >= pricing.target) {
return { status: 'accept', price: offer, message: "Great choice! I'll process your order." };
}
if (offer >= pricing.floor) {
return {
status: 'counter_offer',
original_price: product.list_price,
counter_price: pricing.suggested,
reasoning: pricing.explanation,
valid_until: new Date(Date.now() + 3600000).toISOString(),
bundle_suggestion: await this.suggestBundle(productId),
};
}
return {
status: 'reject',
reason: 'Price is below our minimum',
floor_hint: `Our best price is around $${Math.ceil(pricing.floor / 5) * 5}`,
};
}
The quote handler is where most negotiation logic lives. Use the pricing tools to calculate dynamic prices based on context.
Checkout Events
Handle checkout initialization and payment finalization. See the full checkout and finalize handler patterns in the SDK—validate inventory, create checkout session, process payment via SPT, create order, and publish order.completed event.
Error Handling
Return structured errors that buyer agents can handle:
@OnACPEvent('quote')
async handleQuote(productId: string, offer: number, context: BuyerContext) {
try {
const product = await getProduct(productId);
if (!product) {
return {
status: 'error',
error: 'product_not_found',
message: `Product ${productId} not found`,
};
}
// ... rest of quote logic
} catch (error) {
console.error('Quote error:', error);
return {
status: 'error',
error: 'internal_error',
message: 'Unable to process quote at this time',
retry_after: 60,
};
}
}
// With ValidateInput decorator
import { ValidateInput } from '@hyperfold/actions-sdk';
@OnACPEvent('quote')
@ValidateInput({
productId: { type: 'string', required: true },
offer: { type: 'number', min: 0 },
context: { type: 'object' },
})
async handleQuote(productId: string, offer: number, context: BuyerContext) {
// Input is guaranteed to be valid
}
Schedule recurring tasks with @OnSchedule.