Buyer clients

Vercel AI SDK

Use @devlab.group/agent-commerce-ai-sdk to create gateway tools and pause paid calls for approval.

@devlab.group/agent-commerce-ai-sdk lets Vercel AI SDK agents use resources behind a gateway. agentCommerceTools() discovers eligible resources and creates a tool for each. Free tools run directly; paid tools use x402 within spending limits the model cannot change. The buyer core handles discovery, payments and limits.

Install

bash
npm install @devlab.group/agent-commerce-ai-sdk ai viem

Requires Node.js 22 or later and AI SDK 7 (ai is a peer dependency). viem provides the wallet account.

Example

typescript
import { generateText, isStepCount } from 'ai';
import { privateKeyToAccount } from 'viem/accounts';
import { agentCommerceToolApproval, agentCommerceTools } from '@devlab.group/agent-commerce-ai-sdk';

const tools = await agentCommerceTools({
  gatewayUrl: 'https://merchant.example.com',
  wallet: privateKeyToAccount(process.env.BUYER_PRIVATE_KEY as `0x${string}`),
  networks: ['eip155:84532'],
  limits: { maxPerCall: '0.05', maxPerDay: '1.00' },
});

const { text, content } = await generateText({
  model: 'openai/gpt-5-mini',
  prompt: 'Get me the market report on solar panels.',
  tools,
  // Paid tools stop for human approval; free tools run directly
  toolApproval: agentCommerceToolApproval(tools),
  stopWhen: isStepCount(5),
});

for (const part of content) {
  if (part.type === 'tool-approval-request') console.log('Needs approval:', part.toolCall.toolName, part.toolCall.input);
}
console.log(text);

A paid market report pauses generation with an approval request, which the loop prints. See Approval to respond and continue.

Use a dedicated wallet funded to just above the daily limit on a network listed in networks. Payments use that network's USDC, except on local Anvil, which allows any asset. Testnets such as Base Sepolia (eip155:84532) need no extra opt-in; mainnet networks require allowMainnet: true. The supported networks are listed under buyer core configuration.

OptionMeaning
gatewayUrlGateway base URL
walletviem account; raw private keys are refused
networksAllowed CAIP-2 EVM networks
limits.maxPerCall, limits.maxPerDayUSDC limits per call and per UTC day, as decimal strings
resourcesOptional allowlist of resource ids
approvalOptional: paid (default), always or never
allowMainnet, spendStore, fetchOptional, see the buyer core

Approval

In AI SDK 7, generateText, streamText and ToolLoopAgent take their approval rules from the toolApproval option. agentCommerceToolApproval(tools) returns 'user-approval' for each tool whose needsApproval flag is true. The default approval: 'paid' requires approval for paid tools and lets free tools run directly; 'always' requires it for every tool, and 'never' for none. A tool that needs approval pauses generation with a tool-approval-request. Your application answers with a tool-approval-response to continue:

typescript
import { createInterface } from 'node:readline/promises';
import { generateText, isStepCount } from 'ai';
import type { ModelMessage } from 'ai';
import { privateKeyToAccount } from 'viem/accounts';
import { agentCommerceToolApproval, agentCommerceTools } from '@devlab.group/agent-commerce-ai-sdk';

const tools = await agentCommerceTools({
  gatewayUrl: 'https://merchant.example.com',
  wallet: privateKeyToAccount(process.env.BUYER_PRIVATE_KEY as `0x${string}`),
  networks: ['eip155:84532'],
  limits: { maxPerCall: '0.05', maxPerDay: '1.00' },
  approval: 'paid', // the default: paid resources wait for a human, free ones run directly
});

async function confirm(question: string) {
  const terminal = createInterface({ input: process.stdin, output: process.stdout });
  const answer = await terminal.question(question);
  terminal.close();
  return answer.trim().toLowerCase() === 'y';
}

const messages: ModelMessage[] = [{ role: 'user', content: 'Get me the market report on solar panels.' }];
const options = {
  model: 'openai/gpt-5-mini',
  tools,
  toolApproval: agentCommerceToolApproval(tools),
  stopWhen: isStepCount(5),
} as const;

let result = await generateText({ ...options, messages });
messages.push(...result.response.messages);

// A tool that needs approval stops the loop with a request instead of running
const requests = result.content.filter((part) => part.type === 'tool-approval-request');
if (requests.length > 0) {
  const responses = [];
  for (const request of requests) {
    const approved = await confirm(`Run ${request.toolCall.toolName} ${JSON.stringify(request.toolCall.input)}? [y/N] `);
    responses.push({ type: 'tool-approval-response' as const, approvalId: request.approvalId, approved });
  }
  messages.push({ role: 'tool', content: responses });
  result = await generateText({ ...options, messages });
}
console.log(result.text);

Entries in toolApproval override a tool's needsApproval flag, which applies when toolApproval has no entry for it. To change one tool, spread the helper's result first: { ...agentCommerceToolApproval(tools), report: 'approved' }. Passing a toolApproval function replaces the per-tool flags.

Errors

A limit or payment failure throws an AgentCommerceError; the AI SDK passes its text to the model as a tool error and generation continues. The codes are LIMIT_EXCEEDED, NETWORK_NOT_ALLOWED, PAYMENT_FAILED, BACKEND_ERROR and GATEWAY_UNREACHABLE; see buyer core errors.

End-to-end evidence

These runs called the tool's execute function through typescript/agent-commerce-ai-sdk/test/e2e.test.ts, without a language model.

Local demo stack (gateway http://127.0.0.1:8080, Anvil eip155:31337, MockUSDC, market_report at 0.01 USDC). These transactions exist only on the local chain.

CaseResult
Price within the limit (maxPerCall: '0.05')Status 200, PAYMENT-RESPONSE present, tx 0xda69fd586106f1666259616abf664d85450ed96f1ea0b3b89615d06e94508478
Same, second runStatus 200, tx 0x202045c392da03d445568cc0e432a072270635ab4a4a71be0018b5cbf6ceae96; buyer balance 99960000 to 99950000 base units (10000 = 0.01 USDC); a gateway receipt carries the tx hash
Same, third run (after the USDC and transfer-method offer checks)Status 200, tx 0x0109c19bbd56493b7f66ec9f7ab17144d7f9bb3901d4cbc3ad826428f26b7878; buyer balance 99870000 to 99860000 base units
Price above the limit (maxPerCall: '0.005')LIMIT_EXCEEDED before signing; buyer balance unchanged (99860000 in the latest run); gateway receipt list unchanged (17 before, 17 after in the latest run)

Base Sepolia (eip155:84532), 2026-11-13: the testnet demo merchant run locally (gateway 2.7.1 in a locally built image, before the demo moved to the published 2.7.2 image; public facilitator https://x402.org/facilitator, Circle USDC, report at 0.01 USDC, a temporary merchant wallet as payTo).

CaseResult
Price within the limit (maxPerCall: '0.05')Status 200, PAYMENT-RESPONSE present, tx 0x9af6317a2b4c4805d7f9d1083191c3373164effd897f9577b5871fd8ac16e546; buyer balance 19870000 to 19860000 base units
Price above the limit (maxPerCall: '0.005')LIMIT_EXCEEDED before signing; buyer balance unchanged; gateway receipt list unchanged

The first Base Sepolia attempt failed at the public facilitator (invalid_exact_evm_transaction_failed) without a transaction or charge. The gateway returned 502 PAYMENT_SETTLEMENT_FAILED; the rerun recorded above passed.

Package source and README: typescript/agent-commerce-ai-sdk.

Based on: docs/ai-sdk.md