Open a turn for the message
A message whoseadvances is true ends a step the shopper took outside the chat. NEXT_STEP_KINDS lists the kinds that advance: card_added, store_connected, and approval_declined. Call buy.nextStep(message) in your onMessage. It returns a turn to run your model on, or null when there is no step to take.
server.ts
anthropic, histories, postToChat, and textOf stand for your own code. The turn is a buy.turn in every way but one: no shopper wrote anything, so turn.messages(history) ends with the event, marked as written by the system, and the instruction to take the next step now in one short message. The model never asks “you’re all set, what’s next?”.
Post the model’s answer in place of message.text. The shopper never reads message.text, and the client never hands that text to a later turn as words the shopper read.
Wait for each post. A post of the model’s answer that fails sends the turn to the fallback, and a fallback post that fails records nothing as delivered, so Agentcard’s redelivery of the event opens the turn again.
Know what the shopper reads next
Keep the shopper’s decision theirs
The turn moves the purchase only up to the shopper’s own decision, and the tool holds it there whatever the model calls.- No call of the turn sends an ask to Agentcard, because Agentcard could read the model’s words as the shopper’s go-ahead. Each call reads where the purchase stands and answers with the next step: the card link the sign-in held back, or the cart and the ask for the shopper’s yes.
- A confirm goes in only for the yes the shopper already gave in the chat, such as “lets place it”, when that confirm stopped for the card or the sign-in they finished. It goes in once, for that cart’s hash, so Agentcard refuses it if the cart changed. A shopper message after the yes that is not a plain go-ahead, such as “wait, make it two”, takes the yes back.
- Any other confirm the model sends is refused by the tool, and nothing reaches Agentcard.
- The approval in the Vault is still the shopper’s. The confirm sends the approval link, and Agentcard places the order only once the shopper approves it.
- A shopper who was not connected yet when they asked gets their own first ask run, in their words, once their card is saved.
Handle the cases with no step
buy.nextStep returns null in four cases, and your code posts message.text then:
- The message’s
advancesis false, such asorder_placed, whosetextis already the whole message. - The message is for another shopper, or its link closed.
- The tool already took a turn for the message.
- The message arrives while a call for the shopper’s own message is still running. That call’s answer tells the model where the purchase stands.
step.release() before you post message.text. The text then reaches later turns once agentcard.delivered records it, and a redelivery of the event opens a turn again.
Run the listener and the model in one process
The client keeps each message in memory, at most 20 for each shopper, solisten or the webhook handler and the turn run in one process on one client. When your model runs elsewhere, such as a serverless chat route, keep each message where your chat history lives, as an assistant message, and the model reads it from the history. The Quickstart covers the webhook handler for serverless hosts.
Check it in the sandbox
- Send “two packs of AA batteries from Amazon” as a test shopper nobody connected, and open the Vault link.
- Save a card with the code
111111. Do not type anything in the chat. - Check that the chat posts one message that does the next thing, such as the ask for your yes or the approval link, with nothing typed after the card saved.
- Check that the agent ran your first ask, “two packs of AA batteries from Amazon”, in your words once your card was saved, without you asking again.
- Make your model throw during
runModel. Check that the shopper still readsmessage.text.