# aixyz/accepts Source: https://aixyz.sh/api-reference/accepts Payment configuration types and facilitator client for x402 gating Types and utilities for configuring x402 payment gating on agent and tool endpoints. ```typescript theme={null} import type { Accepts, AcceptsX402, AcceptsX402Entry, AcceptsX402Multi, AcceptsFree } from "aixyz/accepts"; import { HTTPFacilitatorClient, facilitator, normalizeAcceptsX402, isAcceptsPaid } from "aixyz/accepts"; ``` ## Types ### `Accepts` Union type for payment configuration. Supports a single payment option, free access, or an array of payment options for multi-network support: ```typescript theme={null} type Accepts = AcceptsX402 | AcceptsFree | AcceptsX402Multi; ``` ### `AcceptsX402` Requires x402 exact payment: ```typescript theme={null} type AcceptsX402 = { scheme: "exact"; price: string; // USD string, e.g. "$0.005" network?: string; // CAIP-2 chain ID, overrides config payTo?: string; // EVM address, overrides config }; ``` | Field | Type | Required | Description | | --------- | -------- | -------- | ------------------------------------------------------------- | | `scheme` | `string` | Yes | Must be `"exact"` | | `price` | `string` | Yes | USD price string (e.g. `"$0.005"`) | | `network` | `string` | No | CAIP-2 chain ID, overrides `x402.network` from config | | `payTo` | `string` | No | EVM address to receive payment, overrides `x402.payTo` config | ### `AcceptsX402Entry` A single payment option within a multi-accepts array. Same as `AcceptsX402` but `network` is **required** — the server needs an explicit network to register each payment scheme: ```typescript theme={null} type AcceptsX402Entry = { scheme: "exact"; price: string; network: string; // required payTo?: string; }; ``` | Field | Type | Required | Description | | --------- | -------- | -------- | ------------------------------------------------------------- | | `scheme` | `string` | Yes | Must be `"exact"` | | `price` | `string` | Yes | USD price string (e.g. `"$0.005"`) | | `network` | `string` | Yes | CAIP-2 chain ID — required for multi-accepts | | `payTo` | `string` | No | EVM address to receive payment, overrides `x402.payTo` config | ### `AcceptsX402Multi` An array of payment entries, enabling multi-network support. Must contain at least one entry: ```typescript theme={null} type AcceptsX402Multi = AcceptsX402Entry[]; ``` ### `AcceptsFree` No payment required: ```typescript theme={null} type AcceptsFree = { scheme: "free"; }; ``` ## Exports ### `normalizeAcceptsX402` Converts a single or multi accepts value into a uniform array: ```typescript theme={null} function normalizeAcceptsX402(accepts: AcceptsX402 | AcceptsX402Multi): AcceptsX402[]; ``` ### `isAcceptsPaid` Type guard that returns `true` if the accepts config requires payment (i.e., is not `{ scheme: "free" }`): ```typescript theme={null} function isAcceptsPaid(accepts: Accepts): accepts is AcceptsX402 | AcceptsX402Multi; ``` ### `HTTPFacilitatorClient` Client for communicating with an x402 facilitator service. Re-exported from `@x402/core/server`. ```typescript theme={null} import { HTTPFacilitatorClient } from "aixyz/accepts"; const client = new HTTPFacilitatorClient({ url: "https://x402.use-agently.com/facilitator", }); ``` ### `facilitator` The default facilitator client used by `AixyzApp`. Points to the Agently-hosted x402 facilitator. ```typescript theme={null} import { facilitator } from "aixyz/accepts"; ``` ### `AcceptsScheme` Zod schema for validating `Accepts` objects at runtime. Accepts a single object or an array of payment entries: ```typescript theme={null} import { AcceptsScheme } from "aixyz/accepts"; // Single accepts AcceptsScheme.parse({ scheme: "exact", price: "$0.005" }); // Multiple accepts AcceptsScheme.parse([ { scheme: "exact", price: "$0.005", network: "eip155:8453" }, { scheme: "exact", price: "$0.005", network: "eip155:84532" }, ]); ``` ## Usage Agents and tools declare an `accepts` export to control x402 payment gating. Endpoints without `accepts` are **not registered**. ```typescript title="app/agent.ts" theme={null} import type { Accepts } from "aixyz/accepts"; export const accepts: Accepts = { scheme: "exact", price: "$0.005", }; ``` ```typescript title="app/tools/lookup.ts" theme={null} import type { Accepts } from "aixyz/accepts"; export const accepts: Accepts = { scheme: "free", }; ``` ### Multiple payment options Accept payment across multiple networks by passing an array. Each entry requires an explicit `network`: ```typescript title="app/agent.ts" theme={null} import type { Accepts } from "aixyz/accepts"; export const accepts: Accepts = [ { scheme: "exact", price: "$0.005", network: "eip155:8453" }, { scheme: "exact", price: "$0.005", network: "eip155:84532" }, ]; ``` All referenced networks are automatically registered with the payment gateway during initialization — no manual setup required. ### Custom facilitator Create `app/accepts.ts` to override the default facilitator: ```typescript title="app/accepts.ts" theme={null} import { HTTPFacilitatorClient } from "aixyz/accepts"; export const facilitator = new HTTPFacilitatorClient({ url: process.env.X402_FACILITATOR_URL ?? "https://x402.use-agently.com/facilitator", }); ``` # agent.ts Source: https://aixyz.sh/api-reference/agent Define your agent with Vercel AI SDK The agent definition file. Must export a default `ToolLoopAgent` and optionally `accepts` for payment gating and `capabilities` for A2A agent card configuration. ```typescript title="app/agent.ts" theme={null} import { openai } from "@ai-sdk/openai"; import { stepCountIs, ToolLoopAgent } from "ai"; import type { Accepts } from "aixyz/accepts"; import type { Capabilities } from "aixyz/app/plugins/a2a"; import weather from "./tools/weather"; export const accepts: Accepts = { scheme: "exact", price: "$0.005", }; export const capabilities: Capabilities = { streaming: false, pushNotifications: false, }; export default new ToolLoopAgent({ model: openai("gpt-4o-mini"), instructions: "You are a helpful weather assistant.", tools: { weather }, stopWhen: stepCountIs(10), }); ``` ## Exports | Export | Type | Required | Description | | -------------- | --------------- | -------- | ------------------------------------------------------------------- | | `default` | `ToolLoopAgent` | Yes | The agent instance | | `accepts` | `Accepts` | No | Payment config — gates the A2A `/agent` route | | `capabilities` | `Capabilities` | No | A2A capabilities — controls streaming and push notification support | When `accepts` is exported, the `/agent` endpoint requires x402 payment. Without it, the agent is not registered on the A2A endpoint. `accepts` can also be an array of payment entries for [multi-network support](/getting-started/payments#multiple-payment-options). ## Capabilities The optional `capabilities` export controls the A2A agent card's `capabilities` field and the executor's behavior: ```typescript theme={null} import type { Capabilities } from "aixyz/app/plugins/a2a"; export const capabilities: Capabilities = { streaming: false, // default: true pushNotifications: false, // default: false stateTransitionHistory: false, // default: undefined }; ``` | Field | Type | Default | Description | | ------------------------ | --------- | ----------- | ---------------------------------------------------- | | `streaming` | `boolean` | `true` | Whether the agent streams responses via `textStream` | | `pushNotifications` | `boolean` | `false` | Whether the agent supports push notifications | | `stateTransitionHistory` | `boolean` | `undefined` | Whether the agent exposes state transition history | When `streaming` is set to `false`, the executor uses `agent.generate()` instead of `agent.stream()`, returning the full response as a single artifact rather than streaming chunks. This is useful for agents backed by models or APIs that don't support streaming. # agents/[name].ts Source: https://aixyz.sh/api-reference/agents Auto-discovered sub-agent definitions for multiple A2A endpoints Each `.ts` file in `app/agents/` is auto-discovered and registered as a sub-agent on its own A2A endpoint. ```typescript title="app/agents/research.ts" theme={null} import { openai } from "@ai-sdk/openai"; import { stepCountIs, ToolLoopAgent } from "ai"; import type { Accepts } from "aixyz/accepts"; export const accepts: Accepts = { scheme: "exact", price: "$0.005", }; export default new ToolLoopAgent({ model: openai("gpt-4o-mini"), instructions: "You are a research assistant specialized in finding and summarizing information.", stopWhen: stepCountIs(10), }); ``` Given the file `app/agents/research.ts`, the sub-agent is exposed at `/research/agent` with its agent card at `/research/.well-known/agent-card.json`. ## Exports | Export | Type | Required | Description | | --------- | --------------- | -------- | ----------------------------------------------------------- | | `default` | `ToolLoopAgent` | Yes | The agent instance | | `accepts` | `Accepts` | No | Payment config — gates the sub-agent A2A endpoint with x402 | When `accepts` is exported, the sub-agent endpoint requires x402 payment. Without it, the sub-agent is not registered. ## Conventions * **Auto-discovered** — All `.ts` files in `app/agents/` are registered automatically * **Ignored files** — Files starting with `_` (e.g., `_helpers.ts`) are skipped * **Routing** — The filename (without `.ts`) becomes the URL prefix (e.g., `research.ts` → `/research/agent`) ## Multiple Sub-Agents You can mix `app/agent.ts` (main agent) with `app/agents/` (sub-agents) in the same project: ``` app/ agent.ts # → /agent agents/ research.ts # → /research/agent implement.ts # → /implement/agent ``` This exposes three independent A2A endpoints from a single deployment. # aixyz/config Source: https://aixyz.sh/api-reference/aixyz-config Agent configuration types and runtime config access Provides the `AixyzConfig` type and functions to access your agent's configuration at runtime. ```typescript theme={null} import type { AixyzConfig, AixyzConfigRuntime } from "aixyz/config"; import { getAixyzConfig, getAixyzConfigRuntime } from "aixyz/config"; ``` ## Types ### `AixyzConfig` The full configuration object parsed from `aixyz.config.ts`: ```typescript theme={null} type AixyzConfig = { name: string; description: string; version: string; url?: string; x402: { payTo: string; network: string; // CAIP-2 chain ID }; build?: { output?: "standalone" | "vercel"; includes?: string | string[]; excludes?: string | string[]; }; skills?: Skill[]; }; ``` ### `AixyzConfigRuntime` The subset of config safe to access at runtime (excludes `x402` and `build`): ```typescript theme={null} type AixyzConfigRuntime = { name: string; description: string; version: string; url: string; skills: Skill[]; }; ``` ## Functions ### `getAixyzConfig()` Returns the full parsed config. Intended for build-time and CLI use. ```typescript theme={null} const config = getAixyzConfig(); console.log(config.x402.payTo); ``` ### `getAixyzConfigRuntime()` Returns the runtime-safe subset of the config. Use this in your server code. ```typescript theme={null} const config = getAixyzConfigRuntime(); console.log(config.name, config.version); ``` ## Under the hood: materialization When you run `aixyz build` or `aixyz dev`, the config is **materialized** into the bundle at build time. This means `getAixyzConfig()` and `getAixyzConfigRuntime()` don't read from the filesystem at runtime — they return a pre-computed JSON object that was inlined during the build. The `AixyzConfigPlugin` in the CLI: 1. Reads and validates your `aixyz.config.ts` at build time 2. Resolves environment variables (e.g. `VERCEL_URL` for the `url` field) 3. Replaces the `aixyz/config` module in the bundle with a static JSON literal: ```typescript theme={null} // What your code imports: import { getAixyzConfig } from "aixyz/config"; // What ends up in the bundle: const config = { name: "my-agent", description: "..." /* ... */ }; export function getAixyzConfig() { return config; } export function getAixyzConfigRuntime() { return config; } ``` This means: * **No filesystem access** at runtime — the config is baked into the bundle * **Environment variables are resolved at build time** — changing `VERCEL_URL` after build has no effect on the config * The bundle is fully self-contained and portable You don't need to call these functions directly in most cases. The auto-generated server, `A2APlugin`, and `MCPPlugin` all read from the materialized config automatically. # aixyz/model/fake Source: https://aixyz.sh/api-reference/aixyz-model-fake Deterministic fake language model for testing and development A drop-in `LanguageModelV3` implementation that maps user messages through a transform function — no API key, no network calls, fully deterministic. ```typescript theme={null} import { fake, type Prompt } from "aixyz/model"; ``` ## `fake(transform)` Creates a fake language model conforming to the Vercel AI SDK `LanguageModelV3` specification. The returned model can be passed directly to `ToolLoopAgent` or any AI SDK function that accepts a `LanguageModel`. ```typescript theme={null} function fake(transform: (lastMessage: string, prompt: Prompt) => string): LanguageModelV3; ``` ### Parameters | Parameter | Type | Description | | ----------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | `transform` | `(lastMessage: string, prompt: Prompt) => string` | Function that receives the last user message text and the full prompt, and returns the model output string | The `transform` function receives two arguments: * **`lastMessage`** — the text content of the most recent user message (empty string if none) * **`prompt`** — the full `LanguageModelV3Prompt` conversation history, useful for tracking turn count or prior context ### Return value A `LanguageModelV3` object with: * `specificationVersion: "v3"` * `provider: "aixyz/fake"` * `modelId: "aixyz/fake"` * `doGenerate()` and `doStream()` that call your transform and report zero token usage ### Examples Simple echo: ```typescript theme={null} import { fake } from "aixyz/model"; const model = fake((input) => `You said: ${input}`); ``` Using full prompt context: ```typescript theme={null} import { fake } from "aixyz/model"; const model = fake((input, prompt) => { const turn = prompt.filter((m) => m.role === "user").length; return `Turn ${turn}: ${input}`; }); ``` Wiring into an agent: ```typescript title="app/agent.ts" theme={null} import { fake } from "aixyz/model"; import { ToolLoopAgent } from "ai"; export const model = fake((input) => `Echo: ${input}`); export default new ToolLoopAgent({ model, instructions: "You echo back whatever the user says.", }); ``` ## `Prompt` Type alias for `LanguageModelV3Prompt` from `@ai-sdk/provider`. This is an array of messages where each message has a `role` and `content`: ```typescript theme={null} import type { Prompt } from "aixyz/model"; ``` Use this type when you need to reference the prompt shape in your transform function or tests: ```typescript theme={null} import { fake, type Prompt } from "aixyz/model"; const model = fake((_input: string, prompt: Prompt) => { return `${prompt.length} messages in history`; }); ``` ## Testing with `fake()` The fake model makes every test deterministic and CI-safe. Export the model from your agent file so tests can call `doGenerate()` directly: ```typescript title="app/agent.test.ts" theme={null} import { describe, expect, test } from "bun:test"; import { model } from "./agent"; import type { Prompt } from "aixyz/model"; describe("agent (fake model)", () => { test("echoes the user message", async () => { const prompt: Prompt = [{ role: "user", content: [{ type: "text", text: "hello" }] }]; const result = await model.doGenerate({ prompt }); expect(result.content).toEqual([{ type: "text", text: "Echo: hello" }]); }); }); ``` See the [Testing guide](/getting-started/testing#fully-offline-tests-with-fake) and the [Fake Model Agent template](/templates/advanced/fake-llm) for complete examples. # aixyz/app Source: https://aixyz.sh/api-reference/aixyz-server The core application class built on web-standard Request/Response `AixyzApp` is a framework-agnostic server that manages route registration, middleware chaining, and optional x402 payment gating via a `PaymentGateway`. It uses web-standard `Request`/`Response` — no Express dependency. ```typescript theme={null} import { AixyzApp } from "aixyz/app"; ``` ## Constructor ```typescript theme={null} new AixyzApp(options?: AixyzAppOptions) ``` | Parameter | Type | Default | Description | | ---------------------- | ------------------------------------------ | ------- | ------------------------------- | | `options.facilitators` | `FacilitatorClient \| FacilitatorClient[]` | — | Payment verification service(s) | When `facilitators` is provided, a `PaymentGateway` is created and x402 payment verification is enabled for routes that declare a `payment` option. After constructing, register plugins with `await server.withPlugin(...)`, then call `await server.initialize()` to finalize payment routes. ## Properties | Property | Type | Description | | --------- | ----------------------------- | ---------------------------------------------------- | | `routes` | `Map` | Registered routes, keyed by `"METHOD /path"` | | `payment` | `PaymentGateway \| undefined` | Payment gateway (present when facilitators provided) | ## Methods ### `route(method, path, handler, options?)` Register a route with an optional x402 payment requirement: ```typescript theme={null} server.route("POST", "/agent", handler, { payment: { scheme: "exact", price: "$0.005" }, }); ``` | Parameter | Type | Description | | ----------------- | -------------- | ----------------------------------------------------- | | `method` | `HttpMethod` | HTTP method (GET, POST, etc.) | | `path` | `string` | Route path | | `handler` | `RouteHandler` | `(request: Request) => Response \| Promise` | | `options.payment` | `AcceptsX402` | Optional payment configuration | ### `fetch(request)` Dispatch a web-standard `Request` through payment verification, middleware chain, and route handler: ```typescript theme={null} const response = await server.fetch(new Request("http://localhost/agent", { method: "POST" })); ``` ### `use(middleware)` Append a middleware to the chain. Middlewares run in registration order before the route handler: ```typescript theme={null} server.use(async (request, next) => { const response = await next(); return new Response(await response.text(), { status: response.status, headers: { ...Object.fromEntries(response.headers), "x-custom": "value" }, }); }); ``` ### `withPlugin(plugin)` Register a plugin. Creates a scoped `RegisterContext` that auto-tracks routes in `plugin.registeredRoutes`, calls `plugin.register(ctx)`, and returns `this` for chaining: ```typescript theme={null} await server.withPlugin(new IndexPagePlugin()); ``` ### `initialize()` Initialize payment gateway and plugins. Passes an `InitializeContext` with read access to all routes, registered plugins, and payment gateway. Must be called after all plugins are registered: ```typescript theme={null} await server.initialize(); ``` ## Example ```typescript title="app/server.ts" theme={null} import { AixyzApp } from "aixyz/app"; import { SessionPlugin } from "aixyz/app/plugins/session"; import { IndexPagePlugin } from "aixyz/app/plugins/index-page"; import { A2APlugin } from "aixyz/app/plugins/a2a"; import * as agent from "./agent"; const server = new AixyzApp(); // SessionPlugin must be registered first so its middleware runs before other plugins await server.withPlugin(new SessionPlugin()); await server.withPlugin(new IndexPagePlugin()); await server.withPlugin(new A2APlugin([{ exports: agent }])); await server.initialize(); export default server; ``` # experimental_StripePaymentIntentPlugin Source: https://aixyz.sh/api-reference/experimental-stripe-payment-intent-plugin Add Stripe PaymentIntent-based payments to your agent server Adds Stripe PaymentIntent creation and validation to an `AixyzApp`, providing an alternative payment method alongside [x402](/protocols/x402). ```typescript theme={null} import { experimental_StripePaymentIntentPlugin } from "@aixyz/stripe"; ``` This API is experimental and may change significantly in future releases. ## Installation ```bash theme={null} bun add @aixyz/stripe ``` ## Usage ```typescript theme={null} await server.withPlugin(new experimental_StripePaymentIntentPlugin()); ``` ## Environment Variables | Variable | Required | Default | Description | | -------------------- | -------- | ------- | ------------------------------ | | `STRIPE_SECRET_KEY` | Yes | — | Stripe secret key | | `STRIPE_PRICE_CENTS` | No | `100` | Price per request in USD cents | If `STRIPE_SECRET_KEY` is not set, the function is a no-op — no endpoints or middleware are registered. ## Registered Endpoints ### `POST /stripe/create-payment-intent` Creates a new Stripe PaymentIntent for the configured price. **Response:** ```json theme={null} { "clientSecret": "pi_xxx_secret_xxx", "paymentIntentId": "pi_xxx" } ``` ## Payment Middleware After registering the `experimental_StripePaymentIntentPlugin`, the server checks incoming requests for the `x-stripe-payment-intent-id` header: 1. If the header is present, it validates the PaymentIntent: * Status must be `succeeded` * Amount must meet the configured `STRIPE_PRICE_CENTS` * Payment must not have been already consumed 2. Valid payments are marked as consumed (one-time use) and the request proceeds 3. Invalid payments return `402 Payment Required` 4. If no header is present, the request falls through to x402 middleware ## Usage Use in a [custom server](/api-reference/agent) (`app/server.ts`): ```typescript title="app/server.ts" theme={null} import { AixyzApp } from "aixyz/app"; import { IndexPagePlugin } from "aixyz/app/plugins/index-page"; import { A2APlugin } from "aixyz/app/plugins/a2a"; import { experimental_StripePaymentIntentPlugin } from "@aixyz/stripe"; import * as agent from "./agent"; const server = new AixyzApp(); await server.withPlugin(new IndexPagePlugin()); // Register Stripe before other plugins so the Stripe middleware runs first await server.withPlugin(new experimental_StripePaymentIntentPlugin()); await server.withPlugin(new A2APlugin([{ exports: agent }])); await server.initialize(); export default server; ``` Register the `experimental_StripePaymentIntentPlugin` **before** A2A or MCP plugins so the Stripe middleware runs first. If a valid Stripe payment is found, x402 verification is skipped. # SessionPlugin Source: https://aixyz.sh/api-reference/session-plugin Payer-scoped key-value storage gated by x402 payment identity ## Overview `SessionPlugin` provides per-payer key-value storage for aixyz agents. Each x402 signer gets an isolated session -- two different payers never see each other's data. Sessions are accessed via `getSession()` using `AsyncLocalStorage`, so tools don't need to thread payer identity through function parameters. SessionPlugin is **always registered** by the build pipeline. To use a custom storage backend, create an `app/session.ts` file. ```typescript theme={null} import { getSession, getPayer } from "aixyz/app/plugins/session"; ``` ## Setup SessionPlugin is auto-registered when `aixyz build` or `aixyz dev` generates the server entrypoint. No manual setup is needed for the default in-memory store. To use a custom store (Redis, database, etc.), create `app/session.ts`: ```typescript title="app/session.ts" theme={null} import { defineSessionStore, InMemorySessionStore } from "aixyz/app/plugins/session"; export default defineSessionStore(new InMemorySessionStore()); ``` The build pipeline detects `app/session.ts` and passes it to `SessionPlugin({ store: sessionStore })`. If no `app/session.ts` exists, the default `InMemorySessionStore` is used. ## API ### `getSession()` Returns the current payer-scoped `Session`, or `undefined` if no x402 payment was made for this request. ```typescript theme={null} import { getSession } from "aixyz/app/plugins/session"; const session = getSession(); if (!session) { return { error: "No authenticated signer" }; } await session.set("key", "value"); const value = await session.get("key"); ``` ### `getPayer()` Shorthand for `getSession()?.payer`. Returns the x402 signer address or `undefined`. ```typescript theme={null} import { getPayer } from "aixyz/app/plugins/session"; const payer = getPayer(); // "0x1234..." ``` ## Session Interface All operations are scoped to the current x402 payer automatically. | Method | Signature | Description | | -------- | --------------------------------------------------------------------- | --------------------------------------------- | | `payer` | `readonly string` | The x402 signer address | | `get` | `(key: string) => Promise` | Get a value by key | | `set` | `(key: string, value: string, options?: SetOptions) => Promise` | Store a key-value pair | | `delete` | `(key: string) => Promise` | Delete a key, returns whether it existed | | `list` | `(options?: ListOptions) => Promise` | List key-value pairs with optional pagination | ### `SetOptions` | Field | Type | Description | | ------- | -------- | -------------------------------------------------------------------- | | `ttlMs` | `number` | Per-key TTL in milliseconds. Overrides store default when supported. | ### `ListOptions` | Field | Type | Description | | ---------- | --------- | ------------------------------------------------- | | `prefix` | `string` | Only return keys starting with this prefix | | `cursor` | `string` | Opaque cursor from a previous `list()` call | | `limit` | `number` | Maximum number of entries to return | | `keysOnly` | `boolean` | If true, values are omitted (all values are `""`) | ### `ListResult` | Field | Type | Description | | --------- | ------------------------ | ------------------------------------------------------------------- | | `entries` | `Record` | The key-value pairs | | `cursor` | `string \| undefined` | If present, more results are available. Pass to next `list()` call. | ## InMemorySessionStore The default store. Uses LRU eviction and optional sliding-window TTL. * **LRU eviction** -- when `maxEntries` is reached, the least-recently-used entry is evicted * **Sliding-window TTL** -- `get()` refreshes the expiry timer. Expired entries are lazily removed on read. * **OOM-safe** -- `maxEntries` is a hard cap across all payers ```typescript theme={null} import { InMemorySessionStore } from "aixyz/app/plugins/session"; const store = new InMemorySessionStore({ maxEntries: 10_000, // default ttlMs: 3_600_000, // 1 hour default, 0 to disable }); ``` | Option | Type | Default | Description | | ------------ | -------- | --------- | ------------------------------------------------ | | `maxEntries` | `number` | `10000` | Maximum entries across all payers. Must be >= 1. | | `ttlMs` | `number` | `3600000` | Sliding-window TTL in ms. 0 disables expiry. | ## Custom Storage Backend Implement the `SessionStore` interface for Redis, a database, or any KV store: ```typescript title="app/session.ts" theme={null} import { defineSessionStore } from "aixyz/app/plugins/session"; import type { SessionStore, ListOptions, SetOptions } from "aixyz/app/plugins/session"; class RedisSessionStore implements SessionStore { async get(payer: string, key: string) { return (await redis.get(`${payer}:${key}`)) ?? undefined; } async set(payer: string, key: string, value: string, options?: SetOptions) { if (options?.ttlMs) { await redis.set(`${payer}:${key}`, value, "PX", options.ttlMs); } else { await redis.set(`${payer}:${key}`, value); } } async delete(payer: string, key: string) { return (await redis.del(`${payer}:${key}`)) > 0; } async list(payer: string, options?: ListOptions) { // scan for keys matching payer prefix, apply options.prefix/limit/cursor return { entries: { /* ... */ }, }; } } export default defineSessionStore(new RedisSessionStore()); ``` ### `SessionStore` Interface **Required methods:** | Method | Signature | Description | | -------- | ------------------------------------------------------------------------------------ | ----------------------- | | `get` | `(payer: string, key: string) => Promise` | Get a value | | `set` | `(payer: string, key: string, value: string, options?: SetOptions) => Promise` | Store a value | | `delete` | `(payer: string, key: string) => Promise` | Delete a value | | `list` | `(payer: string, options?: ListOptions) => Promise` | List values for a payer | **Optional methods:** | Method | Signature | Description | | ------------ | ----------------------------------------------------------------------------------------- | --------------------------- | | `getMany` | `(payer: string, keys: string[]) => Promise>` | Batch get | | `setMany` | `(payer: string, entries: Record, options?: SetOptions) => Promise` | Batch set | | `deleteMany` | `(payer: string, keys: string[]) => Promise` | Batch delete, returns count | | `close` | `() => Promise` | Release connections/timers | ## MCP Integration Sessions work automatically inside MCP tool handlers. The MCP plugin detects the session plugin and wraps tool execution with the payer context from x402 payment verification. No additional configuration is needed. ## Constructor Options ```typescript theme={null} new SessionPlugin(options?: SessionPluginOptions) ``` | Option | Type | Default | Description | | ------- | -------------- | ---------------------- | ---------------------- | | `store` | `SessionStore` | `InMemorySessionStore` | Custom storage backend | Payment protocol that provides payer identity for sessions. Working example with session-backed content storage. # tools/[name].ts Source: https://aixyz.sh/api-reference/tools Auto-discovered tool definitions for A2A and MCP Each `.ts` file in `app/tools/` is auto-discovered and registered as a tool on both A2A and MCP endpoints. ```typescript title="app/tools/weather.ts" theme={null} import { tool } from "ai"; import { z } from "zod"; import type { Accepts } from "aixyz/accepts"; export const accepts: Accepts = { scheme: "exact", price: "$0.0001", }; export default tool({ description: "Get current weather conditions for a city.", inputSchema: z.object({ location: z.string().describe("City name"), }), execute: async ({ location }) => { // your implementation }, }); ``` ## Exports | Export | Type | Required | Description | | --------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `default` | `tool()` | Yes | The tool instance | | `accepts` | `Accepts` | No | Payment config — gates the tool on MCP `/mcp`. Supports [array format](/getting-started/payments#multiple-payment-options) for multi-network | ## Conventions * **Auto-discovered** — All `.ts` files in `app/tools/` are registered automatically * **Ignored files** — Files starting with `_` (e.g., `_helpers.ts`) are skipped * **Naming** — The tool is registered with the filename as its name (e.g., `weather.ts` → `weather`) # IndexPagePlugin Source: https://aixyz.sh/api-reference/unstable-with-index-page Add a human-readable text page to your agent server Adds a human-readable plain text page to your `AixyzApp` showing agent name, description, version, and skills. ```typescript theme={null} import { IndexPagePlugin } from "aixyz/app/plugins/index-page"; await server.withPlugin(new IndexPagePlugin()); ``` ## Behavior Registers a `GET` handler at the given path that responds with `text/plain` content: ``` My Agent ======== Description: A helpful travel agent Version: 0.1.0 Skills: 1. Search Flights ID: search-flights Description: Search for flights between airports Tags: travel, flights Examples: - Find flights from SFO to LAX ``` The output includes: * Agent **name** and a separator line * **Description** and **version** from config * **Skills** list with IDs, descriptions, tags, and examples (if configured) ## Usage The auto-generated server registers the `IndexPagePlugin` automatically at `"/"`. In a custom server, register it explicitly: ```typescript title="app/server.ts" theme={null} import { AixyzApp } from "aixyz/app"; import { IndexPagePlugin } from "aixyz/app/plugins/index-page"; const server = new AixyzApp(); // Mount at root (default) await server.withPlugin(new IndexPagePlugin()); await server.initialize(); export default server; ``` # aixyz.config.ts Source: https://aixyz.sh/config/aixyz-config Complete reference for the aixyz configuration file Every aixyz project requires an `aixyz.config.ts` at the project root. This file defines your agent's identity, payment settings, and skills. It is validated at build time using Zod schemas. ## Full Example ```typescript title="aixyz.config.ts" theme={null} import type { AixyzConfig } from "aixyz/config"; const config: AixyzConfig = { name: "Weather Agent", description: "Get current weather for any location worldwide.", version: "0.1.0", url: "https://my-agent.vercel.app", x402: { payTo: "0x...", network: "eip155:8453", }, skills: [ { id: "get-weather", name: "Get Weather", description: "Get current weather conditions for any city", tags: ["weather"], examples: ["What's the weather in Tokyo?"], }, ], }; export default config; ``` ## Config Fields | Field | Type | Required | Description | | -------------- | -------------- | -------- | ---------------------------------------- | | `name` | `string` | Yes | Agent display name | | `description` | `string` | Yes | What the agent does | | `version` | `string` | Yes | Semver version | | `url` | `string` | No | Agent base URL (auto-detected on Vercel) | | `x402` | `object` | Yes | Payment configuration | | `x402.payTo` | `string` | Yes | EVM address to receive payments | | `x402.network` | `string` | Yes | CAIP-2 chain ID (e.g., `eip155:8453`) | | `skills` | `AgentSkill[]` | Yes | Skills exposed in the A2A agent card | ### `AgentSkill` | Field | Type | Required | Description | | ------------- | ---------- | -------- | ----------------------- | | `id` | `string` | Yes | Unique skill identifier | | `name` | `string` | Yes | Skill display name | | `description` | `string` | Yes | What the skill does | | `tags` | `string[]` | Yes | Categorization tags | | `examples` | `string[]` | No | Example prompts | | `inputModes` | `string[]` | No | Input MIME types | | `outputModes` | `string[]` | No | Output MIME types | ## URL Resolution If `url` is omitted, it is auto-detected in the following order: 1. `https://${VERCEL_PROJECT_PRODUCTION_URL}/` (when `VERCEL_ENV` is `"production"`) 2. `https://${VERCEL_URL}/` (Vercel preview/other environments) 3. `http://localhost:${PORT}/` (fallback, default port 3000) ## Payment Networks | Network | CAIP-2 ID | | ------------ | -------------- | | Base | `eip155:8453` | | Base Sepolia | `eip155:84532` | | Ethereum | `eip155:1` | Switch networks per environment: ```typescript title="aixyz.config.ts" theme={null} x402: { payTo: "0x...", network: process.env.NODE_ENV === "production" ? "eip155:8453" : "eip155:84532", }, ``` ## Build-Time vs Runtime At build time, the `AixyzConfigPlugin` resolves the full config and materializes a runtime-safe subset into the bundle. This means: * `import config from "aixyz/config"` works at runtime without the config file * Environment variables referenced in the config are baked in at build time * The config file itself is not included in the output bundle # Environment Variables Source: https://aixyz.sh/config/environment-variables Configure your agent with .env files and environment variables ## `.env` Files aixyz loads environment variables from `.env` files in the following order (later files take precedence): 1. `.env` — Shared defaults 2. `.env.local` — Local overrides (gitignored) 3. `.env.$(NODE_ENV)` — e.g., `.env.production` 4. `.env.$(NODE_ENV).local` — e.g., `.env.production.local` This matches the loading order used by Next.js. `.env.local` is always gitignored. Use it for API keys and secrets during development. ## Common Variables | Variable | Description | | ---------------------- | ------------------------------------------------------ | | `OPENAI_API_KEY` | OpenAI API key | | `X402_PAY_TO` | Default payment recipient address | | `X402_NETWORK` | Default payment network | | `X402_FACILITATOR_URL` | Custom x402 facilitator URL | | `CDP_API_KEY_ID` | Coinbase CDP key ID (switches to Coinbase facilitator) | | `CDP_API_KEY_SECRET` | Coinbase CDP key secret | | `STRIPE_SECRET_KEY` | Experimental Stripe adapter | | `STRIPE_PRICE_CENTS` | Stripe price in cents (default: 100) | ## Using in Config Reference environment variables directly in `aixyz.config.ts`: ```typescript title="aixyz.config.ts" theme={null} import type { AixyzConfig } from "aixyz/config"; const config: AixyzConfig = { name: "My Agent", description: "...", version: "0.1.0", x402: { payTo: process.env.X402_PAY_TO!, network: process.env.X402_NETWORK!, }, skills: [], }; export default config; ``` ## Testing Environment For tests, `loadEnv` from `aixyz/test` loads `.env.test.local` (where your `OPENAI_API_KEY` lives for tests). `.env.local` is ignored during testing: ```typescript theme={null} import { loadEnv } from "aixyz/test"; describe("tests", () => { loadEnv(); // process.env.OPENAI_API_KEY is now available }); ``` ## Built-in Environment Variables The CLI automatically sets these environment variables based on the command: | Variable | `aixyz dev` | `aixyz build` | Description | | ----------- | --------------- | -------------- | ---------------------------------------------------------------------------- | | `NODE_ENV` | `"development"` | `"production"` | Standard Node.js environment indicator, for compatibility with node packages | | `AIXYZ_ENV` | `"development"` | `"production"` | aixyz-specific environment indicator, mirrors `NODE_ENV` | ## Build-Time Resolution Environment variables referenced in `aixyz.config.ts` are resolved and baked into the bundle at build time. The config file itself is not included in the output — only the resolved values. # Agent and Tools Source: https://aixyz.sh/getting-started/agent-and-tools Define agents with Vercel AI SDK and create tools for A2A and MCP ## Agent (`app/agent.ts`) Your agent is defined using [Vercel AI SDK](https://ai-sdk.dev) (`ai@^6`). The file exports a default `ToolLoopAgent` and an optional `accepts` for payment pricing. ```typescript title="app/agent.ts" theme={null} import { openai } from "@ai-sdk/openai"; import { stepCountIs, ToolLoopAgent } from "ai"; import type { Accepts } from "aixyz/accepts"; import weather from "./tools/weather"; export const accepts: Accepts = { scheme: "exact", price: "$0.005", }; export default new ToolLoopAgent({ model: openai("gpt-4o-mini"), instructions: "You are a helpful weather assistant.", tools: { weather }, stopWhen: stepCountIs(10), }); ``` ### `ToolLoopAgent` The `ToolLoopAgent` from Vercel AI SDK handles multi-step tool execution loops automatically. It: * Takes a model, instructions, and a set of tools * Executes tool calls in a loop until the stop condition is met * Returns the final response ### `accepts` Export The optional `accepts` named export controls [x402 payment](/getting-started/payments) gating on the A2A `/agent` endpoint: * Agents **with** `accepts` are registered on payment-gated A2A endpoints * Agents **without** `accepts` are not registered You can also pass an array to accept payment on multiple networks: ```typescript theme={null} export const accepts: Accepts = [ { scheme: "exact", price: "$0.005", network: "eip155:8453" }, { scheme: "exact", price: "$0.005", network: "eip155:84532" }, ]; ``` ### `capabilities` Export The optional `capabilities` named export configures the A2A agent card's capabilities and controls how the executor runs your agent: ```typescript title="app/agent.ts" theme={null} import type { Capabilities } from "aixyz/app/plugins/a2a"; export const capabilities: Capabilities = { streaming: false, pushNotifications: false, }; ``` * `streaming` (default: `true`) — When `false`, the executor uses `generate()` instead of `stream()`, returning the full response as a single artifact * `pushNotifications` (default: `false`) — Advertises push notification support in the agent card * `stateTransitionHistory` — Advertises state transition history support in the agent card If omitted, the agent defaults to `{ streaming: true, pushNotifications: false }`. See the [agent.ts reference](/api-reference/agent#capabilities) for details. ## Sub-Agents (`app/agents/*.ts`) Place additional agent files in `app/agents/` to expose multiple A2A endpoints from a single deployment. Each file follows the same format as `app/agent.ts` and is automatically registered on its own path. ```typescript title="app/agents/research.ts" theme={null} // → /research/agent import { openai } from "@ai-sdk/openai"; import { stepCountIs, ToolLoopAgent } from "ai"; import type { Accepts } from "aixyz/accepts"; export const accepts: Accepts = { scheme: "exact", price: "$0.005", }; export default new ToolLoopAgent({ model: openai("gpt-4o-mini"), instructions: "You are a research assistant.", stopWhen: stepCountIs(10), }); ``` With the layout below, the build auto-generates three independent A2A endpoints: Files starting with `_` (e.g., `_helpers.ts`) are ignored by the build pipeline. Use this convention for shared utilities. ## Tools (`app/tools/*.ts`) Each `.ts` file in `app/tools/` exports a Vercel AI SDK `tool` and an optional `accepts` for MCP payment gating. Tools are automatically discovered and registered on both A2A and MCP endpoints. ```typescript title="app/tools/weather.ts" theme={null} import { tool } from "ai"; import { z } from "zod"; import type { Accepts } from "aixyz/accepts"; export const accepts: Accepts = { scheme: "exact", price: "$0.0001", }; export default tool({ description: "Get current weather conditions for a city.", inputSchema: z.object({ location: z.string().describe("City name"), }), execute: async ({ location }) => { // your implementation }, }); ``` ### Tool Discovery The build pipeline automatically discovers all `.ts` files in `app/tools/`: * Each file is registered as an MCP tool * Tools with `accepts` are payment-gated on the `/mcp` endpoint * Tools without `accepts` are not registered on MCP Files starting with `_` (e.g., `_helpers.ts`) are ignored by the build pipeline. Use this convention for shared utilities. ### Per-Tool Pricing Each tool can declare its own price, enabling granular pricing across your agent: ```typescript theme={null} // app/tools/basic-search.ts — free tool (no accepts) export default tool({ ... }); // app/tools/premium-search.ts — paid tool export const accepts: Accepts = { scheme: "exact", price: "$0.001" }; export default tool({ ... }); ``` Tools also support [multiple payment options](/getting-started/payments#multiple-payment-options) via an array of accepts entries. ## Custom Server For full control over endpoint registration and middleware, create `app/server.ts`. This overrides auto-generation entirely: ```typescript title="app/server.ts" theme={null} import { AixyzApp } from "aixyz/app"; import { IndexPagePlugin } from "aixyz/app/plugins/index-page"; import { A2APlugin } from "aixyz/app/plugins/a2a"; import { MCPPlugin } from "aixyz/app/plugins/mcp"; import * as agent from "./agent"; import * as lookup from "./tools/lookup"; const server = new AixyzApp(); await server.withPlugin(new IndexPagePlugin()); await server.withPlugin(new A2APlugin([{ exports: agent }])); await server.withPlugin( new MCPPlugin([ { name: "lookup", exports: { default: lookup.default, accepts: { scheme: "exact", price: "$0.001" }, }, }, ]), ); await server.initialize(); export default server; ``` See the [Custom Server template](/templates/advanced/with-custom-server) for a working example. # Deploying Source: https://aixyz.sh/getting-started/deploying Deploy your agent to Vercel, standalone Bun, or Docker ## Vercel aixyz has first-class Vercel support using the [Build Output API v3](https://vercel.com/docs/build-output-api/v3). ```bash theme={null} bun run build vercel deploy ``` The build automatically detects the Vercel environment via `VERCEL=1` and outputs a Vercel serverless function using Bun Runtime. The agent URL is auto-detected from Vercel environment variables. ### `vercel.json` Every aixyz project includes a `vercel.json`: ```json title="vercel.json" theme={null} { "$schema": "https://openapi.vercel.sh/vercel.json", "framework": null, "buildCommand": "bun run build", "bunVersion": "1.x" } ``` No need to pass `--vercel` — the flag is automatically detected during Vercel builds. Set `OPENAI_API_KEY` and any payment environment variables in the Vercel dashboard under **Settings → Environment Variables**. ## Standalone (Bun Runtime) For direct deployment on Bun-compatible hosts (DigitalOcean, Railway, Fly.io, etc.): ```bash theme={null} aixyz build bun .aixyz/output/server.js ``` The build outputs a single bundled file at `.aixyz/output/server.js`. ### Environment Variables Set these before running: ```bash theme={null} export PORT=3000 export OPENAI_API_KEY=sk-... bun .aixyz/output/server.js ``` ## Docker Using the standalone build: ```dockerfile title="Dockerfile" theme={null} FROM oven/bun:1.3.9 WORKDIR /app COPY . . RUN bun install RUN bun run build CMD ["bun", ".aixyz/output/server.js"] ``` Build and run: ```bash theme={null} docker build -t my-agent . docker run -p 3000:3000 \ -e OPENAI_API_KEY=sk-... \ -e PORT=3000 \ my-agent ``` Never bake API keys into Docker images. Pass them as environment variables at runtime. Set the `url` field in `aixyz.config.ts` manually for Docker deployments since Vercel environment variables won't be available. ## Build Output Formats | Format | Flag | Output Path | Description | | ---------- | ------------------------------- | ------------------------- | --------------------------------------------------------------------------------- | | Standalone | (default) | `.aixyz/output/server.js` | Single-file bundle, run with `bun` | | Vercel | `VERCEL=1` or `--output vercel` | `.vercel/output/` | [Build Output API v3](https://vercel.com/docs/build-output-api/v3) for serverless | | Executable | `--output executable` | `.aixyz/output/server` | Self-contained binary, no Bun runtime required | ## Verifying Your Deployment After deploying, verify your agent is accessible: ```bash theme={null} # Check the agent card curl https://your-agent.example.com/.well-known/agent-card.json # Test the A2A endpoint curl -X POST https://your-agent.example.com/agent \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "tasks/send", "id": "1", "params": { "id": "task-1", "message": { "role": "user", "parts": [{"type": "text", "text": "Hello"}] } } }' ``` # Installation Source: https://aixyz.sh/getting-started/installation Install Bun and create your first aixyz agent ## Prerequisites * [Bun](https://bun.sh) 1.3.9 or later * An API key for your preferred LLM provider (e.g., OpenAI) Verify your Bun version: ```bash theme={null} bun --version # Should be ≥ 1.3.9 ``` ## Create a New Project Scaffold a new agent project with `create-aixyz-app`: ```bash theme={null} bunx create-aixyz-app my-agent cd my-agent ``` The CLI prompts for an agent name and optional OpenAI API key. Use `-y` to skip prompts: ```bash theme={null} bunx create-aixyz-app my-agent -y ``` ## Start the Dev Server ```bash theme={null} bun run dev ``` This runs `aixyz dev`, which watches `app/` and `aixyz.config.ts` for changes and automatically restarts the server. Your agent is available at `http://localhost:3000`. ## Verify It Works ```bash theme={null} # Check the agent card curl http://localhost:3000/.well-known/agent-card.json # Test the A2A endpoint curl -X POST http://localhost:3000/agent \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"tasks/send","id":"1","params":{"id":"task-1","message":{"role":"user","parts":[{"type":"text","text":"Hello"}]}}}' ``` The MCP endpoint is available at `http://localhost:3000/mcp` for MCP-compatible clients. ## Endpoints Once running, your agent exposes: | Endpoint | Protocol | Description | | ------------------------------ | -------- | ---------------------------------------- | | `/.well-known/agent-card.json` | A2A | Agent discovery card | | `/agent` | A2A | JSON-RPC endpoint with x402 payment gate | | `/mcp` | MCP | Tool sharing with MCP clients | # Payments (x402) Source: https://aixyz.sh/getting-started/payments Add micropayments to your agent with x402 ## Overview [x402](https://www.x402.org/) is a payment protocol for HTTP resources. aixyz has built-in x402 support — when a client requests a payment-gated endpoint, the server responds with HTTP 402 and payment requirements. The client includes a payment proof in the `X-Payment` header, and the server verifies it via a facilitator. ## How It Works 1. Client requests a gated endpoint (e.g., `POST /agent`) 2. Server returns **HTTP 402** with payment requirements (price, network, payTo address) 3. Client constructs payment proof and retries with `X-Payment` header 4. Server verifies payment via a facilitator and grants access ## The `accepts` Export Define payment requirements by exporting `accepts` from your agent or tool: ```typescript theme={null} import type { Accepts } from "aixyz/accepts"; export const accepts: Accepts = { scheme: "exact", price: "$0.005", }; ``` This gates the endpoint behind a \$0.005 exact payment. ### With Overrides ```typescript theme={null} export const accepts: Accepts = { scheme: "exact", price: "$0.005", network: "eip155:8453", // override config network payTo: "0x...", // override config payTo }; ``` Prices are USD strings: `"$0.005"`, `"$0.01"`, `"$0.0001"`. ## Per-Agent and Per-Tool Pricing Agents and tools can each declare their own `accepts` export, enabling granular pricing: ```typescript theme={null} // app/agent.ts — agent-level pricing (gates /agent) export const accepts: Accepts = { scheme: "exact", price: "$0.01", }; // app/tools/premium-search.ts — tool-level pricing (gates on /mcp) export const accepts: Accepts = { scheme: "exact", price: "$0.001", }; ``` * Agent `accepts` gates the A2A `/agent` endpoint * Tool `accepts` gates the tool on the MCP `/mcp` endpoint * Agents and tools **without** `accepts` are not registered on protocol endpoints ## Multiple Payment Options Accept payment across multiple networks by passing an array of payment entries. This lets clients pay on whichever network they prefer: ```typescript theme={null} export const accepts: Accepts = [ { scheme: "exact", price: "$0.005", network: "eip155:8453" }, // Base { scheme: "exact", price: "$0.005", network: "eip155:84532" }, // Base Sepolia ]; ``` When using the array format, `network` is **required** on each entry so the server can register the correct payment scheme for each network. All referenced networks are automatically discovered and registered during server initialization — no manual configuration needed beyond the `accepts` export. This works for both agents and tools: ```typescript theme={null} // app/agent.ts — multi-network agent pricing export const accepts: Accepts = [ { scheme: "exact", price: "$0.01", network: "eip155:8453" }, { scheme: "exact", price: "$0.01", network: "eip155:1" }, ]; // app/tools/premium-search.ts — multi-network tool pricing export const accepts: Accepts = [ { scheme: "exact", price: "$0.001", network: "eip155:8453" }, { scheme: "exact", price: "$0.001", network: "eip155:1" }, ]; ``` ## Payment Networks Configure the payment network in `aixyz.config.ts`: | Network | CAIP-2 ID | | ------------ | -------------- | | Base | `eip155:8453` | | Base Sepolia | `eip155:84532` | | Ethereum | `eip155:1` | Switch networks per environment: ```typescript theme={null} x402: { payTo: "0x...", network: process.env.NODE_ENV === "production" ? "eip155:8453" : "eip155:84532", }, ``` ## Facilitators Payment verification is handled by a facilitator service: | Facilitator | Activation | URL | | ------------ | ---------------------------- | ------------------------------------------ | | Default | Always available | `https://x402.use-agently.com/facilitator` | | Coinbase CDP | When `CDP_API_KEY_ID` is set | Auto-configured | | Custom | Set `X402_FACILITATOR_URL` | Your custom URL | ### Custom Facilitator Create `app/accepts.ts` to provide a custom facilitator: ```typescript title="app/accepts.ts" theme={null} import { HTTPFacilitatorClient } from "aixyz/accepts"; export const facilitator = new HTTPFacilitatorClient({ url: process.env.X402_FACILITATOR_URL ?? "https://x402.use-agently.com/facilitator", }); ``` See the [BYO Facilitator template](/templates/advanced/with-custom-facilitator) for a working example. ## Payer Identity and Sessions Every verified x402 payment identifies the payer by their wallet address. `SessionPlugin` (auto-registered by the build pipeline) gives each payer isolated key-value storage accessible via `getSession()`: ```typescript theme={null} import { getSession } from "aixyz/app/plugins/session"; const session = getSession(); if (session) { await session.set("preference", "dark-mode"); const payer = session.payer; // "0x1234..." } ``` This works in both A2A agent handlers and MCP tool handlers. See [SessionPlugin](/api-reference/session-plugin) for the full API and custom store configuration. # Project Structure Source: https://aixyz.sh/getting-started/project-structure Understand the app/ directory structure and file conventions An aixyz agent is defined by a small set of files in a standard layout. Run `aixyz dev` and the framework auto-generates a server from them. ## Directory Layout
required Must be provided optional override Provide to override auto-generated behavior
## Key Files | File | Required | Description | | ------------------- | -------- | --------------------------------------------------------------------------------- | | `aixyz.config.ts` | Yes | [Agent metadata, payment config, and skills](/config/aixyz-config) | | `app/agent.ts` | No\* | Agent definition using Vercel AI SDK | | `app/agents/*.ts` | No | Sub-agent definitions, each gets its own endpoint | | `app/tools/*.ts` | No | Tool implementations, auto-discovered by the build | | `app/server.ts` | No | [Custom server](/api-reference/aixyz-server) — overrides auto-generation entirely | | `app/accepts.ts` | No | [Custom x402 facilitator](/api-reference/accepts) for payment verification | | `app/session.ts` | No | [Custom session store](/api-reference/session-plugin) override for SessionPlugin | | `app/erc-8004.ts` | No | ERC-8004 identity registration and trust config | | `app/agent.test.ts` | No | Agent tests using `bun:test` | | `app/icon.svg` | No | Agent icon served as a static asset | | `vercel.json` | No | Vercel deployment configuration | \* If omitted, the build skips A2A endpoints and only serves tools via MCP. ## How It Works The build pipeline scans the `app/` directory to auto-generate a server: 1. Reads `aixyz.config.ts` for agent metadata and skills 2. Imports `app/agent.ts` for the main agent definition (if present) 3. Discovers all `.ts` files in `app/agents/` for sub-agents (each gets its own A2A endpoint) 4. Discovers all `.ts` files in `app/tools/` (excluding `_` prefixed files) 5. Registers `SessionPlugin` (with custom store from `app/session.ts` if present) 6. Wires up A2A (when agents exist), MCP (when tools exist), and x402 endpoints automatically The result is a server exposing: * **A2A** at `/agent` and `/.well-known/agent-card.json` (main agent) * **A2A** at `/{name}/agent` and `/{name}/.well-known/agent-card.json` (per sub-agent in `app/agents/`) * **MCP** at `/mcp` * **x402** payment verification on gated endpoints For full control, provide `app/server.ts` to override auto-generation. See the [Custom Server template](/templates/advanced/with-custom-server) for an example. # Testing Source: https://aixyz.sh/getting-started/testing Test your agent with bun:test and aixyz/test utilities ## Overview aixyz provides a `loadEnv` utility from `aixyz/test` for loading environment variables during tests. Combined with Bun's built-in test runner, you can write both deterministic and non-deterministic agent tests. ## Test File Convention Place your test file alongside your agent: ## Writing Tests ```typescript title="app/agent.test.ts" theme={null} import { describe, expect, test } from "bun:test"; import { ToolLoopAgent } from "ai"; import { loadEnv } from "aixyz/test"; import agent, { accepts } from "./agent"; // Deterministic tests — no API calls, always run test("default export is a ToolLoopAgent", () => { expect(agent).toBeInstanceOf(ToolLoopAgent); }); test("has convertTemperature tool registered", () => { expect(agent.tools).toHaveProperty("convertTemperature"); }); test("accepts config uses exact scheme", () => { expect(accepts.scheme).toBe("exact"); }); // Non-deterministic tests — require API key, skip gracefully in CI describe("non deterministic agent test", () => { loadEnv(); test.skipIf(!process.env.OPENAI_API_KEY)("agent can convert temperature", async () => { const result = await agent.generate({ prompt: "convert 100 degrees celsius to fahrenheit", }); expect(result.text).toContain("212"); }); }); ``` ## `loadEnv()` The `loadEnv` function from `aixyz/test` loads environment variables for test runs. It loads `.env.test.local` (where your `OPENAI_API_KEY` lives for tests) while `.env.local` is ignored during testing. ```typescript theme={null} import { loadEnv } from "aixyz/test"; describe("tests needing env vars", () => { loadEnv(); // process.env is now populated }); ``` ## Deterministic vs Non-Deterministic | Type | Description | CI-Safe | | ----------------- | -------------------------------------- | ------- | | Deterministic | Validate agent type, tools, config | Yes | | Non-deterministic | Call LLM and validate response content | No\* | \* Use `test.skipIf(!process.env.OPENAI_API_KEY)` to skip gracefully when no API key is available. ## Fully Offline Tests with `fake()` Use `fake()` from `aixyz/model` to replace the real LLM with a deterministic transform. It returns a `LanguageModelV3` that works with `ToolLoopAgent` — no API key, no network calls: ```typescript title="app/agent.ts" theme={null} import { fake } from "aixyz/model"; import { ToolLoopAgent } from "ai"; export const model = fake((lastMessage) => `You said: ${lastMessage}`); export default new ToolLoopAgent({ model, instructions: "..." }); ``` Then test the model's output directly: ```typescript title="app/agent.test.ts" theme={null} import type { Prompt } from "aixyz/model"; import { model } from "./agent"; test("echoes back the user message", async () => { const prompt: Prompt = [{ role: "user", content: [{ type: "text", text: "hello" }] }]; const result = await model.doGenerate({ prompt }); expect(result.content).toEqual([{ type: "text", text: "You said: hello" }]); }); ``` See the [`aixyz/model` API reference](/api-reference/fake) for full details and the [Fake Model Agent template](/templates/advanced/fake-llm) for a complete working example. ## Running Tests ```bash theme={null} # Run all tests bun test # Run a specific test file bun test app/agent.test.ts ``` # Why Bun? Source: https://aixyz.sh/getting-started/why-bun Why aixyz uses Bun as its runtime ## Software is Being Rewritten — by Agents We are entering a world where humans won't be writing most software. AI agents will. And agents will be creating other agents. When Claude Code scaffolds a project, installs dependencies, writes tests, and iterates on failures — all in a single session — the bottleneck isn't the model. It's the runtime. In December 2025, Anthropic made their first-ever acquisition: [they bought Bun](https://www.anthropic.com/news/anthropic-acquires-bun-as-claude-code-reaches-usd1b-milestone). Not a model company. Not a data company. A JavaScript runtime. That tells you everything about where this is going. We're making the same bet. ### The Runtime is the Agent's Operating System Every tool an AI agent calls is a runtime operation — HTTP requests, file I/O, code execution, JSON parsing. An agent making dozens of tool calls per session doesn't experience latency the way a human does. For a human, 500ms is imperceptible. For an agent in a tight loop, [it's the difference between fast iteration and a stall](https://dev.to/inboryn_99399f96579fcd705/anthropic-acquires-bun-why-ai-agents-need-a-lightweight-runtime-228d). Node.js was designed for a world where humans start a server and it runs for hours. Bun was designed for a world where processes spin up, execute, and terminate in milliseconds. That's the world agents live in — generate code, run it, check the output, fix the errors, repeat. The runtime needs to keep up. Anthropic understood this before anyone else. Claude Code [ships as a Bun executable](https://betterstack.com/community/guides/scaling-nodejs/anthropic-acquires-bun/). So does [FactoryAI. So does OpenCode](https://betterstack.com/community/guides/scaling-nodejs/anthropic-acquires-bun/). The pattern is clear: [the runtime is becoming the agent's operating system](https://jimmysong.io/blog/bun-anthropic-runtime-shift/). ### One Binary, Zero Configuration The Node.js ecosystem asks you to choose: which package manager, which bundler, which test runner, which TypeScript compiler. Five tools, five configurations, five places where things break. Bun is one binary that does all of it: ```bash theme={null} bun install # Package manager bun run dev # Runtime bun run build # Bundler bun test # Test runner ``` This isn't just developer convenience. [For an AI agent that needs to spin up projects, test, bundle, run, and iterate at speed, that fragmentation is friction](https://dev.to/axrisi/anthropic-just-acquired-bun-and-it-signals-the-beginning-of-ai-native-software-engineering-3cd9). Every tool choice is a decision point. Every configuration file is a place where an automated workflow can break. Bun eliminates the entire category of problem. Jarred Sumner, Bun's creator, said it directly: [Bun's job is to be the best place to build, run, and test AI-driven software](https://bun.com/blog/bun-joins-anthropic). ### TypeScript Without the Ceremony Bun runs TypeScript natively. No compilation step. No `tsconfig.json`. No build pipeline between writing code and running it. aixyz packages ship raw `.ts` files and Bun handles them directly. When an agent generates TypeScript, it can execute it immediately — the generate-run-verify loop has zero gap. This is the kind of simplicity that [makes agent-driven development practical at scale](https://dev.to/axrisi/anthropic-just-acquired-bun-and-it-signals-the-beginning-of-ai-native-software-engineering-3cd9). ### Compile Once, Run Anywhere Bun compiles projects into [single-file executables](https://bun.com/docs/bundler/executables). No runtime installation required. No dependency management on the target machine. This is how Claude Code distributes to millions of developers — one binary, every platform. For aixyz agents deploying to serverless platforms like Vercel, Bun's cold starts are fast enough that your agent responds quickly even on first invocation: ```json title="vercel.json" theme={null} { "buildCommand": "bun run build", "bunVersion": "1.x" } ``` ### Built-in Testing Bun includes a Jest-compatible test runner with near-instant startup. aixyz uses `bun:test` for agent testing: ```typescript title="app/agent.test.ts" theme={null} import { describe, expect, test } from "bun:test"; import { ToolLoopAgent } from "ai"; import agent from "./agent"; test("default export is a ToolLoopAgent", () => { expect(agent).toBeInstanceOf(ToolLoopAgent); }); ``` # Introduction Source: https://aixyz.sh/index Payment-native SDK for AI Agents. Build an agent, get paid today. **aixyz** is the payment-native SDK for AI agents. Build agents and tools with your favorite framework, and get paid instantly through web3 payment rails — no payment integration work required. > Build an agent and get paid today. Every agent you deploy with aixyz comes with [x402 payments](/protocols/x402) embedded. When another agent or user calls yours, settlement happens on-chain automatically. You set a price, deploy, and start earning. ## How It Works 1. **Build your agent** — Use [Vercel AI SDK](https://ai-sdk.dev) (`ai@^6`) or any framework to create agents with tools 2. **Set your price** — Export an `accepts` object to gate endpoints with instant micropayments 3. **Deploy and earn** — Run `aixyz build` and deploy. Payments settle on-chain via [x402](https://www.x402.org/) ```typescript title="app/agent.ts" theme={null} import type { Accepts } from "aixyz/accepts"; // set your price and start earning export const accepts: Accepts = { scheme: "exact", price: "$0.01", }; ``` aixyz auto-generates a server that exposes: | Endpoint | Protocol | Description | | ------------------------------ | -------- | ---------------------------------------- | | `/.well-known/agent-card.json` | A2A | Agent discovery card | | `/agent` | A2A | JSON-RPC endpoint with x402 payment gate | | `/mcp` | MCP | Tool sharing with MCP clients | ## Quick Start ```bash theme={null} bunx create-aixyz-app my-agent cd my-agent bun run dev ``` Your agent is running at `http://localhost:3000` with payments, [A2A](/protocols/a2a), and [MCP](/protocols/mcp) out of the box. Create and run your first agent in under a minute. Set prices for your agent and tools with x402. Define agents and tools with Vercel AI SDK. Working agent templates to start from. Deploy to Vercel, Docker, or standalone. # aixyz Source: https://aixyz.sh/packages/aixyz CLI for building and running aixyz agents The `aixyz` package includes the CLI for developing and building agents. Install it to get the `aixyz` command. ```bash theme={null} bun add aixyz ``` ## Commands ### `aixyz dev` Starts a local development server with hot reload. Watches `app/` and `aixyz.config.ts` for changes and restarts automatically (100ms debounce). ```bash theme={null} aixyz dev aixyz dev --port 4000 ``` | Flag | Default | Description | | ------------ | ------- | ----------------- | | `-p, --port` | `3000` | Port to listen on | On startup, the dev server prints the A2A and MCP endpoints: ``` ⟡ aixyz.sh v0.0.0 - A2A: http://localhost:3000/.well-known/agent-card.json - MCP: http://localhost:3000/mcp - Environments: .env, .env.local ``` If no `app/server.ts` exists, one is auto-generated from `app/agent.ts` and `app/tools/*.ts`. ### `aixyz build` Bundles your agent for deployment. ```bash theme={null} aixyz build # Standalone (default) aixyz build --output standalone # Explicit standalone aixyz build --output vercel # Vercel Build Output API v3 aixyz build --output executable # Self-contained binary ``` | Flag | Default | Description | | ---------- | ------------ | ------------------------------------------------------ | | `--output` | `standalone` | Output format: `standalone`, `vercel`, or `executable` | **Standalone** output goes to `.aixyz/output/server.js` — run it with `bun .aixyz/output/server.js`. **Vercel** output goes to `.vercel/output/` using Build Output API v3. Automatically detected when `VERCEL=1` is set or configured in `aixyz.config.ts`. The build process: 1. Loads environment variables from `.env` files 2. Reads and validates `aixyz.config.ts` 3. Detects or auto-generates the server entrypoint 4. Bundles with `Bun.build()`, [materializing the config](/api-reference/aixyz-config) into the bundle 5. Copies static assets from `public/` and `app/icon.png` ### `aixyz erc-8004 register` Registers a new agent on the ERC-8004 IdentityRegistry. Creates `app/erc-8004.ts` if it doesn't exist (prompting for supported trust mechanisms), asks for your agent's deployment URL, and derives the on-chain URI as `/_aixyz/erc-8004.json`. After a successful registration, the entry is written back to `app/erc-8004.ts`. ```bash theme={null} aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia --broadcast ``` | Flag | Description | | ------------- | ------------------------------------------------- | | `--url` | Agent deployment URL (prompted if omitted) | | `--chain` | `mainnet`, `sepolia`, `base-sepolia`, `localhost` | | `--rpc-url` | Custom RPC endpoint | | `--keystore` | Encrypted keystore file | | `--browser` | Use browser wallet (EIP-6963) | | `--broadcast` | Send transaction (default is dry-run) | | `--out-dir` | Write result JSON to directory | ### `aixyz erc-8004 update` Updates the metadata URI of a registered agent. Reads existing registrations from `app/erc-8004.ts`, lets you select which one to update (if multiple), and derives the new URI from the provided URL. ```bash theme={null} aixyz erc-8004 update --url "https://new-domain.example.com" --broadcast ``` | Flag | Description | | ------------- | ---------------------------------------------- | | `--url` | New agent deployment URL (prompted if omitted) | | `--rpc-url` | Custom RPC endpoint | | `--keystore` | Encrypted keystore file | | `--browser` | Use browser wallet (EIP-6963) | | `--broadcast` | Send transaction (default is dry-run) | | `--out-dir` | Write result JSON to directory | See [ERC-8004 Identity](/protocols/erc-8004) for the full protocol documentation. # create-aixyz-app Source: https://aixyz.sh/packages/create-aixyz-app Scaffold new aixyz agent projects ## Overview `create-aixyz-app` is a project scaffolding CLI that generates a new aixyz agent project with the standard directory structure, configuration, and example files. ## Usage ```bash theme={null} bunx create-aixyz-app my-agent ``` The CLI prompts for: * **Agent name** — Used in `aixyz.config.ts` and `package.json` * **OpenAI API key** — Saved to `.env.local` (optional) ### Non-Interactive Mode Skip all prompts with the `-y` flag: ```bash theme={null} bunx create-aixyz-app my-agent -y ``` ## What Gets Scaffolded ### Scripts The generated `package.json` includes: ```json title="package.json" theme={null} { "scripts": { "dev": "aixyz dev", "build": "aixyz build" } } ``` ## After Scaffolding ```bash theme={null} cd my-agent bun install bun run dev ``` Your agent starts at `http://localhost:3000` with A2A, MCP, and x402 support ready to go. ## Next Steps Full walkthrough of setting up your agent. Understand the generated project layout. # A2A Protocol Source: https://aixyz.sh/protocols/a2a Agent-to-Agent protocol for agent discovery and communication ## Overview The [A2A (Agent-to-Agent)](https://google.github.io/A2A/) protocol enables standardized agent discovery and inter-agent communication. aixyz implements A2A protocol version **0.3.0** with an agent card for discovery and a JSON-RPC endpoint for task execution. ## Endpoints | Endpoint | Method | Description | | ------------------------------ | ------ | ------------------------------------------------------------ | | `/.well-known/agent-card.json` | GET | Agent discovery card with metadata, skills, and capabilities | | `/agent` | POST | JSON-RPC task handler, x402-gated if `accepts` is defined | ## Agent Card The agent card is automatically generated from your `aixyz.config.ts`. Other agents fetch this card to discover your agent's capabilities. ```bash theme={null} curl http://localhost:3000/.well-known/agent-card.json ``` Example response: ```json theme={null} { "name": "Weather Agent", "description": "Get current weather for any location worldwide.", "protocolVersion": "0.3.0", "version": "0.1.0", "url": "https://my-agent.vercel.app/agent", "capabilities": { "streaming": true, "pushNotifications": false }, "defaultInputModes": ["text/plain"], "defaultOutputModes": ["text/plain"], "skills": [ { "id": "get-weather", "name": "Get Weather", "description": "Get current weather conditions for any city", "tags": ["weather"], "examples": ["What's the weather in Tokyo?"] } ] } ``` ## JSON-RPC Endpoint The `/agent` endpoint accepts JSON-RPC 2.0 requests. The primary method is `tasks/send`: ```bash theme={null} curl -X POST http://localhost:3000/agent \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "method": "tasks/send", "id": "1", "params": { "id": "task-1", "message": { "role": "user", "parts": [{"type": "text", "text": "What is the weather in NYC?"}] } } }' ``` ## Capabilities The agent card's `capabilities` field is configured via the `capabilities` export from your agent file. If omitted, defaults to `{ streaming: true, pushNotifications: false }`. ```typescript title="app/agent.ts" theme={null} import type { Capabilities } from "aixyz/app/plugins/a2a"; export const capabilities: Capabilities = { streaming: false, pushNotifications: false, }; ``` When `streaming` is `false`, the executor uses `agent.generate()` instead of `agent.stream()`, returning a single artifact with the full response. See [agent.ts — Capabilities](/api-reference/agent#capabilities) for the full reference. ## Using the A2A Plugin The `A2APlugin` wires up both endpoints on your server: ```typescript theme={null} import { A2APlugin } from "aixyz/app/plugins/a2a"; // agentExports is `import * as agent from "./agent"` // reads `default` (the ToolLoopAgent), `accepts` (payment config), and `capabilities` await server.withPlugin(new A2APlugin([{ exports: agent }])); ``` This registers: * A **GET** handler at `/.well-known/agent-card.json` serving the agent card * A **POST** handler at `/agent` routing JSON-RPC requests to `ToolLoopAgentExecutor` ### Sub-Agent Routing Pass multiple entries with a `name` to mount sub-agents under their own paths: ```typescript title="app/server.ts" theme={null} import * as research from "./agents/research"; import * as implement from "./agents/implement"; await server.withPlugin( new A2APlugin([ { exports: agent }, // → /agent { name: "research", exports: research }, // → /research/agent { name: "implement", exports: implement }, // → /implement/agent ]), ); ``` Each sub-agent gets its own agent card at `/{name}/.well-known/agent-card.json` and its own JSON-RPC endpoint at `/{name}/agent`. When using `app/agents/`, the build pipeline generates these calls automatically. ## Payment Integration When an agent exports an `accepts` configuration with `scheme: "exact"`, the `/agent` endpoint is gated behind [x402 payment](/protocols/x402). Clients must include a valid `X-Payment` header. Agents without `accepts` are served without payment requirements. How payment gating works on A2A endpoints. Expose tools via MCP alongside A2A. # ERC-8004 Identity Source: https://aixyz.sh/protocols/erc-8004 On-chain agent identity using the ERC-8004 standard ## Overview ERC-8004 defines an on-chain identity standard for AI agents. It allows agents to register their identity on a blockchain, providing verifiable, decentralized identity for agent-to-agent and agent-to-user interactions. aixyz supports ERC-8004 through the `@aixyz/erc-8004` package (contract ABIs, deployed addresses, and Zod schemas) and built-in CLI commands for registry operations. ## Supported Networks **Mainnets:** Ethereum, Base, Polygon, Scroll, Monad, BSC, Gnosis **Testnets:** Sepolia, Base Sepolia, Polygon Amoy, Scroll Sepolia, Monad Testnet, BSC Testnet ## The `app/erc-8004.ts` File Every agent that uses ERC-8004 identity declares an `app/erc-8004.ts` file. This file defines the agent's supported trust mechanisms and tracks on-chain registrations: ```typescript title="app/erc-8004.ts" theme={null} import type { ERC8004Registration } from "aixyz/erc-8004"; const metadata: ERC8004Registration = { registrations: [], supportedTrust: ["reputation"], }; export default metadata; ``` When this file exists, the build pipeline automatically exposes two endpoints: * `GET /_aixyz/erc-8004.json` These serve the full agent registration file, merging defaults from `aixyz.config.ts` (name, description, image, services). ## Register an Agent Use `aixyz erc-8004 register` to register your agent on the IdentityRegistry: ```bash theme={null} aixyz erc-8004 register --chain sepolia --broadcast ``` The command walks you through an interactive flow: 1. **Creates `app/erc-8004.ts`** if it doesn't exist — prompts you to select supported trust mechanisms 2. **Asks for your agent's deployment URL** (e.g., `https://my-agent.vercel.app`) 3. **Derives the on-chain URI** as `/_aixyz/erc-8004.json` and asks you to confirm 4. **Selects chain and wallet**, then signs and broadcasts the transaction 5. **Writes the registration** back into the `registrations` array in `app/erc-8004.ts` After registration, your `app/erc-8004.ts` will contain the on-chain reference: ```typescript title="app/erc-8004.ts" theme={null} const metadata: ERC8004Registration = { registrations: [{ agentId: 42, agentRegistry: "eip155:84532:0x8004A818BFB912233c491871b3d84c89A494BD9e" }], supportedTrust: ["reputation"], }; ``` | Flag | Description | | ------------- | ------------------------------------------------- | | `--url` | Agent deployment URL (prompted if omitted) | | `--chain` | `mainnet`, `sepolia`, `base-sepolia`, `localhost` | | `--rpc-url` | Custom RPC endpoint | | `--keystore` | Encrypted keystore file | | `--browser` | Use browser wallet (EIP-6963) | | `--broadcast` | Send transaction (default is dry-run) | ## Update Agent URI After redeploying to a new URL, update the on-chain URI with `aixyz erc-8004 update`: ```bash theme={null} aixyz erc-8004 update --url "https://new-domain.example.com" --broadcast ``` The command reads existing registrations from `app/erc-8004.ts`: * If there's one registration, it confirms and proceeds * If there are multiple, it prompts you to select which one to update The chain and registry address are derived automatically from the selected registration's `agentRegistry` field. | Flag | Description | | ------------- | ---------------------------------------------- | | `--url` | New agent deployment URL (prompted if omitted) | | `--rpc-url` | Custom RPC endpoint | | `--keystore` | Encrypted keystore file | | `--browser` | Use browser wallet (EIP-6963) | | `--broadcast` | Send transaction (default is dry-run) | ## Programmatic Usage Use the `@aixyz/erc-8004` package to interact with the registry from code: ```typescript theme={null} import { IdentityRegistryAbi, getIdentityRegistryAddress } from "@aixyz/erc-8004"; ``` The package provides: * **Contract ABIs** — TypeScript-typed ABIs for use with viem, ethers, or wagmi * **Deployed addresses** — Known contract addresses across supported networks * **Zod schemas** — Validation schemas for registration data and agent URIs ## Integration with aixyz ERC-8004 identity complements the other protocols: * **A2A** — The agent card can reference an on-chain identity for verification * **x402** — Payment gating tied to verified on-chain agent identities * **MCP** — Tool discovery backed by verifiable identity Agent discovery with A2A agent cards. Payment gating for agent endpoints. # MCP Protocol Source: https://aixyz.sh/protocols/mcp Model Context Protocol support for sharing tools with MCP clients ## Overview The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) allows AI clients to discover and invoke tools exposed by your agent. aixyz implements MCP via the `MCPPlugin`, serving tools at `/mcp` over a stateless `WebStandardStreamableHTTPServerTransport`. ## Endpoints | Endpoint | Method | Description | | -------- | ------ | ------------------------------------------------------------------ | | `/mcp` | POST | JSON-RPC requests using `WebStandardStreamableHTTPServerTransport` | | `/mcp` | GET | SSE stream for server-initiated messages | | `/mcp` | DELETE | Session termination (stateless, returns 200) | ## Compatible Clients Any MCP-compatible client can connect to your agent's `/mcp` endpoint: * **Claude Desktop** * **VS Code** (GitHub Copilot) * **Cursor** * Other MCP-enabled tools Point the client at your agent's MCP URL: ``` http://localhost:3000/mcp ``` ## Automatic Registration When using the standard `app/` directory structure, all tools in `app/tools/*.ts` are automatically registered as MCP tools during the build. No additional configuration is needed. Files starting with `_` (e.g., `app/tools/_helpers.ts`) are excluded from tool registration. ## Manual Setup If you're using a custom server, wire up MCP manually with the `MCPPlugin`: ```typescript title="app/server.ts" theme={null} import { MCPPlugin } from "aixyz/app/plugins/mcp"; import * as lookup from "./tools/lookup"; // Register tools and mount the /mcp endpoint await server.withPlugin( new MCPPlugin([ { name: "lookup", exports: { default: lookup.default, accepts: { scheme: "exact", price: "$0.001" }, }, }, ]), ); ``` ## Session Integration Paid MCP tools automatically get session context. When a tool is invoked with x402 payment, `getSession()` returns the payer-scoped session inside the tool handler — no additional configuration needed. ```typescript theme={null} import { getSession } from "aixyz/app/plugins/session"; // Inside an MCP tool handler: const session = getSession(); await session?.set("key", "value"); // stored per-payer ``` See [SessionPlugin](/api-reference/session-plugin) for the full API. ## Payment-Gated Tools Tools with `accepts.scheme === "exact"` require [x402 payment](/protocols/x402) via `@x402/mcp`. The payment wrapper is applied automatically when you provide an `accepts` configuration during registration. ```typescript theme={null} // This tool requires $0.001 per invocation await server.withPlugin( new MCPPlugin([ { name: "premiumSearch", exports: { default: searchTool, accepts: { scheme: "exact", price: "$0.001" }, }, }, ]), ); ``` How tools are defined in the app/ directory. Payer-scoped storage for MCP tools. Agent discovery and communication via A2A. # x402 Payments Source: https://aixyz.sh/protocols/x402 HTTP 402 micropayments for agent endpoints ## Overview [x402](https://www.x402.org/) is a payment protocol for HTTP resources. When a client requests a payment-gated endpoint, the server responds with HTTP 402 and payment requirements. The client includes a payment proof in the `X-Payment` header, and the server verifies it via a facilitator before granting access. In aixyz, `AixyzApp` uses a `PaymentGateway` to provide built-in payment gating for agent and tool endpoints. ## How It Works 1. Client requests a gated endpoint (e.g., `POST /agent`) 2. Server returns **HTTP 402** with payment requirements (price, network, payTo address) 3. Client constructs payment proof and retries with `X-Payment` header 4. Server verifies payment via a facilitator and grants access ## The `accepts` Export Define payment requirements by exporting `accepts` from your agent or tool: ```typescript theme={null} import type { Accepts } from "aixyz/accepts"; export const accepts: Accepts = { scheme: "exact", price: "$0.005", }; ``` This gates the endpoint behind a \$0.005 exact payment. ### With Overrides ```typescript theme={null} export const accepts: Accepts = { scheme: "exact", price: "$0.005", network: "eip155:8453", // override config network payTo: "0x...", // override config payTo }; ``` Prices are USD strings: `"$0.005"`, `"$0.01"`, `"$0.0001"`. ## Per-Agent and Per-Tool Pricing Agents and tools can each declare their own `accepts` export, enabling granular pricing: ```typescript theme={null} // app/agent.ts — agent-level pricing export const accepts: Accepts = { scheme: "exact", price: "$0.01", }; // app/tools/premium-search.ts — tool-level pricing export const accepts: Accepts = { scheme: "exact", price: "$0.001", }; ``` * Agent `accepts` gates the A2A `/agent` endpoint * Tool `accepts` gates the tool on the MCP `/mcp` endpoint * Agents and tools **without** `accepts` are not registered on protocol endpoints ## Facilitators Payment verification is handled by a facilitator service: | Facilitator | Activation | URL | | ------------ | ---------------------------- | ------------------------------------------ | | Default | Always available | `https://x402.use-agently.com/facilitator` | | Coinbase CDP | When `CDP_API_KEY_ID` is set | Auto-configured | | Custom | Set `X402_FACILITATOR_URL` | Your custom URL | ### Custom Facilitator Create `app/accepts.ts` to provide a custom facilitator: ```typescript title="app/accepts.ts" theme={null} import { HTTPFacilitatorClient } from "aixyz/accepts"; export const facilitator = new HTTPFacilitatorClient({ url: process.env.X402_FACILITATOR_URL ?? "https://x402.use-agently.com/facilitator", }); ``` ## Server Integration In a custom server, register payment-gated routes via `route()`: ```typescript theme={null} server.route("POST", "/agent", handler, { payment: { scheme: "exact", price: "$0.005" }, }); ``` How to configure payments in your agent. On-chain agent identity for verified payments. # Fake Model Agent Source: https://aixyz.sh/templates/advanced/fake-llm Fully deterministic agent testing using the aixyz fake model — no API key required **Source:** [`examples/fake-llm`](https://github.com/AgentlyHQ/aixyz/tree/main/examples/fake-llm) ## Overview This template demonstrates how to use `fake()` from `aixyz/model` to build and test an agent with **zero API calls and no API key**. The fake model's transform function deterministically maps each user message to a response, making every test fast, repeatable, and CI-safe. The example agent checks whether the user's input is a palindrome, or reverses it if not — using both the `lastMessage` and the full `prompt` history from the transform signature. ## Project Structure ``` fake-llm/ ├── aixyz.config.ts # Agent metadata ├── app/ │ ├── agent.ts # Agent using fake() model │ └── agent.test.ts # Fully deterministic test suite ├── package.json └── vercel.json ``` ## The Fake Model Import `fake` from `aixyz/model` and pass a transform function: ```typescript title="app/agent.ts" theme={null} import { fake } from "aixyz/model"; import { ToolLoopAgent } from "ai"; import type { Accepts } from "aixyz/accepts"; import type { Capabilities } from "aixyz/app/plugins/a2a"; export const model = fake((lastMessage, prompt) => { const reversed = [...lastMessage].reverse().join(""); const isPalindrome = lastMessage.toLowerCase() === reversed.toLowerCase(); const turn = prompt.filter((m) => m.role === "user").length; if (isPalindrome) { return `"${lastMessage}" is a palindrome! (turn ${turn})`; } return `"${lastMessage}" reversed is "${reversed}" (turn ${turn})`; }); export const accepts: Accepts = { scheme: "free", }; export const capabilities: Capabilities = { streaming: false, pushNotifications: false, }; export default new ToolLoopAgent({ model, instructions: "You analyze text for palindromes and reverse it when asked.", }); ``` The transform receives: * `lastMessage` — the text content of the last user turn * `prompt` — the full conversation history, useful for tracking turn count or extracting prior context ## Test Pattern Because the fake model is deterministic, every test runs without an API key: ```typescript title="app/agent.test.ts" theme={null} import { describe, expect, test } from "bun:test"; import type { Prompt } from "aixyz/model"; import { model } from "./agent"; describe("palindrome checker (fake model)", () => { test("detects a palindrome", async () => { const prompt: Prompt = [{ role: "user", content: [{ type: "text", text: "racecar" }] }]; const result = await model.doGenerate({ prompt }); expect(result.content[0]).toEqual( expect.objectContaining({ type: "text", text: expect.stringContaining("is a palindrome") }), ); }); test("tracks turn number from prompt context", async () => { const prompt: Prompt = [ { role: "user", content: [{ type: "text", text: "hi" }] }, { role: "assistant", content: [{ type: "text", text: "..." }] }, { role: "user", content: [{ type: "text", text: "level" }] }, ]; const result = await model.doGenerate({ prompt }); expect(result.content[0]).toEqual( expect.objectContaining({ type: "text", text: expect.stringContaining("turn 2") }), ); }); }); ``` ## Key Features * **No API key** — `fake()` never makes network requests * **Fully deterministic** — same input always produces the same output * **CI-safe** — all tests run in any environment * **Full prompt context** — the transform's second argument exposes the entire conversation history ## Running Tests ```bash theme={null} cd examples/fake-llm bun install bun test ``` ## Running the Agent ```bash theme={null} cd examples/fake-llm bun run dev ``` Endpoints available at `http://localhost:3000`: | Endpoint | Protocol | Description | | ------------------------------ | -------- | --------------------- | | `/.well-known/agent-card.json` | A2A | Agent discovery card | | `/agent` | A2A | JSON-RPC task handler | # Agent with Sub-Agents Source: https://aixyz.sh/templates/advanced/sub-agents Template demonstrating multiple A2A endpoints from a single deployment using app/agents/[name].ts **Source:** [`examples/sub-agents`](https://github.com/AgentlyHQ/aixyz/tree/main/examples/sub-agents) ## Overview This template shows how to deploy **multiple specialist agents** from a single service using the `app/agents/` directory. Each file in `app/agents/` becomes an independent A2A endpoint alongside the main `app/agent.ts` coordinator. The example has three agents and two tools: * **Coordinator** (`app/agent.ts`) — routes users to the right specialist * **Math specialist** (`app/agents/math.ts`) — performs arithmetic using the `calculate` tool * **Text specialist** (`app/agents/text.ts`) — analyzes text using the `word-count` tool ## Project Structure ``` sub-agents/ ├── aixyz.config.ts # Agent metadata and skills ├── app/ │ ├── agent.ts # Coordinator agent → /agent │ ├── agents/ │ │ ├── math.ts # Math sub-agent → /math/agent │ │ └── text.ts # Text sub-agent → /text/agent │ ├── tools/ │ │ ├── calculate.ts # Arithmetic tool (add/subtract/multiply/divide) │ │ └── word-count.ts # Text analysis tool (words/characters/sentences) │ └── icon.png ├── package.json └── vercel.json ``` ## Endpoints A single deployment exposes three independent A2A endpoints: | Endpoint | Agent | Description | | ----------------------------------- | ----------- | --------------------------------- | | `/agent` | Coordinator | Routes users to specialists | | `/math/agent` | Math | Arithmetic calculations | | `/text/agent` | Text | Word / character / sentence count | | `/.well-known/agent-card.json` | Coordinator | A2A discovery card | | `/math/.well-known/agent-card.json` | Math | A2A discovery card | | `/text/.well-known/agent-card.json` | Text | A2A discovery card | | `/mcp` | — | MCP tool endpoint | ## Sub-Agent Definition Each file in `app/agents/` follows the same pattern as `app/agent.ts`: ```typescript title="app/agents/math.ts" theme={null} import { openai } from "@ai-sdk/openai"; import { stepCountIs, ToolLoopAgent } from "ai"; import type { Accepts } from "aixyz/accepts"; import calculate from "../tools/calculate"; export const accepts: Accepts = { scheme: "exact", price: "$0.001" }; export default new ToolLoopAgent({ model: openai("gpt-4o-mini"), instructions: "You are a math specialist...", tools: { calculate }, stopWhen: stepCountIs(5), }); ``` The filename (`math.ts`) determines the URL prefix (`/math/agent`). ## Key Features * **Auto-discovery** — All `.ts` files in `app/agents/` are automatically registered * **Independent endpoints** — Each sub-agent gets its own A2A endpoint and agent card * **Shared tools via MCP** — Tools in `app/tools/` are exposed on a single `/mcp` endpoint * **Mixed deployment** — `app/agent.ts` and `app/agents/` can coexist in the same project ## Running ```bash theme={null} cd examples/sub-agents bun install bun run dev ``` Agents available at `http://localhost:3000`: | Endpoint | Protocol | Description | | ----------------------------------- | -------- | ------------------------ | | `/.well-known/agent-card.json` | A2A | Coordinator discovery | | `/agent` | A2A | Coordinator JSON-RPC | | `/math/.well-known/agent-card.json` | A2A | Math sub-agent discovery | | `/math/agent` | A2A | Math sub-agent JSON-RPC | | `/text/.well-known/agent-card.json` | A2A | Text sub-agent discovery | | `/text/agent` | A2A | Text sub-agent JSON-RPC | | `/mcp` | MCP | Shared tool endpoint | # BYO Facilitator Agent Source: https://aixyz.sh/templates/advanced/with-custom-facilitator Template demonstrating a custom x402 payment facilitator **Source:** [`examples/with-custom-facilitator`](https://github.com/AgentlyHQ/aixyz/tree/main/examples/with-custom-facilitator) ## Overview This template demonstrates how to bring your own x402 payment facilitator by providing an `app/accepts.ts` file. Instead of using the default facilitator at `x402.org`, you can point to a custom facilitator URL for payment verification. ## Project Structure ``` with-custom-facilitator/ ├── aixyz.config.ts # Agent metadata ├── app/ │ ├── agent.ts # Agent with temperature tool │ ├── accepts.ts # Custom facilitator configuration │ ├── tools/ │ │ └── temperature.ts # Temperature conversion tool │ └── icon.png # Agent icon ├── package.json └── vercel.json ``` ## Custom Facilitator The `app/accepts.ts` file configures a custom payment facilitator: ```typescript title="app/accepts.ts" theme={null} import { HTTPFacilitatorClient } from "aixyz/accepts"; export const facilitator = new HTTPFacilitatorClient({ url: process.env.X402_FACILITATOR_URL ?? "https://www.x402.org/facilitator", }); ``` ## Key Features * **Custom facilitator URL** — Point to any x402-compatible facilitator service * **Environment-driven** — Facilitator URL configurable via `X402_FACILITATOR_URL` * **Fallback to default** — Uses the default x402.org facilitator if no env var is set * **Auto-generated server** — No custom `server.ts` needed; the build pipeline picks up `app/accepts.ts` automatically ## When to Use Use a custom facilitator when you need to: * Run your own x402 facilitator service for payment verification * Use a third-party facilitator other than the default * Test payments against a local or staging facilitator ## Payment The agent charges `$0.005` per request via x402 on Base Sepolia. ## Environment Variables | Variable | Description | | ---------------------- | --------------------------------------------- | | `OPENAI_API_KEY` | OpenAI API key | | `X402_FACILITATOR_URL` | Custom facilitator URL (defaults to x402.org) | ## Running ```bash theme={null} cd examples/with-custom-facilitator bun install bun run dev ``` # Custom Server Agent Source: https://aixyz.sh/templates/advanced/with-custom-server Template demonstrating manual server control with app/server.ts **Source:** [`examples/with-custom-server`](https://github.com/AgentlyHQ/aixyz/tree/main/examples/with-custom-server) ## Overview This template demonstrates how to take full control over endpoint registration by providing a custom `app/server.ts`. Instead of relying on the auto-generated server, you manually wire up A2A, MCP, and x402 — giving you complete control over pricing, middleware, and route configuration. ## Project Structure ``` with-custom-server/ ├── aixyz.config.ts # Agent metadata ├── app/ │ ├── server.ts # Custom server setup │ ├── agent.ts # Agent definition │ ├── tools/ │ │ └── lookup.ts # Example tool │ └── icon.png # Agent icon ├── package.json └── vercel.json ``` ## Custom Server The `app/server.ts` file gives you full control: ```typescript title="app/server.ts" theme={null} import { AixyzApp } from "aixyz/app"; import { IndexPagePlugin } from "aixyz/app/plugins/index-page"; import { A2APlugin } from "aixyz/app/plugins/a2a"; import { MCPPlugin } from "aixyz/app/plugins/mcp"; import * as agent from "./agent"; import lookup from "./tools/lookup"; const server = new AixyzApp(); await server.withPlugin(new IndexPagePlugin()); await server.withPlugin(new A2APlugin([{ exports: agent }])); await server.withPlugin( new MCPPlugin([ { name: "latestData", exports: { default: lookup, accepts: { scheme: "exact", price: "$0.001", }, }, }, ]), ); await server.initialize(); export default server; ``` ## Key Features * **Manual A2A setup** — `A2APlugin` registers agent card and JSON-RPC endpoint * **Manual MCP setup** — `MCPPlugin` registers tools with custom names and pricing * **Per-tool pricing** — Set different x402 prices for each MCP tool * **Index page** — `IndexPagePlugin` adds a human-readable landing page ## When to Use Use a custom server when you need to: * Register MCP tools with custom names or specific pricing * Add custom Express middleware or routes * Control the order of endpoint registration * Integrate additional services (e.g., Stripe, webhooks) ## Running ```bash theme={null} cd examples/with-custom-server bun install bun run dev ``` # Express Integration Agent Source: https://aixyz.sh/templates/advanced/with-express Template demonstrating mounting AixyzApp as Express middleware **Source:** [`examples/with-express`](https://github.com/AgentlyHQ/aixyz/tree/main/examples/with-express) ## Overview This template demonstrates how to mount an `AixyzApp` on an Express server using the `toExpressMiddleware` adapter. Your own Express routes coexist alongside A2A, MCP, and x402 endpoints without conflict. ## Project Structure ``` with-express/ ├── aixyz.config.ts # Agent metadata ├── app/ │ ├── server.ts # Express + AixyzApp setup │ ├── server.test.ts # End-to-end tests with testcontainers │ ├── agent.ts # Agent definition │ ├── accepts.ts # Custom x402 facilitator │ ├── tools/ │ │ ├── temperature.ts # Free tool │ │ └── premium-temperature.ts # Paid tool │ └── icon.png # Agent icon ├── package.json └── vercel.json ``` ## Custom Server The `app/server.ts` creates an Express app and mounts AixyzApp as middleware: ```typescript title="app/server.ts" theme={null} import express from "express"; import { AixyzApp } from "aixyz/app"; import { toExpressMiddleware } from "aixyz/app/adapters/express"; import { IndexPagePlugin } from "aixyz/app/plugins/index-page"; import { A2APlugin } from "aixyz/app/plugins/a2a"; import { MCPPlugin } from "aixyz/app/plugins/mcp"; import { facilitator } from "./accepts"; import * as agent from "./agent"; import * as convertTemperature from "./tools/temperature"; import * as premiumTemperature from "./tools/premium-temperature"; // 1. Create AixyzApp with plugins const app = new AixyzApp(facilitator ? { facilitators: facilitator } : undefined); await app.withPlugin(new IndexPagePlugin()); await app.withPlugin(new A2APlugin([{ exports: agent }])); await app.withPlugin( new MCPPlugin([ { name: "convertTemperature", exports: convertTemperature }, { name: "premiumTemperature", exports: premiumTemperature }, ]), ); await app.initialize(); // 2. Mount on Express const expressApp = express(); // Custom Express routes expressApp.get("/health", (_req, res) => res.json({ status: "ok" })); expressApp.post("/echo", express.json(), (req, res) => res.json(req.body)); // Mount AixyzApp — do NOT use express.json() before this expressApp.use(toExpressMiddleware(app)); // 3. Start server const port = parseInt(process.env.PORT || "3000", 10); expressApp.listen(port); ``` ## Key Features * **Express + AixyzApp coexistence** — Your own Express routes alongside A2A/MCP/x402 * **`toExpressMiddleware` adapter** — Bridges web-standard `Request`/`Response` to Express middleware * **Mixed tool pricing** — Free and paid tools on the same MCP endpoint * **Custom facilitator** — BYO x402 facilitator via `app/accepts.ts` * **End-to-end tests** — Full payment flow tests using testcontainers Do not apply `express.json()` or other body-parsing middleware before `toExpressMiddleware`, as it needs access to the raw request body stream. ## When to Use Use Express integration when you need to: * Add AixyzApp to an existing Express application * Mix custom REST endpoints with A2A/MCP protocol endpoints * Use Express-specific middleware for some routes ## Running ```bash theme={null} cd examples/with-express bun install bun run dev ``` # Agent with Tests Source: https://aixyz.sh/templates/advanced/with-tests Template demonstrating testing patterns for aixyz agents using bun:test **Source:** [`examples/with-tests`](https://github.com/AgentlyHQ/aixyz/tree/main/examples/with-tests) ## Overview This template demonstrates how to write both deterministic and non-deterministic tests for aixyz agents using `bun:test`. It covers unit testing agent configuration, tool registration, and end-to-end agent execution. ## Project Structure ``` with-tests/ ├── aixyz.config.ts # Agent metadata ├── app/ │ ├── agent.ts # Agent with temperature tool │ ├── agent.test.ts # Test suite │ ├── tools/ │ │ └── temperature.ts # Temperature conversion tool │ └── icon.png # Agent icon ├── package.json └── vercel.json ``` ## Test Pattern ```typescript title="app/agent.test.ts" theme={null} import { describe, expect, test } from "bun:test"; import { ToolLoopAgent } from "ai"; import { loadEnv } from "aixyz/test"; import agent, { accepts } from "./agent"; // Deterministic tests — no API calls needed test("default export is a ToolLoopAgent", () => { expect(agent).toBeInstanceOf(ToolLoopAgent); }); test("has convertTemperature tool registered", () => { expect(agent.tools).toHaveProperty("convertTemperature"); }); test("accepts config uses exact scheme", () => { expect(accepts.scheme).toBe("exact"); }); // Non-deterministic tests — require OPENAI_API_KEY describe("non deterministic agent test", () => { loadEnv(); test.skipIf(!process.env.OPENAI_API_KEY)("agent can convert temperature", async () => { const result = await agent.generate({ prompt: "convert 100 degrees celsius to fahrenheit", }); expect(result.text).toContain("212"); }); }); ``` ## Key Features * **Deterministic tests** — Validate agent type, tool registration, and config without API calls * **Non-deterministic tests** — Test actual agent execution with `test.skipIf` for missing API keys * **Environment loading** — `loadEnv()` from `aixyz/test` loads `.env` files for test runs * **CI-friendly** — Deterministic tests always run; non-deterministic tests skip gracefully ## Running Tests ```bash theme={null} cd examples/with-tests bun install bun test ``` ## Running the Agent ```bash theme={null} cd examples/with-tests bun run dev ``` # x402 Sessions Source: https://aixyz.sh/templates/advanced/x402-sessions Template demonstrating payer-scoped session storage with x402 payment identity **Source:** [`examples/x402-sessions`](https://github.com/AgentlyHQ/aixyz/tree/main/examples/x402-sessions) ## Overview This template demonstrates `SessionPlugin` -- payer-scoped key-value storage gated by x402 payment identity. Each x402 signer gets isolated storage that persists across requests. Two different payers never see each other's data. ## Project Structure ``` x402-sessions/ ├── aixyz.config.ts # Agent metadata and skills ├── app/ │ ├── session.ts # Custom session store (optional) │ ├── agent.ts # Agent definition │ ├── accepts.ts # Custom x402 facilitator │ └── tools/ │ ├── put-content.ts # Store content in session │ └── get-content.ts # Retrieve content from session ├── package.json └── vercel.json ``` ## Session Store SessionPlugin is auto-registered by the build pipeline. To customize the store, create `app/session.ts`: ```typescript title="app/session.ts" theme={null} import { defineSessionStore, InMemorySessionStore } from "aixyz/app/plugins/session"; // Use the built-in in-memory store. Replace with Redis, DB, etc. for production. export default defineSessionStore(new InMemorySessionStore()); ``` If `app/session.ts` is not present, the default `InMemorySessionStore` is used automatically. ## Using Sessions in Tools Tools access the session via `getSession()` -- no need to pass payer identity manually: ```typescript title="app/tools/put-content.ts" theme={null} import { tool } from "ai"; import { z } from "zod"; import { getSession } from "aixyz/app/plugins/session"; import type { Accepts } from "aixyz/accepts"; export default tool({ description: "Store a key-value pair in the current user's session", inputSchema: z.object({ key: z.string(), value: z.string().nullable(), }), execute: async ({ key, value }) => { const session = getSession(); if (!session) { return { success: false, error: "No authenticated signer in context" }; } if (value === null) { await session.delete(key); return { success: true, key, deleted: true }; } await session.set(key, value); return { success: true, key }; }, }); export const accepts: Accepts = { scheme: "exact", price: "$0.01" }; ``` ## Skills | Skill | Description | Price | | ------------- | ----------------------------------------------- | ------- | | `put-content` | Store key-value content in payer-scoped session | \$0.01 | | `get-content` | Retrieve stored content from session | \$0.001 | ## Running ```bash theme={null} cd examples/x402-sessions # create a .env file bun install bun run dev ``` Full API reference for SessionPlugin, Session, and SessionStore. Payment protocol that provides payer identity for sessions. # Boilerplate Agent Source: https://aixyz.sh/templates/basic/boilerplate A starter template with unit conversion tools for length, weight, and temperature **Source:** [`examples/boilerplate`](https://github.com/AgentlyHQ/aixyz/tree/main/examples/boilerplate) ## Overview The boilerplate agent is the simplest starting point for building an aixyz agent. It demonstrates a multi-skill agent with three unit conversion tools — length, weight, and temperature — using the auto-generated server pattern. ## Project Structure ``` boilerplate/ ├── aixyz.config.ts # Agent metadata and skills ├── app/ │ ├── agent.ts # Agent definition with tools │ ├── tools/ │ │ ├── length.ts # Length conversion tool │ │ ├── weight.ts # Weight conversion tool │ │ └── temperature.ts # Temperature conversion tool │ └── icon.png # Agent icon ├── package.json └── vercel.json ``` ## Skills | Skill | Description | | ------------------- | ------------------------------------------------------------------ | | Convert Length | Convert length and distance values between metric and imperial | | Convert Weight | Convert weight and mass values between metric and imperial | | Convert Temperature | Convert temperature values between Celsius, Fahrenheit, and Kelvin | ## Configuration ```typescript title="aixyz.config.ts" theme={null} const config: AixyzConfig = { name: "Unit Conversion Agent", description: "AI agent that converts values between metric, imperial, and other measurement systems.", version: "0.1.0", x402: { payTo: "0x0799872E07EA7a63c79357694504FE66EDfE4a0A", network: process.env.NODE_ENV === "production" ? "eip155:8453" : "eip155:84532", }, skills: [ { id: "convert-length", name: "Convert Length", ... }, { id: "convert-weight", name: "Convert Weight", ... }, { id: "convert-temperature", name: "Convert Temperature", ... }, ], }; ``` ## Payment The agent charges `$0.001` per request via x402 on Base (mainnet in production, Base Sepolia in development). ## Running ```bash theme={null} cd examples/boilerplate bun install bun run dev ``` Endpoints available at `http://localhost:3000`: | Endpoint | Protocol | Description | | ------------------------------ | -------- | --------------------- | | `/.well-known/agent-card.json` | A2A | Agent discovery card | | `/agent` | A2A | JSON-RPC task handler | | `/mcp` | MCP | Tool endpoint | # Chainlink Price Oracle Source: https://aixyz.sh/templates/basic/chainlink Real-time crypto price agent using Chainlink price feeds on Ethereum mainnet **Source:** [`examples/chainlink`](https://github.com/AgentlyHQ/aixyz/tree/main/examples/chainlink) ## Overview The Chainlink Price Oracle agent provides real-time cryptocurrency price data by reading Chainlink price feeds directly from Ethereum mainnet. It demonstrates on-chain data integration with an aixyz agent, gated behind x402 payments. ## Project Structure ``` chainlink/ ├── aixyz.config.ts # Agent metadata and skills ├── app/ │ ├── agent.ts # Agent with Chainlink lookup tool │ ├── tools/ │ │ └── lookup.ts # Chainlink price feed reader │ └── icon.png # Agent icon ├── package.json └── vercel.json ``` ## Skills | Skill | Description | | ---------------------- | -------------------------------------------------------------------- | | Chainlink Price Lookup | Look up real-time cryptocurrency prices in USD using Chainlink feeds | ## Configuration ```typescript title="aixyz.config.ts" theme={null} const config: AixyzConfig = { name: "Chainlink Price Oracle", description: "AI agent that provides real-time cryptocurrency price data using Chainlink price feeds on Ethereum mainnet.", version: "1.0.0", x402: { payTo: process.env.X402_PAY_TO!, network: process.env.X402_NETWORK!, }, }; ``` ## Key Features * **On-chain data** — Reads directly from Chainlink price feeds on Ethereum mainnet * **Symbol validation** — Does not assume token symbols; validates via the tool * **Environment-driven config** — Payment address and network set via environment variables * **Auto-generated server** — No custom `server.ts` needed ## Payment The agent charges `$0.01` per request via x402. Payment destination is configured via environment variables. ## Environment Variables | Variable | Description | | ---------------- | ------------------------------------- | | `OPENAI_API_KEY` | OpenAI API key | | `X402_PAY_TO` | Payment destination address | | `X402_NETWORK` | Payment network (e.g., `eip155:8453`) | ## Running ```bash theme={null} cd examples/chainlink bun install bun run dev ``` # Local LLM Agent Source: https://aixyz.sh/templates/basic/local-llm An agent powered by a local LLM running entirely in-process via WebAssembly — no API key required **Source:** [`examples/local-llm`](https://github.com/AgentlyHQ/aixyz/tree/main/examples/local-llm) ## Overview The local LLM agent runs a quantized language model entirely in-process using [Transformers.js](https://huggingface.co/docs/transformers.js) (ONNX/WebAssembly). No external server, no base URL, and no API key are required. The model is downloaded from HuggingFace Hub on first run and cached locally. ## Project Structure ``` local-llm/ ├── aixyz.config.ts # Agent metadata and skills (standalone output) ├── app/ │ ├── agent.ts # Agent definition with local LLM model │ ├── agent.test.ts # Integration test │ ├── tools/ │ │ └── temperature.ts # Temperature conversion tool │ └── icon.png # Agent icon ├── prewarm.ts # Model prewarming script (used by Docker) ├── Dockerfile # Multi-stage Docker image with prewarmed model ├── .dockerignore └── package.json ``` ## Skills | Skill | Description | | ------------------- | ------------------------------------------------------------------ | | Convert Temperature | Convert temperature values between Celsius, Fahrenheit, and Kelvin | ## Agent ```typescript title="app/agent.ts" theme={null} import { transformersJS } from "@browser-ai/transformers-js"; import { stepCountIs, ToolLoopAgent } from "ai"; export default new ToolLoopAgent({ model: transformersJS("onnx-community/Qwen2.5-1.5B-Instruct", { dtype: "q4" }), instructions: instructions, tools: { convertTemperature }, stopWhen: stepCountIs(10), }); ``` The model (`onnx-community/Qwen2.5-1.5B-Instruct`, q4 quantized) is loaded via `@browser-ai/transformers-js`, which is an official [Vercel AI SDK community provider](https://ai-sdk.dev/providers/community-providers/built-in-ai) for Transformers.js. ## Payment This agent is free (`scheme: "free"`) — no x402 payment is required to call it. ## Running locally ```bash theme={null} cd examples/local-llm bun install bun run dev ``` ## Docker deployment Because the local LLM model (\~1 GB of weights) is downloaded and prewarmed **at image build time**, the container starts serving requests immediately with no cold-start delay. ### Build the image ```bash theme={null} cd examples/local-llm docker build -t local-llm . ``` Or use the npm script: ```bash theme={null} bun run docker:build ``` ### Run the container ```bash theme={null} docker run -p 3000:3000 local-llm ``` Or: ```bash theme={null} bun run docker:run ``` Endpoints available at `http://localhost:3000`: | Endpoint | Protocol | Description | | ------------------------------ | -------- | --------------------- | | `/.well-known/agent-card.json` | A2A | Agent discovery card | | `/agent` | A2A | JSON-RPC task handler | | `/mcp` | MCP | Tool endpoint | # Templates Source: https://aixyz.sh/templates/overview Working agent templates demonstrating common patterns These templates are working examples in the [`examples/`](https://github.com/AgentlyHQ/aixyz/tree/main/examples) directory. Each follows the standard aixyz `app/` directory structure and can be run locally. ## Running a Template ```bash theme={null} cd examples/boilerplate bun install bun run dev ``` The agent starts at `http://localhost:3000` with all protocol endpoints enabled. ## Basic Standard agents using `app/agent.ts` + `app/tools/`. No custom server — the server is auto-generated by the build pipeline. Start here. Unit conversion agent with length, weight, and temperature tools. Flight search with Skyscanner API and Stripe payments. Chainlink price feeds on Ethereum mainnet. ## Advanced Custom server wiring, bring-your-own payment facilitator, and testing patterns. These templates demonstrate how to take full control of the aixyz server. Multiple A2A endpoints from one deployment using app/agents/\[name].ts. Manual server control with app/server.ts — wire A2A, MCP, and x402 yourself. Bring-your-own x402 facilitator via app/accepts.ts. Testing patterns with bun:test — deterministic and non-deterministic tests. Fully deterministic testing with `fake()` — no API key required. Mount AixyzApp as Express middleware alongside your own routes. Payer-scoped session storage with x402 payment identity.