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.
- package/README.md +1 -1
- package/app/adapters/express.ts +1 -1
- package/app/index.ts +22 -4
- package/app/plugin.ts +78 -3
- package/app/plugins/a2a.ts +37 -27
- package/app/plugins/erc-8004.ts +4 -6
- package/app/plugins/index-page/index.ts +6 -7
- package/app/plugins/mcp.ts +8 -9
- package/docs/api-reference/accepts.mdx +116 -0
- package/docs/api-reference/agent.mdx +63 -0
- package/docs/api-reference/agents.mdx +54 -0
- package/docs/api-reference/aixyz-config.mdx +105 -0
- package/docs/api-reference/aixyz-model-fake.mdx +111 -0
- package/docs/api-reference/aixyz-server.mdx +106 -0
- package/docs/api-reference/experimental-stripe-payment-intent-plugin.mdx +90 -0
- package/docs/api-reference/tools.mdx +40 -0
- package/docs/api-reference/unstable-with-index-page.mdx +57 -0
- package/docs/config/aixyz-config.mdx +92 -0
- package/docs/config/environment-variables.mdx +77 -0
- package/docs/getting-started/agent-and-tools.mdx +197 -0
- package/docs/getting-started/deploying.mdx +116 -0
- package/docs/getting-started/installation.mdx +62 -0
- package/docs/getting-started/payments.mdx +108 -0
- package/docs/getting-started/project-structure.mdx +153 -0
- package/docs/getting-started/testing.mdx +122 -0
- package/docs/getting-started/why-bun.mdx +70 -0
- package/docs/index.mdx +62 -0
- package/docs/packages/aixyz.mdx +101 -0
- package/docs/packages/create-aixyz-app.mdx +95 -0
- package/docs/protocols/a2a.mdx +135 -0
- package/docs/protocols/erc-8004.mdx +125 -0
- package/docs/protocols/mcp.mdx +87 -0
- package/docs/protocols/x402.mdx +108 -0
- package/docs/templates/advanced/fake-llm.mdx +126 -0
- package/docs/templates/advanced/sub-agents.mdx +97 -0
- package/docs/templates/advanced/with-custom-facilitator.mdx +74 -0
- package/docs/templates/advanced/with-custom-server.mdx +88 -0
- package/docs/templates/advanced/with-express.mdx +101 -0
- package/docs/templates/advanced/with-tests.mdx +81 -0
- package/docs/templates/basic/boilerplate.mdx +73 -0
- package/docs/templates/basic/chainlink.mdx +72 -0
- package/docs/templates/basic/flights-search.mdx +74 -0
- package/docs/templates/basic/local-llm.mdx +98 -0
- package/docs/templates/overview.mdx +57 -0
- package/examples/boilerplate/README.md +59 -0
- package/examples/boilerplate/aixyz.config.ts +38 -0
- package/examples/boilerplate/app/agent.ts +33 -0
- package/examples/boilerplate/app/icon.svg +6 -0
- package/examples/boilerplate/app/tools/length.ts +45 -0
- package/examples/boilerplate/app/tools/temperature.ts +39 -0
- package/examples/boilerplate/app/tools/weight.ts +33 -0
- package/examples/boilerplate/package.json +18 -0
- package/examples/boilerplate/tsconfig.json +10 -0
- package/examples/boilerplate/vercel.json +5 -0
- package/examples/with-custom-facilitator/README.md +64 -0
- package/examples/with-custom-facilitator/aixyz.config.ts +13 -0
- package/examples/with-custom-facilitator/app/accepts.ts +5 -0
- package/examples/with-custom-facilitator/app/agent.ts +17 -0
- package/examples/with-custom-facilitator/app/tools/temperature.ts +23 -0
- package/examples/with-custom-facilitator/package.json +18 -0
- package/examples/with-custom-facilitator/tsconfig.json +10 -0
- package/examples/with-custom-facilitator/vercel.json +5 -0
- package/examples/with-express/aixyz.config.ts +13 -0
- package/examples/with-express/app/accepts.ts +5 -0
- package/examples/with-express/app/agent.ts +19 -0
- package/examples/with-express/app/server.test.ts +206 -0
- package/examples/with-express/app/server.ts +44 -0
- package/examples/with-express/app/tools/premium-temperature.ts +40 -0
- package/examples/with-express/app/tools/temperature.ts +42 -0
- package/examples/with-express/package.json +25 -0
- package/examples/with-express/tsconfig.json +11 -0
- 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(
|
package/app/adapters/express.ts
CHANGED
|
@@ -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?.(
|
|
62
|
+
await plugin.initialize?.(ctx);
|
|
57
63
|
}
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
/** Register a plugin
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
6
|
-
|
|
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
|
}
|
package/app/plugins/a2a.ts
CHANGED
|
@@ -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
|
|
152
|
-
* and
|
|
153
|
-
* Routes are only registered
|
|
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
|
|
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(
|
|
167
|
-
|
|
168
|
-
|
|
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 =
|
|
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
|
|
177
|
-
const
|
|
178
|
-
|
|
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
|
|
182
|
-
const
|
|
183
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
253
|
+
payment: entry.exports.accepts.scheme === "exact" ? entry.exports.accepts : undefined,
|
|
244
254
|
},
|
|
245
255
|
);
|
|
246
256
|
}
|
package/app/plugins/erc-8004.ts
CHANGED
|
@@ -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
|
|
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(
|
|
59
|
+
register(ctx: RegisterContext): void {
|
|
61
60
|
const file = getAgentRegistrationFile(this.exports.default, this.exports.options);
|
|
62
61
|
|
|
63
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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;
|
package/app/plugins/mcp.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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(
|
|
83
|
-
if (!
|
|
81
|
+
async initialize(ctx: InitializeContext): Promise<void> {
|
|
82
|
+
if (!ctx.payment) return;
|
|
84
83
|
|
|
85
84
|
const config = getAixyzConfig();
|
|
86
|
-
const 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.
|