@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

EventACP EndpointDescription
searchPOST /acp/searchSemantic product discovery
quotePOST /acp/quotePrice quote and negotiation
checkoutPOST /acp/checkout/initInitialize checkout session
finalizePOST /acp/checkout/finalizeProcess 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}`,
  };
}

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
}