aixyz 0.27.1 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +1 -1
  2. package/app/adapters/express.ts +1 -1
  3. package/app/index.ts +22 -4
  4. package/app/plugin.ts +78 -3
  5. package/app/plugins/a2a.ts +37 -27
  6. package/app/plugins/erc-8004.ts +4 -6
  7. package/app/plugins/index-page/index.ts +6 -7
  8. package/app/plugins/mcp.ts +8 -9
  9. package/docs/api-reference/accepts.mdx +116 -0
  10. package/docs/api-reference/agent.mdx +63 -0
  11. package/docs/api-reference/agents.mdx +54 -0
  12. package/docs/api-reference/aixyz-config.mdx +105 -0
  13. package/docs/api-reference/aixyz-model-fake.mdx +111 -0
  14. package/docs/api-reference/aixyz-server.mdx +106 -0
  15. package/docs/api-reference/experimental-stripe-payment-intent-plugin.mdx +90 -0
  16. package/docs/api-reference/tools.mdx +40 -0
  17. package/docs/api-reference/unstable-with-index-page.mdx +57 -0
  18. package/docs/config/aixyz-config.mdx +92 -0
  19. package/docs/config/environment-variables.mdx +77 -0
  20. package/docs/getting-started/agent-and-tools.mdx +197 -0
  21. package/docs/getting-started/deploying.mdx +116 -0
  22. package/docs/getting-started/installation.mdx +62 -0
  23. package/docs/getting-started/payments.mdx +108 -0
  24. package/docs/getting-started/project-structure.mdx +153 -0
  25. package/docs/getting-started/testing.mdx +122 -0
  26. package/docs/getting-started/why-bun.mdx +70 -0
  27. package/docs/index.mdx +62 -0
  28. package/docs/packages/aixyz.mdx +101 -0
  29. package/docs/packages/create-aixyz-app.mdx +95 -0
  30. package/docs/protocols/a2a.mdx +135 -0
  31. package/docs/protocols/erc-8004.mdx +125 -0
  32. package/docs/protocols/mcp.mdx +87 -0
  33. package/docs/protocols/x402.mdx +108 -0
  34. package/docs/templates/advanced/fake-llm.mdx +126 -0
  35. package/docs/templates/advanced/sub-agents.mdx +97 -0
  36. package/docs/templates/advanced/with-custom-facilitator.mdx +74 -0
  37. package/docs/templates/advanced/with-custom-server.mdx +88 -0
  38. package/docs/templates/advanced/with-express.mdx +101 -0
  39. package/docs/templates/advanced/with-tests.mdx +81 -0
  40. package/docs/templates/basic/boilerplate.mdx +73 -0
  41. package/docs/templates/basic/chainlink.mdx +72 -0
  42. package/docs/templates/basic/flights-search.mdx +74 -0
  43. package/docs/templates/basic/local-llm.mdx +98 -0
  44. package/docs/templates/overview.mdx +57 -0
  45. package/examples/boilerplate/README.md +59 -0
  46. package/examples/boilerplate/aixyz.config.ts +38 -0
  47. package/examples/boilerplate/app/agent.ts +33 -0
  48. package/examples/boilerplate/app/icon.svg +6 -0
  49. package/examples/boilerplate/app/tools/length.ts +45 -0
  50. package/examples/boilerplate/app/tools/temperature.ts +39 -0
  51. package/examples/boilerplate/app/tools/weight.ts +33 -0
  52. package/examples/boilerplate/package.json +18 -0
  53. package/examples/boilerplate/tsconfig.json +10 -0
  54. package/examples/boilerplate/vercel.json +5 -0
  55. package/examples/with-custom-facilitator/README.md +64 -0
  56. package/examples/with-custom-facilitator/aixyz.config.ts +13 -0
  57. package/examples/with-custom-facilitator/app/accepts.ts +5 -0
  58. package/examples/with-custom-facilitator/app/agent.ts +17 -0
  59. package/examples/with-custom-facilitator/app/tools/temperature.ts +23 -0
  60. package/examples/with-custom-facilitator/package.json +18 -0
  61. package/examples/with-custom-facilitator/tsconfig.json +10 -0
  62. package/examples/with-custom-facilitator/vercel.json +5 -0
  63. package/examples/with-express/aixyz.config.ts +13 -0
  64. package/examples/with-express/app/accepts.ts +5 -0
  65. package/examples/with-express/app/agent.ts +19 -0
  66. package/examples/with-express/app/server.test.ts +206 -0
  67. package/examples/with-express/app/server.ts +44 -0
  68. package/examples/with-express/app/tools/premium-temperature.ts +40 -0
  69. package/examples/with-express/app/tools/temperature.ts +42 -0
  70. package/examples/with-express/package.json +25 -0
  71. package/examples/with-express/tsconfig.json +11 -0
  72. package/package.json +7 -5
