aixyz 0.28.0 → 0.30.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.
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,20 @@
1
+ import { getAixyzConfigRuntime } from "@aixyz/config";
2
+ import { BasePlugin } from "../plugin";
3
+ import type { AixyzApp } from "../index";
4
+
5
+ /** Metadata plugin. Serves agent metadata at `/_aixyz/metadata.json`. */
6
+ export class MetadataPlugin extends BasePlugin {
7
+ readonly name = "metadata";
8
+
9
+ register(app: AixyzApp): void {
10
+ const config = getAixyzConfigRuntime();
11
+ const metadata = {
12
+ name: config.name,
13
+ description: config.description,
14
+ version: config.version,
15
+ skills: config.skills,
16
+ };
17
+
18
+ app.route("GET", "/_aixyz/metadata.json", () => Response.json(metadata));
19
+ }
20
+ }
@@ -73,7 +73,7 @@ server.use(async (request, next) => {
73
73
 
74
74
  ### `withPlugin(plugin)`
75
75
 
76
- Register a plugin. Calls `plugin.register(this)` and returns `this` for chaining:
76
+ Register a plugin. Creates a scoped `RegisterContext` that auto-tracks routes in `plugin.registeredRoutes`, calls `plugin.register(ctx)`, and returns `this` for chaining:
77
77
 
78
78
  ```typescript
79
79
  await server.withPlugin(new IndexPagePlugin());
@@ -81,7 +81,7 @@ await server.withPlugin(new IndexPagePlugin());
81
81
 
82
82
  ### `initialize()`
83
83
 
84
- Initialize payment gateway. Must be called after all routes are registered:
84
+ 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:
85
85
 
86
86
  ```typescript
87
87
  await server.initialize();
@@ -98,7 +98,7 @@ import * as agent from "./agent";
98
98
  const server = new AixyzApp();
99
99
 
100
100
  await server.withPlugin(new IndexPagePlugin());
101
- await server.withPlugin(new A2APlugin(agent));
101
+ await server.withPlugin(new A2APlugin([{ exports: agent }]));
102
102
 
103
103
  await server.initialize();
104
104
 
@@ -77,7 +77,7 @@ await server.withPlugin(new IndexPagePlugin());
77
77
  // Register Stripe before other plugins so the Stripe middleware runs first
78
78
  await server.withPlugin(new experimental_StripePaymentIntentPlugin());
79
79
 
80
- await server.withPlugin(new A2APlugin(agent));
80
+ await server.withPlugin(new A2APlugin([{ exports: agent }]));
81
81
 
82
82
  await server.initialize();
83
83
 
@@ -176,7 +176,7 @@ import * as lookup from "./tools/lookup";
176
176
  const server = new AixyzApp();
177
177
 
178
178
  await server.withPlugin(new IndexPagePlugin());
179
- await server.withPlugin(new A2APlugin(agent));
179
+ await server.withPlugin(new A2APlugin([{ exports: agent }]));
180
180
  await server.withPlugin(
181
181
  new MCPPlugin([
182
182
  {
@@ -94,7 +94,7 @@ import { A2APlugin } from "aixyz/app/plugins/a2a";
94
94
 
95
95
  // agentExports is `import * as agent from "./agent"`
96
96
  // reads `default` (the ToolLoopAgent), `accepts` (payment config), and `capabilities`
97
- await server.withPlugin(new A2APlugin(agent));
97
+ await server.withPlugin(new A2APlugin([{ exports: agent }]));
98
98
  ```
99
99
 
100
100
  This registers:
@@ -104,18 +104,22 @@ This registers:
104
104
 
105
105
  ### Sub-Agent Routing
106
106
 
107
- Pass an optional `prefix` argument to mount a sub-agent under its own path:
107
+ Pass multiple entries with a `name` to mount sub-agents under their own paths:
108
108
 
109
109
  ```typescript title="app/server.ts"
110
110
  import * as research from "./agents/research";
111
111
  import * as implement from "./agents/implement";
112
112
 
113
- await server.withPlugin(new A2APlugin(agent)); // → /agent
114
- await server.withPlugin(new A2APlugin(research, "research")); // → /research/agent
115
- await server.withPlugin(new A2APlugin(implement, "implement")); // → /implement/agent
113
+ await server.withPlugin(
114
+ new A2APlugin([
115
+ { exports: agent }, // → /agent
116
+ { name: "research", exports: research }, // → /research/agent
117
+ { name: "implement", exports: implement }, // → /implement/agent
118
+ ]),
119
+ );
116
120
  ```
117
121
 
118
- Each sub-agent gets its own agent card at `/{prefix}/.well-known/agent-card.json` and its own JSON-RPC endpoint at `/{prefix}/agent`. When using `app/agents/`, the build pipeline generates these calls automatically.
122
+ 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.
119
123
 
120
124
  ## Payment Integration
121
125
 
@@ -33,7 +33,6 @@ export default metadata;
33
33
  When this file exists, the build pipeline automatically exposes two endpoints:
34
34
 
35
35
  - `GET /_aixyz/erc-8004.json`
36
- - `GET /.well-known/erc-8004.json`
37
36
 
38
37
  These serve the full agent registration file, merging defaults from `aixyz.config.ts` (name, description, image, services).
39
38
 
@@ -42,7 +42,7 @@ import lookup from "./tools/lookup";
42
42
  const server = new AixyzApp();
43
43
 
44
44
  await server.withPlugin(new IndexPagePlugin());
45
- await server.withPlugin(new A2APlugin(agent));
45
+ await server.withPlugin(new A2APlugin([{ exports: agent }]));
46
46
  await server.withPlugin(
47
47
  new MCPPlugin([
48
48
  {
@@ -47,7 +47,7 @@ import * as premiumTemperature from "./tools/premium-temperature";
47
47
  // 1. Create AixyzApp with plugins
48
48
  const app = new AixyzApp(facilitator ? { facilitators: facilitator } : undefined);
49
49
  await app.withPlugin(new IndexPagePlugin());
50
- await app.withPlugin(new A2APlugin(agent));
50
+ await app.withPlugin(new A2APlugin([{ exports: agent }]));
51
51
  await app.withPlugin(
52
52
  new MCPPlugin([
53
53
  { name: "convertTemperature", exports: convertTemperature },
@@ -13,7 +13,7 @@ import * as premiumTemperature from "./tools/premium-temperature";
13
13
  // 1. Create AixyzApp with plugins
14
14
  const app = new AixyzApp(facilitator ? { facilitators: facilitator } : undefined);
15
15
  await app.withPlugin(new IndexPagePlugin());
16
- await app.withPlugin(new A2APlugin(agent));
16
+ await app.withPlugin(new A2APlugin([{ exports: agent }]));
17
17
  await app.withPlugin(
18
18
  new MCPPlugin([
19
19
  { name: "convertTemperature", exports: convertTemperature },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aixyz",
3
- "version": "0.28.0",
3
+ "version": "0.30.0",
4
4
  "description": "Payment-native SDK for AI Agent",
5
5
  "keywords": [
6
6
  "ai",
@@ -40,9 +40,9 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@a2a-js/sdk": "^0.3.10",
43
- "@aixyz/cli": "0.28.0",
44
- "@aixyz/config": "0.28.0",
45
- "@aixyz/erc-8004": "0.28.0",
43
+ "@aixyz/cli": "0.30.0",
44
+ "@aixyz/config": "0.30.0",
45
+ "@aixyz/erc-8004": "0.30.0",
46
46
  "@kitajs/html": "^4.2.13",
47
47
  "@modelcontextprotocol/sdk": "^1.27.1",
48
48
  "@next/env": "^16.1.6",