package/README.md CHANGED
@@ -137,7 +137,7 @@ const server = new AixyzApp();
137
137
  await server.withPlugin(new IndexPagePlugin());
138
138
 
139
139
  // A2A: agent discovery + JSON-RPC endpoint
140
- await server.withPlugin(new A2APlugin(agent));
140
+ await server.withPlugin(new A2APlugin([{ exports: agent }]));
141
141
 
142
142
  // MCP: expose tools to MCP clients
143
143
  await server.withPlugin(
@@ -47,7 +47,7 @@ interface NodeResponse {
47
47
  * import * as myTool from "./tools/my-tool";
48
48
  *
49
49
  * const app = new AixyzApp();
50
- * await app.withPlugin(new A2APlugin(agent));
50
+ * await app.withPlugin(new A2APlugin([{ exports: agent }]));
51
51
  * await app.withPlugin(new MCPPlugin([{ name: "myTool", exports: myTool }]));
52
52
  * await app.initialize();
53
53
  *
package/app/index.ts CHANGED
@@ -5,12 +5,13 @@ import { PaymentGateway } from "./payment/payment";
5
5
  import { Network } from "@x402/core/types";
6
6
  import { getAixyzConfig } from "@aixyz/config";
7
7
  import { loadEnvConfig } from "@next/env";
8
- import { BasePlugin } from "./plugin";
8
+ import { BasePlugin, type RegisterContext, type InitializeContext } from "./plugin";
9
9
 
10
10
  // Load .env and .env.production files at runtime (local files are excluded at build time).
11
11
  loadEnvConfig(process.cwd());
12
12
 
13
13
  export { BasePlugin };
14
+ export type { RegisterContext, InitializeContext };
14
15
  export type { HttpMethod, RouteHandler, Middleware, RouteEntry };
15
16
 
16
17
  export interface AixyzAppOptions {
@@ -52,15 +53,32 @@ export class AixyzApp {
52
53
  await this.payment.initialize();
53
54
  }
54
55
 
56
+ const ctx: InitializeContext = {
57
+ routes: this.routes,
58
+ getPlugin: <T extends BasePlugin>(name: string) => this.getPlugin<T>(name),
59
+ payment: this.payment,
60
+ };
55
61
  for (const plugin of this.plugins) {
56
- await plugin.initialize?.(this);
62
+ await plugin.initialize?.(ctx);
57
63
  }
58
64
  }
59
65
 
60
- /** Register a plugin. Calls plugin.register(this) and returns this for chaining. */
66
+ /** Register a plugin with a scoped RegisterContext that auto-tracks registered routes. */
61
67
  async withPlugin<B extends BasePlugin>(plugin: B): Promise<this> {
62
68
  this.plugins.push(plugin);
63
- await plugin.register(this);
69
+ // clear registered routes for this plugin before registration, in case of hot reload or multiple registrations
70
+ plugin.registeredRoutes.clear();
71
+
72
+ const ctx: RegisterContext = {
73
+ route: (method, path, handler, options) => {
74
+ this.route(method, path, handler, options);
75
+ const key = this.getRouteKey(method, path);
76
+ const entry = this.routes.get(key);
77
+ if (entry) plugin.registeredRoutes.set(key, entry);
78
+ },
79
+ use: (mw) => this.use(mw),
80
+ };
81
+ await plugin.register?.(ctx);
64
82
  return this;
65
83
  }
66
84
 
package/app/plugin.ts CHANGED
@@ -1,7 +1,82 @@
1
- import type { AixyzApp } from "./index";
1
+ import type { HttpMethod, RouteHandler, RouteEntry, Middleware } from "./types";
2
+ import type { AcceptsX402 } from "../accepts";
3
+ import type { PaymentGateway } from "./payment/payment";
2
4
 
5
+ /**
6
+ * Scoped context passed to {@link BasePlugin.register}.
7
+ *
8
+ * Intentionally narrow — plugins can only register routes and middleware during
9
+ * the registration phase. Every `route()` call is automatically tracked in
10
+ * {@link BasePlugin.registeredRoutes}, so plugins never need to track their own routes.
11
+ *
12
+ * This follows the Rollup/Vite plugin context pattern: plugins receive a scoped
13
+ * interface rather than the full application instance.
14
+ */
15
+ export interface RegisterContext {
16
+ /** Register a route on the application. Automatically tracked in `plugin.registeredRoutes`. */
17
+ route(method: HttpMethod, path: string, handler: RouteHandler, options?: { payment?: AcceptsX402 }): void;
18
+ /** Append a middleware to the application's middleware chain. */
19
+ use(middleware: Middleware): void;
20
+ }
21
+
22
+ /**
23
+ * Context passed to {@link BasePlugin.initialize}.
24
+ *
25
+ * Available after all plugins have registered their routes. Provides read access
26
+ * to the full route table, other plugins, and the payment gateway — enabling
27
+ * cross-plugin discovery and late-binding concerns like payment wrapper setup.
28
+ *
29
+ * Route registration should happen in `register()`, not `initialize()`.
30
+ */
31
+ export interface InitializeContext {
32
+ /** Read-only view of all registered routes across all plugins. */
33
+ readonly routes: ReadonlyMap<string, RouteEntry>;
34
+ /** Find a registered plugin by name. Returns `undefined` if not found. */
35
+ getPlugin<T extends BasePlugin>(name: string): Readonly<T> | undefined;
36
+ /** Payment gateway instance, if x402 payment is configured. */
37
+ readonly payment?: PaymentGateway;
38
+ }
39
+
40
+ /**
41
+ * Base class for all aixyz plugins.
42
+ *
43
+ * Plugins extend `BasePlugin` and implement one or both lifecycle hooks:
44
+ *
45
+ * - **`register(ctx)`** — Called by {@link AixyzApp.withPlugin}. Use `ctx.route()` and
46
+ * `ctx.use()` to register routes and middleware. Routes registered here are
47
+ * automatically tracked in {@link registeredRoutes}.
48
+ *
49
+ * - **`initialize(ctx)`** — Called by {@link AixyzApp.initialize} after all plugins
50
+ * have registered. Use `ctx.routes`, `ctx.getPlugin()`, and `ctx.payment` for
51
+ * cross-plugin discovery and late-binding (e.g., payment wrappers, UI generation).
52
+ *
53
+ * Both hooks are optional — a plugin may implement only `register` (most common),
54
+ * only `initialize`, or both.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * class MyPlugin extends BasePlugin {
59
+ * readonly name = "my-plugin";
60
+ *
61
+ * register(ctx: RegisterContext) {
62
+ * ctx.route("GET", "/health", () => Response.json({ ok: true }));
63
+ * }
64
+ * }
65
+ * ```
66
+ */
3
67
  export abstract class BasePlugin {
68
+ /** Unique identifier for this plugin. Used by {@link InitializeContext.getPlugin} for discovery. */
4
69
  abstract readonly name: string;
5
- abstract register(app: AixyzApp): void | Promise<void>;
6
- initialize?(app: AixyzApp): void | Promise<void>;
70
+
71
+ /**
72
+ * Routes registered by this plugin during {@link register}.
73
+ * Populated automatically by the framework — plugins do not need to manage this.
74
+ */
75
+ readonly registeredRoutes = new Map<string, RouteEntry>();
76
+
77
+ /** Register routes and middleware. Called once by {@link AixyzApp.withPlugin}. */
78
+ register?(ctx: RegisterContext): void | Promise<void>;
79
+
80
+ /** Late initialization after all plugins have registered. Called once by {@link AixyzApp.initialize}. */
81
+ initialize?(ctx: InitializeContext): void | Promise<void>;
7
82
  }
@@ -12,8 +12,7 @@ import { AgentCard, Message, Task, TaskArtifactUpdateEvent, TaskStatusUpdateEven
12
12
  import type { ToolLoopAgent, ToolSet } from "ai";
13
13
  import { z } from "zod";
14
14
  import { getAixyzConfigRuntime } from "../../config";
15
- import { BasePlugin } from "../plugin";
16
- import type { AixyzApp } from "../index";
15
+ import { BasePlugin, type RegisterContext } from "../plugin";
17
16
  import { Accepts, AcceptsScheme } from "../../accepts";
18
17
 
19
18
  export const CapabilitiesSchema = z.object({
@@ -24,6 +23,13 @@ export const CapabilitiesSchema = z.object({
24
23
 
25
24
  export type Capabilities = z.infer<typeof CapabilitiesSchema>;
26
25
 
26
+ export interface A2AAgentEntry {
27
+ name?: string;
28
+ exports: { default: ToolLoopAgent; accepts?: Accepts; capabilities?: Capabilities };
29
+ /** Optional task store override. Defaults to a per-agent InMemoryTaskStore for isolation. */
30
+ taskStore?: TaskStore;
31
+ }
32
+
27
33
  const DEFAULT_CAPABILITIES: Capabilities = { streaming: true, pushNotifications: false };
28
34
 
29
35
  /**
@@ -148,52 +154,56 @@ export function getAgentCard(agentPath = "/agent", capabilities?: Capabilities):
148
154
  }
149
155
 
150
156
  /**
151
- * A2A protocol plugin. Registers the well-known agent card endpoint
152
- * and a JSON-RPC endpoint that delegates to the given ToolLoopAgent.
153
- * Routes are only registered if the agent exports a valid `accepts` payment config.
157
+ * A2A protocol plugin. Registers well-known agent card endpoints
158
+ * and JSON-RPC endpoints for one or more agents.
159
+ * Routes are only registered for agents that export a valid `accepts` payment config.
154
160
  */
155
- export class A2APlugin<TOOLS extends ToolSet = ToolSet> extends BasePlugin {
161
+ export class A2APlugin extends BasePlugin {
156
162
  readonly name = "a2a";
157
163
 
158
- constructor(
159
- private exports: { default: ToolLoopAgent<never, TOOLS>; accepts?: Accepts; capabilities?: Capabilities },
160
- private prefix?: string,
161
- private taskStore: TaskStore = new InMemoryTaskStore(),
162
- ) {
164
+ constructor(private agents: A2AAgentEntry[]) {
163
165
  super();
164
166
  }
165
167
 
166
- register(app: AixyzApp): void {
167
- if (this.exports.accepts) {
168
- AcceptsScheme.parse(this.exports.accepts);
168
+ register(ctx: RegisterContext): void {
169
+ for (const entry of this.agents) {
170
+ this.registerAgent(ctx, entry);
171
+ }
172
+ }
173
+
174
+ private registerAgent(ctx: RegisterContext, entry: A2AAgentEntry): void {
175
+ if (entry.exports.accepts) {
176
+ const result = AcceptsScheme.safeParse(entry.exports.accepts);
177
+ if (!result.success) {
178
+ const id = entry.name ?? "root";
179
+ throw new Error(`Invalid accepts config for agent "${id}": ${result.error.message}`);
180
+ }
169
181
  } else {
170
182
  return;
171
183
  }
172
184
 
173
- const parsed = this.exports.capabilities ? CapabilitiesSchema.safeParse(this.exports.capabilities) : undefined;
185
+ const parsed = entry.exports.capabilities ? CapabilitiesSchema.safeParse(entry.exports.capabilities) : undefined;
174
186
  const capabilities = parsed?.success ? { ...DEFAULT_CAPABILITIES, ...parsed.data } : DEFAULT_CAPABILITIES;
175
187
 
176
- const agentPath: `/${string}` = this.prefix ? `/${this.prefix}/agent` : "/agent";
177
- const wellKnownPath: `/${string}` = this.prefix
178
- ? `/${this.prefix}/.well-known/agent-card.json`
188
+ const prefix = entry.name;
189
+ const agentPath: `/${string}` = prefix ? `/${prefix}/agent` : "/agent";
190
+ const wellKnownPath: `/${string}` = prefix
191
+ ? `/${prefix}/.well-known/agent-card.json`
179
192
  : "/.well-known/agent-card.json";
180
193
 
181
- const agentExecutor = new ToolLoopAgentExecutor(this.exports.default, capabilities.streaming ?? true);
182
- const requestHandler = new DefaultRequestHandler(
183
- getAgentCard(agentPath, capabilities),
184
- this.taskStore,
185
- agentExecutor,
186
- );
194
+ const taskStore = entry.taskStore ?? new InMemoryTaskStore();
195
+ const agentExecutor = new ToolLoopAgentExecutor(entry.exports.default, capabilities.streaming ?? true);
196
+ const requestHandler = new DefaultRequestHandler(getAgentCard(agentPath, capabilities), taskStore, agentExecutor);
187
197
  const jsonRpcTransport = new JsonRpcTransportHandler(requestHandler);
188
198
 
189
199
  // Agent card — pure web-standard handler
190
- app.route("GET", wellKnownPath, async () => {
200
+ ctx.route("GET", wellKnownPath, async () => {
191
201
  const card = await requestHandler.getAgentCard();
192
202
  return Response.json(card);
193
203
  });
194
204
 
195
205
  // JSON-RPC endpoint — pure web-standard handler using JsonRpcTransportHandler
196
- app.route(
206
+ ctx.route(
197
207
  "POST",
198
208
  agentPath,
199
209
  async (request: Request) => {
@@ -240,7 +250,7 @@ export class A2APlugin<TOOLS extends ToolSet = ToolSet> extends BasePlugin {
240
250
  return Response.json(result);
241
251
  },
242
252
  {
243
- payment: this.exports.accepts.scheme === "exact" ? this.exports.accepts : undefined,
253
+ payment: entry.exports.accepts.scheme === "exact" ? entry.exports.accepts : undefined,
244
254
  },
245
255
  );
246
256
  }
@@ -6,8 +6,7 @@ import {
6
6
  ERC8004_REGISTRATION_TYPE,
7
7
  ServiceSchema,
8
8
  } from "@aixyz/erc-8004/schemas/registration";
9
- import { BasePlugin } from "../plugin";
10
- import type { AixyzApp } from "../index";
9
+ import { BasePlugin, type RegisterContext } from "../plugin";
11
10
 
12
11
  /**
13
12
  * Build an ERC-8004 agent registration file by merging user-provided data with
@@ -49,7 +48,7 @@ export function getAgentRegistrationFile(
49
48
  return withDefault.parse(data);
50
49
  }
51
50
 
52
- /** ERC-8004 identity plugin. Registers `/.well-known/erc-8004.json` and `/_aixyz/erc-8004.json` routes. */
51
+ /** ERC-8004 identity plugin. Registers the `/_aixyz/erc-8004.json` route. */
53
52
  export class ERC8004Plugin extends BasePlugin {
54
53
  readonly name = "erc-8004";
55
54
 
@@ -57,10 +56,9 @@ export class ERC8004Plugin extends BasePlugin {
57
56
  super();
58
57
  }
59
58
 
60
- register(app: AixyzApp): void {
59
+ register(ctx: RegisterContext): void {
61
60
  const file = getAgentRegistrationFile(this.exports.default, this.exports.options);
62
61
 
63
- app.route("GET", "/.well-known/erc-8004.json", () => Response.json(file));
64
- app.route("GET", "/_aixyz/erc-8004.json", () => Response.json(file));
62
+ ctx.route("GET", "/_aixyz/erc-8004.json", () => Response.json(file));
65
63
  }
66
64
  }
@@ -1,7 +1,6 @@
1
1
  import { getAixyzConfigRuntime } from "@aixyz/config";
2
2
  import { z } from "zod";
3
- import { BasePlugin } from "../../plugin";
4
- import type { AixyzApp } from "../../index";
3
+ import { BasePlugin, type RegisterContext, type InitializeContext } from "../../plugin";
5
4
  import type { MCPPlugin } from "../mcp";
6
5
  import { renderHtml } from "./html";
7
6
 
@@ -111,14 +110,14 @@ export class IndexPagePlugin extends BasePlugin {
111
110
  super();
112
111
  }
113
112
 
114
- register(app: AixyzApp): void {
113
+ register(ctx: RegisterContext): void {
115
114
  const config = getAixyzConfigRuntime();
116
115
  if (!this.path.startsWith("/")) {
117
116
  throw new Error(`Invalid path: ${this.path}. Path must start with "/"`);
118
117
  }
119
118
 
120
119
  // Default to serve markdown, else explicitly asked for HTML (which browsers do by default)
121
- app.route("GET", this.path, (request: Request) => {
120
+ ctx.route("GET", this.path, (request: Request) => {
122
121
  if (prefersHtml(request)) {
123
122
  return new Response(renderHtml(config, this.protocols), {
124
123
  headers: { "Content-Type": "text/html; charset=utf-8", Vary: "Accept" },
@@ -130,11 +129,11 @@ export class IndexPagePlugin extends BasePlugin {
130
129
  });
131
130
  }
132
131
 
133
- initialize(app: AixyzApp): void {
132
+ initialize(ctx: InitializeContext): void {
134
133
  const entrypoints: Entrypoint[] = [];
135
134
 
136
135
  // Detect A2A agents from routes (POST */agent pattern)
137
- for (const [key, entry] of app.routes) {
136
+ for (const [key, entry] of ctx.routes) {
138
137
  if (key.startsWith("POST ") && entry.path.endsWith("/agent")) {
139
138
  const prefix = entry.path.slice(1, -"/agent".length); // e.g. "" or "foo"
140
139
  const name = prefix || "agent";
@@ -148,7 +147,7 @@ export class IndexPagePlugin extends BasePlugin {
148
147
  }
149
148
 
150
149
  // Detect MCP tools from MCPPlugin
151
- const mcpPlugin = app.getPlugin<MCPPlugin>("mcp");
150
+ const mcpPlugin = ctx.getPlugin<MCPPlugin>("mcp");
152
151
  if (mcpPlugin?.registeredTools) {
153
152
  for (const tool of mcpPlugin.registeredTools) {
154
153
  let inputSchema: Record<string, unknown> | undefined;
@@ -2,8 +2,7 @@ import { type Tool } from "ai";
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
4
4
  import { createPaymentWrapper } from "@x402/mcp";
5
- import { BasePlugin } from "../plugin";
6
- import type { AixyzApp } from "../index";
5
+ import { BasePlugin, type RegisterContext, type InitializeContext } from "../plugin";
7
6
  import type { Accepts } from "../../accepts";
8
7
  import { AcceptsScheme } from "../../accepts";
9
8
  import { getAixyzConfig, getAixyzConfigRuntime } from "../../config";
@@ -53,7 +52,7 @@ export class MCPPlugin extends BasePlugin {
53
52
  return mcpServer;
54
53
  }
55
54
 
56
- async register(app: AixyzApp): Promise<void> {
55
+ async register(ctx: RegisterContext): Promise<void> {
57
56
  for (const t of this.tools) {
58
57
  if (t.exports.accepts) {
59
58
  AcceptsScheme.parse(t.exports.accepts);
@@ -74,16 +73,16 @@ export class MCPPlugin extends BasePlugin {
74
73
  return transport.handleRequest(request);
75
74
  };
76
75
 
77
- app.route("POST", "/mcp", mcpHandler);
78
- app.route("GET", "/mcp", mcpHandler);
79
- app.route("DELETE", "/mcp", mcpHandler);
76
+ ctx.route("POST", "/mcp", mcpHandler);
77
+ ctx.route("GET", "/mcp", mcpHandler);
78
+ ctx.route("DELETE", "/mcp", mcpHandler);
80
79
  }
81
80
 
82
- async initialize(app: AixyzApp): Promise<void> {
83
- if (!app.payment) return;
81
+ async initialize(ctx: InitializeContext): Promise<void> {
82
+ if (!ctx.payment) return;
84
83
 
85
84
  const config = getAixyzConfig();
86
- const resourceServer = app.payment.resourceServer;
85
+ const resourceServer = ctx.payment.resourceServer;
87
86
 
88
87
  for (const { name, accepts } of this.registeredTools) {
89
88
  if (accepts?.scheme !== "exact") continue;
@@ -0,0 +1,116 @@
1
+ ---
2
+ title: "aixyz/accepts"
3
+ description: "Payment configuration types and facilitator client for x402 gating"
4
+ ---
5
+
6
+ Types and utilities for configuring x402 payment gating on agent and tool endpoints.
7
+
8
+ ```typescript
9
+ import type { Accepts, AcceptsX402, AcceptsFree } from "aixyz/accepts";
10
+ import { HTTPFacilitatorClient, facilitator } from "aixyz/accepts";
11
+ ```
12
+
13
+ ## Types
14
+
15
+ ### `Accepts`
16
+
17
+ Union type for payment configuration:
18
+
19
+ ```typescript
20
+ type Accepts = AcceptsX402 | AcceptsFree;
21
+ ```
22
+
23
+ ### `AcceptsX402`
24
+
25
+ Requires x402 exact payment:
26
+
27
+ ```typescript
28
+ type AcceptsX402 = {
29
+ scheme: "exact";
30
+ price: string; // USD string, e.g. "$0.005"
31
+ network?: string; // CAIP-2 chain ID, overrides config
32
+ payTo?: string; // EVM address, overrides config
33
+ };
34
+ ```
35
+
36
+ | Field | Type | Required | Description |
37
+ | --------- | -------- | -------- | ------------------------------------------------------------- |
38
+ | `scheme` | `string` | Yes | Must be `"exact"` |
39
+ | `price` | `string` | Yes | USD price string (e.g. `"$0.005"`) |
40
+ | `network` | `string` | No | CAIP-2 chain ID, overrides `x402.network` from config |
41
+ | `payTo` | `string` | No | EVM address to receive payment, overrides `x402.payTo` config |
42
+
43
+ ### `AcceptsFree`
44
+
45
+ No payment required:
46
+
47
+ ```typescript
48
+ type AcceptsFree = {
49
+ scheme: "free";
50
+ };
51
+ ```
52
+
53
+ ## Exports
54
+
55
+ ### `HTTPFacilitatorClient`
56
+
57
+ Client for communicating with an x402 facilitator service. Re-exported from `@x402/core/server`.
58
+
59
+ ```typescript
60
+ import { HTTPFacilitatorClient } from "aixyz/accepts";
61
+
62
+ const client = new HTTPFacilitatorClient({
63
+ url: "https://x402.use-agently.com/facilitator",
64
+ });
65
+ ```
66
+
67
+ ### `facilitator`
68
+
69
+ The default facilitator client used by `AixyzApp`. Points to the Agently-hosted x402 facilitator.
70
+
71
+ ```typescript
72
+ import { facilitator } from "aixyz/accepts";
73
+ ```
74
+
75
+ ### `AcceptsScheme`
76
+
77
+ Zod schema for validating `Accepts` objects at runtime:
78
+
79
+ ```typescript
80
+ import { AcceptsScheme } from "aixyz/accepts";
81
+
82
+ AcceptsScheme.parse({ scheme: "exact", price: "$0.005" });
83
+ ```
84
+
85
+ ## Usage
86
+
87
+ Agents and tools declare an `accepts` export to control x402 payment gating. Endpoints without `accepts` are **not registered**.
88
+
89
+ ```typescript title="app/agent.ts"
90
+ import type { Accepts } from "aixyz/accepts";
91
+
92
+ export const accepts: Accepts = {
93
+ scheme: "exact",
94
+ price: "$0.005",
95
+ };
96
+ ```
97
+
98
+ ```typescript title="app/tools/lookup.ts"
99
+ import type { Accepts } from "aixyz/accepts";
100
+
101
+ export const accepts: Accepts = {
102
+ scheme: "free",
103
+ };
104
+ ```
105
+
106
+ ### Custom facilitator
107
+
108
+ Create `app/accepts.ts` to override the default facilitator:
109
+
110
+ ```typescript title="app/accepts.ts"
111
+ import { HTTPFacilitatorClient } from "aixyz/accepts";
112
+
113
+ export const facilitator = new HTTPFacilitatorClient({
114
+ url: process.env.X402_FACILITATOR_URL ?? "https://x402.use-agently.com/facilitator",
115
+ });
116
+ ```
@@ -0,0 +1,63 @@
1
+ ---
2
+ title: "agent.ts"
3
+ description: "Define your agent with Vercel AI SDK"
4
+ ---
5
+
6
+ The agent definition file. Must export a default `ToolLoopAgent` and optionally `accepts` for payment gating and `capabilities` for A2A agent card configuration.
7
+
8
+ ```typescript title="app/agent.ts"
9
+ import { openai } from "@ai-sdk/openai";
10
+ import { stepCountIs, ToolLoopAgent } from "ai";
11
+ import type { Accepts } from "aixyz/accepts";
12
+ import type { Capabilities } from "aixyz/app/plugins/a2a";
13
+ import weather from "./tools/weather";
14
+
15
+ export const accepts: Accepts = {
16
+ scheme: "exact",
17
+ price: "$0.005",
18
+ };
19
+
20
+ export const capabilities: Capabilities = {
21
+ streaming: false,
22
+ pushNotifications: false,
23
+ };
24
+
25
+ export default new ToolLoopAgent({
26
+ model: openai("gpt-4o-mini"),
27
+ instructions: "You are a helpful weather assistant.",
28
+ tools: { weather },
29
+ stopWhen: stepCountIs(10),
30
+ });
31
+ ```
32
+
33
+ ## Exports
34
+
35
+ | Export | Type | Required | Description |
36
+ | -------------- | --------------- | -------- | ------------------------------------------------------------------- |
37
+ | `default` | `ToolLoopAgent` | Yes | The agent instance |
38
+ | `accepts` | `Accepts` | No | Payment config — gates the A2A `/agent` route |
39
+ | `capabilities` | `Capabilities` | No | A2A capabilities — controls streaming and push notification support |
40
+
41
+ When `accepts` is exported, the `/agent` endpoint requires x402 payment. Without it, the agent is not registered on the A2A endpoint.
42
+
43
+ ## Capabilities
44
+
45
+ The optional `capabilities` export controls the A2A agent card's `capabilities` field and the executor's behavior:
46
+
47
+ ```typescript
48
+ import type { Capabilities } from "aixyz/app/plugins/a2a";
49
+
50
+ export const capabilities: Capabilities = {
51
+ streaming: false, // default: true
52
+ pushNotifications: false, // default: false
53
+ stateTransitionHistory: false, // default: undefined
54
+ };
55
+ ```
56
+
57
+ | Field | Type | Default | Description |
58
+ | ------------------------ | --------- | ----------- | ---------------------------------------------------- |
59
+ | `streaming` | `boolean` | `true` | Whether the agent streams responses via `textStream` |
60
+ | `pushNotifications` | `boolean` | `false` | Whether the agent supports push notifications |
61
+ | `stateTransitionHistory` | `boolean` | `undefined` | Whether the agent exposes state transition history |
62
+
63
+ 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.
@@ -0,0 +1,54 @@
1
+ ---
2
+ title: "agents/[name].ts"
3
+ description: "Auto-discovered sub-agent definitions for multiple A2A endpoints"
4
+ ---
5
+
6
+ Each `.ts` file in `app/agents/` is auto-discovered and registered as a sub-agent on its own A2A endpoint.
7
+
8
+ ```typescript title="app/agents/research.ts"
9
+ import { openai } from "@ai-sdk/openai";
10
+ import { stepCountIs, ToolLoopAgent } from "ai";
11
+ import type { Accepts } from "aixyz/accepts";
12
+
13
+ export const accepts: Accepts = {
14
+ scheme: "exact",
15
+ price: "$0.005",
16
+ };
17
+
18
+ export default new ToolLoopAgent({
19
+ model: openai("gpt-4o-mini"),
20
+ instructions: "You are a research assistant specialized in finding and summarizing information.",
21
+ stopWhen: stepCountIs(10),
22
+ });
23
+ ```
24
+
25
+ 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`.
26
+
27
+ ## Exports
28
+
29
+ | Export | Type | Required | Description |
30
+ | --------- | --------------- | -------- | ----------------------------------------------------------- |
31
+ | `default` | `ToolLoopAgent` | Yes | The agent instance |
32
+ | `accepts` | `Accepts` | No | Payment config — gates the sub-agent A2A endpoint with x402 |
33
+
34
+ When `accepts` is exported, the sub-agent endpoint requires x402 payment. Without it, the sub-agent is not registered.
35
+
36
+ ## Conventions
37
+
38
+ - **Auto-discovered** — All `.ts` files in `app/agents/` are registered automatically
39
+ - **Ignored files** — Files starting with `_` (e.g., `_helpers.ts`) are skipped
40
+ - **Routing** — The filename (without `.ts`) becomes the URL prefix (e.g., `research.ts` → `/research/agent`)
41
+
42
+ ## Multiple Sub-Agents
43
+
44
+ You can mix `app/agent.ts` (main agent) with `app/agents/` (sub-agents) in the same project:
45
+
46
+ ```
47
+ app/
48
+ agent.ts # → /agent
49
+ agents/
50
+ research.ts # → /research/agent
51
+ implement.ts # → /implement/agent
52
+ ```
53
+
54
+ This exposes three independent A2A endpoints from a single deployment.