@rickydata/agent0-mcp 0.1.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/.env.example +17 -0
- package/Dockerfile +25 -0
- package/README.md +85 -0
- package/dist/auth/sdk-client.d.ts +47 -0
- package/dist/auth/sdk-client.js +142 -0
- package/dist/auth/sdk-client.js.map +1 -0
- package/dist/auth/token.d.ts +5 -0
- package/dist/auth/token.js +6 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/auth/wallet-derivation.d.ts +26 -0
- package/dist/auth/wallet-derivation.js +76 -0
- package/dist/auth/wallet-derivation.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +140 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/a2a.d.ts +3 -0
- package/dist/tools/a2a.js +170 -0
- package/dist/tools/a2a.js.map +1 -0
- package/dist/tools/discovery.d.ts +3 -0
- package/dist/tools/discovery.js +465 -0
- package/dist/tools/discovery.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +38 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/payments.d.ts +3 -0
- package/dist/tools/payments.js +124 -0
- package/dist/tools/payments.js.map +1 -0
- package/dist/tools/registration.d.ts +3 -0
- package/dist/tools/registration.js +324 -0
- package/dist/tools/registration.js.map +1 -0
- package/dist/tools/reputation.d.ts +3 -0
- package/dist/tools/reputation.js +147 -0
- package/dist/tools/reputation.js.map +1 -0
- package/dist/utils/chains.d.ts +10 -0
- package/dist/utils/chains.js +33 -0
- package/dist/utils/chains.js.map +1 -0
- package/dist/utils/trust-labels.d.ts +10 -0
- package/dist/utils/trust-labels.js +48 -0
- package/dist/utils/trust-labels.js.map +1 -0
- package/dist/utils/validation.d.ts +12 -0
- package/dist/utils/validation.js +19 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +32 -0
- package/src/auth/sdk-client.ts +171 -0
- package/src/auth/token.ts +19 -0
- package/src/auth/wallet-derivation.ts +91 -0
- package/src/index.ts +184 -0
- package/src/tools/a2a.ts +205 -0
- package/src/tools/discovery.ts +517 -0
- package/src/tools/index.ts +45 -0
- package/src/tools/payments.ts +146 -0
- package/src/tools/registration.ts +389 -0
- package/src/tools/reputation.ts +183 -0
- package/src/utils/chains.ts +42 -0
- package/src/utils/trust-labels.ts +53 -0
- package/src/utils/validation.ts +20 -0
- package/tests/a2a.test.ts +234 -0
- package/tests/chains.test.ts +57 -0
- package/tests/discovery.test.ts +455 -0
- package/tests/e2e.test.ts +234 -0
- package/tests/payments.test.ts +148 -0
- package/tests/registration.test.ts +313 -0
- package/tests/reputation.test.ts +231 -0
- package/tests/sdk-client.test.ts +143 -0
- package/tests/tool-router.test.ts +28 -0
- package/tests/trust-labels.test.ts +229 -0
- package/tests/validation.test.ts +132 -0
- package/tests/wallet-derivation.test.ts +109 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
// Mock agent0-sdk
|
|
4
|
+
const mockRequest = vi.fn();
|
|
5
|
+
const mockIsX402Required = vi.fn();
|
|
6
|
+
|
|
7
|
+
vi.mock("agent0-sdk", () => {
|
|
8
|
+
return {
|
|
9
|
+
SDK: vi.fn().mockImplementation(() => ({
|
|
10
|
+
request: mockRequest,
|
|
11
|
+
})),
|
|
12
|
+
isX402Required: (...args: unknown[]) => mockIsX402Required(...args),
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
import { paymentsTools, handlePaymentsTool } from "../src/tools/payments.js";
|
|
17
|
+
import { setDerivedKey, setChainId } from "../src/auth/sdk-client.js";
|
|
18
|
+
|
|
19
|
+
describe("payments tools", () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
vi.clearAllMocks();
|
|
22
|
+
setChainId(11155111);
|
|
23
|
+
setDerivedKey("0x" + "bb".repeat(32));
|
|
24
|
+
mockIsX402Required.mockReturnValue(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// ===========================================================================
|
|
28
|
+
// Tool registration
|
|
29
|
+
// ===========================================================================
|
|
30
|
+
describe("tool registration", () => {
|
|
31
|
+
it("registers 1 payment tool", () => {
|
|
32
|
+
expect(paymentsTools).toHaveLength(1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("registers x402_request", () => {
|
|
36
|
+
expect(paymentsTools.find((t) => t.name === "x402_request")).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// ===========================================================================
|
|
41
|
+
// x402_request
|
|
42
|
+
// ===========================================================================
|
|
43
|
+
describe("x402_request", () => {
|
|
44
|
+
it("returns ok for non-402 response", async () => {
|
|
45
|
+
mockRequest.mockResolvedValue({ status: 200, data: "hello" });
|
|
46
|
+
|
|
47
|
+
const result = (await handlePaymentsTool("x402_request", {
|
|
48
|
+
url: "https://api.test/data",
|
|
49
|
+
})) as { status: string; x402: boolean };
|
|
50
|
+
|
|
51
|
+
expect(result.status).toBe("ok");
|
|
52
|
+
expect(result.x402).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns payment_required when 402 and autoPay is false", async () => {
|
|
56
|
+
mockIsX402Required.mockReturnValue(true);
|
|
57
|
+
mockRequest.mockResolvedValue({
|
|
58
|
+
x402Payment: { pay: vi.fn() },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = (await handlePaymentsTool("x402_request", {
|
|
62
|
+
url: "https://paid.test/resource",
|
|
63
|
+
autoPay: false,
|
|
64
|
+
})) as { status: string; x402: boolean };
|
|
65
|
+
|
|
66
|
+
expect(result.status).toBe("payment_required");
|
|
67
|
+
expect(result.x402).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("auto-pays and returns paid result when autoPay is true", async () => {
|
|
71
|
+
const mockPay = vi.fn().mockResolvedValue({ receipt: "paid_data" });
|
|
72
|
+
mockIsX402Required.mockReturnValue(true);
|
|
73
|
+
mockRequest.mockResolvedValue({
|
|
74
|
+
x402Payment: { pay: mockPay },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const result = (await handlePaymentsTool("x402_request", {
|
|
78
|
+
url: "https://paid.test/resource",
|
|
79
|
+
autoPay: true,
|
|
80
|
+
})) as { status: string; x402: boolean; result: unknown };
|
|
81
|
+
|
|
82
|
+
expect(result.status).toBe("paid");
|
|
83
|
+
expect(result.x402).toBe(true);
|
|
84
|
+
expect(result.result).toEqual({ receipt: "paid_data" });
|
|
85
|
+
expect(mockPay).toHaveBeenCalledOnce();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("returns payment_failed when auto-pay throws", async () => {
|
|
89
|
+
const mockPay = vi.fn().mockRejectedValue(new Error("Insufficient funds"));
|
|
90
|
+
mockIsX402Required.mockReturnValue(true);
|
|
91
|
+
mockRequest.mockResolvedValue({
|
|
92
|
+
x402Payment: { pay: mockPay },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const result = (await handlePaymentsTool("x402_request", {
|
|
96
|
+
url: "https://paid.test/resource",
|
|
97
|
+
autoPay: true,
|
|
98
|
+
})) as { status: string; error: string };
|
|
99
|
+
|
|
100
|
+
expect(result.status).toBe("payment_failed");
|
|
101
|
+
expect(result.error).toContain("Insufficient funds");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("passes method and headers to SDK request", async () => {
|
|
105
|
+
mockRequest.mockResolvedValue({ data: "ok" });
|
|
106
|
+
|
|
107
|
+
await handlePaymentsTool("x402_request", {
|
|
108
|
+
url: "https://api.test/data",
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: { "X-Custom": "value" },
|
|
111
|
+
body: '{"key":"val"}',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(mockRequest).toHaveBeenCalledWith(
|
|
115
|
+
expect.objectContaining({
|
|
116
|
+
url: "https://api.test/data",
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: { "X-Custom": "value" },
|
|
119
|
+
body: { key: "val" },
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("defaults method to GET", async () => {
|
|
125
|
+
mockRequest.mockResolvedValue({ data: "ok" });
|
|
126
|
+
|
|
127
|
+
await handlePaymentsTool("x402_request", {
|
|
128
|
+
url: "https://api.test/data",
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(mockRequest).toHaveBeenCalledWith(
|
|
132
|
+
expect.objectContaining({
|
|
133
|
+
method: "GET",
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// ===========================================================================
|
|
140
|
+
// Unknown tool
|
|
141
|
+
// ===========================================================================
|
|
142
|
+
it("returns error for unknown tool", async () => {
|
|
143
|
+
const result = (await handlePaymentsTool("nonexistent", {})) as {
|
|
144
|
+
error: string;
|
|
145
|
+
};
|
|
146
|
+
expect(result.error).toContain("Unknown payments tool");
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
// Mock agent0-sdk
|
|
4
|
+
const mockChainId = vi.fn().mockResolvedValue(11155111);
|
|
5
|
+
const mockCreateAgent = vi.fn();
|
|
6
|
+
const mockLoadAgent = vi.fn();
|
|
7
|
+
|
|
8
|
+
vi.mock("agent0-sdk", () => {
|
|
9
|
+
return {
|
|
10
|
+
SDK: vi.fn().mockImplementation(() => ({
|
|
11
|
+
chainId: mockChainId,
|
|
12
|
+
createAgent: mockCreateAgent,
|
|
13
|
+
loadAgent: mockLoadAgent,
|
|
14
|
+
})),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Mock wallet-derivation
|
|
19
|
+
vi.mock("../src/auth/wallet-derivation.js", async (importOriginal) => {
|
|
20
|
+
const orig = (await importOriginal()) as Record<string, unknown>;
|
|
21
|
+
return {
|
|
22
|
+
...orig,
|
|
23
|
+
deriveWalletFromSignature: vi.fn().mockReturnValue({
|
|
24
|
+
address: "0xDerivedAddress1234",
|
|
25
|
+
privateKey: "0x" + "ab".repeat(32),
|
|
26
|
+
}),
|
|
27
|
+
verifyDerivationSignature: vi.fn().mockReturnValue(true),
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
registrationTools,
|
|
33
|
+
handleRegistrationTool,
|
|
34
|
+
} from "../src/tools/registration.js";
|
|
35
|
+
import {
|
|
36
|
+
setDerivedKey,
|
|
37
|
+
setChainId,
|
|
38
|
+
} from "../src/auth/sdk-client.js";
|
|
39
|
+
import { verifyDerivationSignature } from "../src/auth/wallet-derivation.js";
|
|
40
|
+
|
|
41
|
+
function makeAgentBuilder() {
|
|
42
|
+
const builder = {
|
|
43
|
+
setMCP: vi.fn(),
|
|
44
|
+
setA2A: vi.fn(),
|
|
45
|
+
setTrust: vi.fn(),
|
|
46
|
+
setActive: vi.fn(),
|
|
47
|
+
setMetadata: vi.fn(),
|
|
48
|
+
registerIPFS: vi.fn().mockResolvedValue({
|
|
49
|
+
waitConfirmed: vi.fn().mockResolvedValue({
|
|
50
|
+
result: { agentId: "11155111:99", agentURI: "ipfs://Qm..." },
|
|
51
|
+
receipt: { transactionHash: "0xtxhash123" },
|
|
52
|
+
}),
|
|
53
|
+
}),
|
|
54
|
+
registerOnChain: vi.fn().mockResolvedValue({
|
|
55
|
+
waitConfirmed: vi.fn().mockResolvedValue({
|
|
56
|
+
result: { agentId: "11155111:99", agentURI: "data:..." },
|
|
57
|
+
receipt: { transactionHash: "0xtxhash456" },
|
|
58
|
+
}),
|
|
59
|
+
}),
|
|
60
|
+
updateInfo: vi.fn(),
|
|
61
|
+
};
|
|
62
|
+
return builder;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe("registration tools", () => {
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
vi.clearAllMocks();
|
|
68
|
+
setChainId(11155111);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// ===========================================================================
|
|
72
|
+
// Tool registration
|
|
73
|
+
// ===========================================================================
|
|
74
|
+
describe("tool registration", () => {
|
|
75
|
+
it("registers 5 registration tools", () => {
|
|
76
|
+
expect(registrationTools).toHaveLength(5);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const expected = [
|
|
80
|
+
"configure_wallet",
|
|
81
|
+
"get_derivation_message",
|
|
82
|
+
"get_auth_status",
|
|
83
|
+
"register_agent",
|
|
84
|
+
"update_agent",
|
|
85
|
+
];
|
|
86
|
+
for (const name of expected) {
|
|
87
|
+
it(`registers ${name}`, () => {
|
|
88
|
+
expect(registrationTools.find((t) => t.name === name)).toBeDefined();
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ===========================================================================
|
|
94
|
+
// get_derivation_message
|
|
95
|
+
// ===========================================================================
|
|
96
|
+
describe("get_derivation_message", () => {
|
|
97
|
+
it("returns message and instructions", async () => {
|
|
98
|
+
const result = (await handleRegistrationTool(
|
|
99
|
+
"get_derivation_message",
|
|
100
|
+
{},
|
|
101
|
+
)) as { message: string; instructions: string };
|
|
102
|
+
|
|
103
|
+
expect(result.message).toBeTypeOf("string");
|
|
104
|
+
expect(result.message.length).toBeGreaterThan(0);
|
|
105
|
+
expect(result.instructions).toContain("personal_sign");
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// ===========================================================================
|
|
110
|
+
// get_auth_status
|
|
111
|
+
// ===========================================================================
|
|
112
|
+
describe("get_auth_status", () => {
|
|
113
|
+
it("returns status object", async () => {
|
|
114
|
+
const result = (await handleRegistrationTool(
|
|
115
|
+
"get_auth_status",
|
|
116
|
+
{},
|
|
117
|
+
)) as { hasKey: boolean; chainId: number; source: string; isReadOnly: boolean };
|
|
118
|
+
|
|
119
|
+
expect(result.chainId).toBeTypeOf("number");
|
|
120
|
+
expect(result.source).toBeTypeOf("string");
|
|
121
|
+
expect(result.isReadOnly).toBeTypeOf("boolean");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// ===========================================================================
|
|
126
|
+
// configure_wallet
|
|
127
|
+
// ===========================================================================
|
|
128
|
+
describe("configure_wallet", () => {
|
|
129
|
+
it("configures with direct private key", async () => {
|
|
130
|
+
const key = "0x" + "ab".repeat(32);
|
|
131
|
+
const result = (await handleRegistrationTool("configure_wallet", {
|
|
132
|
+
privateKey: key,
|
|
133
|
+
})) as { success: boolean; method: string };
|
|
134
|
+
|
|
135
|
+
expect(result.success).toBe(true);
|
|
136
|
+
expect(result.method).toBe("direct_key");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("rejects invalid private key format (no 0x)", async () => {
|
|
140
|
+
const result = (await handleRegistrationTool("configure_wallet", {
|
|
141
|
+
privateKey: "ab".repeat(32),
|
|
142
|
+
})) as { error: string };
|
|
143
|
+
|
|
144
|
+
expect(result.error).toContain("Invalid private key");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("rejects private key with wrong length", async () => {
|
|
148
|
+
const result = (await handleRegistrationTool("configure_wallet", {
|
|
149
|
+
privateKey: "0x1234",
|
|
150
|
+
})) as { error: string };
|
|
151
|
+
|
|
152
|
+
expect(result.error).toContain("Invalid private key");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("configures with signature derivation", async () => {
|
|
156
|
+
const sig = "0x" + "cd".repeat(65);
|
|
157
|
+
const result = (await handleRegistrationTool("configure_wallet", {
|
|
158
|
+
signature: sig,
|
|
159
|
+
signerAddress: "0xSomeAddress",
|
|
160
|
+
})) as { success: boolean; method: string; derivedAddress: string };
|
|
161
|
+
|
|
162
|
+
expect(result.success).toBe(true);
|
|
163
|
+
expect(result.method).toBe("derived_from_signature");
|
|
164
|
+
expect(result.derivedAddress).toBe("0xDerivedAddress1234");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("rejects signature when verification fails", async () => {
|
|
168
|
+
(verifyDerivationSignature as ReturnType<typeof vi.fn>).mockReturnValueOnce(false);
|
|
169
|
+
|
|
170
|
+
const result = (await handleRegistrationTool("configure_wallet", {
|
|
171
|
+
signature: "0xbadsig",
|
|
172
|
+
signerAddress: "0xWrongAddress",
|
|
173
|
+
})) as { error: string };
|
|
174
|
+
|
|
175
|
+
expect(result.error).toContain("Signature verification failed");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("sets chain only when no key or signature", async () => {
|
|
179
|
+
const result = (await handleRegistrationTool("configure_wallet", {
|
|
180
|
+
chainId: 8453,
|
|
181
|
+
})) as { success: boolean; method: string; chainId: number };
|
|
182
|
+
|
|
183
|
+
expect(result.success).toBe(true);
|
|
184
|
+
expect(result.method).toBe("chain_only");
|
|
185
|
+
expect(result.chainId).toBe(8453);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("returns error when no args provided", async () => {
|
|
189
|
+
const result = (await handleRegistrationTool("configure_wallet", {})) as {
|
|
190
|
+
error: string;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
expect(result.error).toContain("Provide either privateKey or signature");
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// ===========================================================================
|
|
198
|
+
// register_agent
|
|
199
|
+
// ===========================================================================
|
|
200
|
+
describe("register_agent", () => {
|
|
201
|
+
it("registers agent via IPFS when authenticated", async () => {
|
|
202
|
+
setDerivedKey("0x" + "ff".repeat(32));
|
|
203
|
+
const builder = makeAgentBuilder();
|
|
204
|
+
mockCreateAgent.mockReturnValue(builder);
|
|
205
|
+
|
|
206
|
+
const result = (await handleRegistrationTool("register_agent", {
|
|
207
|
+
name: "My Agent",
|
|
208
|
+
description: "A cool agent",
|
|
209
|
+
mcpEndpoint: "https://mcp.test",
|
|
210
|
+
active: true,
|
|
211
|
+
trustReputation: true,
|
|
212
|
+
})) as { success: boolean; agentId: string; txHash: string; method: string };
|
|
213
|
+
|
|
214
|
+
expect(result.success).toBe(true);
|
|
215
|
+
expect(result.agentId).toBe("11155111:99");
|
|
216
|
+
expect(result.txHash).toBe("0xtxhash123");
|
|
217
|
+
expect(result.method).toBe("ipfs");
|
|
218
|
+
expect(builder.setMCP).toHaveBeenCalledWith("https://mcp.test");
|
|
219
|
+
expect(builder.setTrust).toHaveBeenCalledWith(true, false, false);
|
|
220
|
+
expect(builder.setActive).toHaveBeenCalledWith(true);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("registers agent on-chain when method is onchain", async () => {
|
|
224
|
+
setDerivedKey("0x" + "ff".repeat(32));
|
|
225
|
+
const builder = makeAgentBuilder();
|
|
226
|
+
mockCreateAgent.mockReturnValue(builder);
|
|
227
|
+
|
|
228
|
+
const result = (await handleRegistrationTool("register_agent", {
|
|
229
|
+
name: "My Agent",
|
|
230
|
+
description: "On-chain agent",
|
|
231
|
+
registrationMethod: "onchain",
|
|
232
|
+
})) as { success: boolean; method: string; txHash: string };
|
|
233
|
+
|
|
234
|
+
expect(result.success).toBe(true);
|
|
235
|
+
expect(result.method).toBe("onchain");
|
|
236
|
+
expect(result.txHash).toBe("0xtxhash456");
|
|
237
|
+
expect(builder.registerOnChain).toHaveBeenCalled();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("sets A2A endpoint when provided", async () => {
|
|
241
|
+
setDerivedKey("0x" + "ff".repeat(32));
|
|
242
|
+
const builder = makeAgentBuilder();
|
|
243
|
+
mockCreateAgent.mockReturnValue(builder);
|
|
244
|
+
|
|
245
|
+
await handleRegistrationTool("register_agent", {
|
|
246
|
+
name: "A2A Agent",
|
|
247
|
+
description: "Has A2A",
|
|
248
|
+
a2aEndpoint: "https://a2a.test/agent.json",
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
expect(builder.setA2A).toHaveBeenCalledWith("https://a2a.test/agent.json");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("sets metadata when provided", async () => {
|
|
255
|
+
setDerivedKey("0x" + "ff".repeat(32));
|
|
256
|
+
const builder = makeAgentBuilder();
|
|
257
|
+
mockCreateAgent.mockReturnValue(builder);
|
|
258
|
+
|
|
259
|
+
await handleRegistrationTool("register_agent", {
|
|
260
|
+
name: "Meta Agent",
|
|
261
|
+
description: "Has metadata",
|
|
262
|
+
metadata: { version: "1.0" },
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(builder.setMetadata).toHaveBeenCalledWith({ version: "1.0" });
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// ===========================================================================
|
|
270
|
+
// update_agent
|
|
271
|
+
// ===========================================================================
|
|
272
|
+
describe("update_agent", () => {
|
|
273
|
+
it("returns error when agentId missing", async () => {
|
|
274
|
+
setDerivedKey("0x" + "ff".repeat(32));
|
|
275
|
+
|
|
276
|
+
const result = (await handleRegistrationTool("update_agent", {})) as {
|
|
277
|
+
error: string;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
expect(result.error).toContain("agentId is required");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("updates agent properties", async () => {
|
|
284
|
+
setDerivedKey("0x" + "ff".repeat(32));
|
|
285
|
+
const builder = makeAgentBuilder();
|
|
286
|
+
mockLoadAgent.mockResolvedValue(builder);
|
|
287
|
+
|
|
288
|
+
const result = (await handleRegistrationTool("update_agent", {
|
|
289
|
+
agentId: "11155111:42",
|
|
290
|
+
description: "Updated description",
|
|
291
|
+
mcpEndpoint: "https://new-mcp.test",
|
|
292
|
+
active: false,
|
|
293
|
+
})) as { success: boolean; agentId: string; txHash: string };
|
|
294
|
+
|
|
295
|
+
expect(result.success).toBe(true);
|
|
296
|
+
expect(result.agentId).toBe("11155111:42");
|
|
297
|
+
expect(result.txHash).toBe("0xtxhash123");
|
|
298
|
+
expect(builder.updateInfo).toHaveBeenCalled();
|
|
299
|
+
expect(builder.setMCP).toHaveBeenCalledWith("https://new-mcp.test");
|
|
300
|
+
expect(builder.setActive).toHaveBeenCalledWith(false);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// ===========================================================================
|
|
305
|
+
// Unknown tool
|
|
306
|
+
// ===========================================================================
|
|
307
|
+
it("returns error for unknown tool", async () => {
|
|
308
|
+
const result = (await handleRegistrationTool("nonexistent", {})) as {
|
|
309
|
+
error: string;
|
|
310
|
+
};
|
|
311
|
+
expect(result.error).toContain("Unknown registration tool");
|
|
312
|
+
});
|
|
313
|
+
});
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
// Mock agent0-sdk
|
|
4
|
+
vi.mock("agent0-sdk", () => {
|
|
5
|
+
const mockGiveFeedback = vi.fn().mockResolvedValue({
|
|
6
|
+
waitConfirmed: vi.fn().mockResolvedValue({
|
|
7
|
+
result: {},
|
|
8
|
+
receipt: { transactionHash: "0xfeedback_tx" },
|
|
9
|
+
}),
|
|
10
|
+
});
|
|
11
|
+
const mockRevokeFeedback = vi.fn().mockResolvedValue({
|
|
12
|
+
waitConfirmed: vi.fn().mockResolvedValue({
|
|
13
|
+
result: {},
|
|
14
|
+
receipt: { transactionHash: "0xrevoke_tx" },
|
|
15
|
+
}),
|
|
16
|
+
});
|
|
17
|
+
const mockPrepareFeedbackFile = vi.fn().mockReturnValue({ uri: "ipfs://feedback" });
|
|
18
|
+
const mockChainId = vi.fn().mockResolvedValue(11155111);
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
SDK: vi.fn().mockImplementation(() => ({
|
|
22
|
+
giveFeedback: mockGiveFeedback,
|
|
23
|
+
revokeFeedback: mockRevokeFeedback,
|
|
24
|
+
prepareFeedbackFile: mockPrepareFeedbackFile,
|
|
25
|
+
chainId: mockChainId,
|
|
26
|
+
})),
|
|
27
|
+
__mocks: { mockGiveFeedback, mockRevokeFeedback, mockPrepareFeedbackFile },
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
reputationTools,
|
|
33
|
+
handleReputationTool,
|
|
34
|
+
} from "../src/tools/reputation.js";
|
|
35
|
+
import { setDerivedKey, setChainId } from "../src/auth/sdk-client.js";
|
|
36
|
+
|
|
37
|
+
async function getMocks() {
|
|
38
|
+
const mod = await import("agent0-sdk");
|
|
39
|
+
return (mod as unknown as { __mocks: Record<string, ReturnType<typeof vi.fn>> }).__mocks;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("reputation tools", () => {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
vi.clearAllMocks();
|
|
45
|
+
setChainId(11155111);
|
|
46
|
+
// Ensure auth is available for write tests
|
|
47
|
+
setDerivedKey("0x" + "aa".repeat(32));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// ===========================================================================
|
|
51
|
+
// Tool registration
|
|
52
|
+
// ===========================================================================
|
|
53
|
+
describe("tool registration", () => {
|
|
54
|
+
it("registers 2 reputation tools", () => {
|
|
55
|
+
expect(reputationTools).toHaveLength(2);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("registers give_feedback", () => {
|
|
59
|
+
expect(reputationTools.find((t) => t.name === "give_feedback")).toBeDefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("registers revoke_feedback", () => {
|
|
63
|
+
expect(reputationTools.find((t) => t.name === "revoke_feedback")).toBeDefined();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ===========================================================================
|
|
68
|
+
// give_feedback
|
|
69
|
+
// ===========================================================================
|
|
70
|
+
describe("give_feedback", () => {
|
|
71
|
+
it("submits feedback successfully", async () => {
|
|
72
|
+
const mocks = await getMocks();
|
|
73
|
+
|
|
74
|
+
const result = (await handleReputationTool("give_feedback", {
|
|
75
|
+
agentId: "11155111:42",
|
|
76
|
+
value: 85,
|
|
77
|
+
tag1: "quality",
|
|
78
|
+
})) as { success: boolean; value: number; txHash: string; tags: string[] };
|
|
79
|
+
|
|
80
|
+
expect(result.success).toBe(true);
|
|
81
|
+
expect(result.value).toBe(85);
|
|
82
|
+
expect(result.txHash).toBe("0xfeedback_tx");
|
|
83
|
+
expect(result.tags).toContain("quality");
|
|
84
|
+
expect(mocks.mockGiveFeedback).toHaveBeenCalledWith(
|
|
85
|
+
"11155111:42",
|
|
86
|
+
85,
|
|
87
|
+
"quality",
|
|
88
|
+
undefined,
|
|
89
|
+
undefined,
|
|
90
|
+
undefined,
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("rejects value below 0", async () => {
|
|
95
|
+
const result = (await handleReputationTool("give_feedback", {
|
|
96
|
+
agentId: "11155111:42",
|
|
97
|
+
value: -1,
|
|
98
|
+
})) as { error: string };
|
|
99
|
+
|
|
100
|
+
expect(result.error).toContain("between 0 and 100");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("rejects value above 100", async () => {
|
|
104
|
+
const result = (await handleReputationTool("give_feedback", {
|
|
105
|
+
agentId: "11155111:42",
|
|
106
|
+
value: 101,
|
|
107
|
+
})) as { error: string };
|
|
108
|
+
|
|
109
|
+
expect(result.error).toContain("between 0 and 100");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("accepts boundary value 0", async () => {
|
|
113
|
+
const result = (await handleReputationTool("give_feedback", {
|
|
114
|
+
agentId: "11155111:42",
|
|
115
|
+
value: 0,
|
|
116
|
+
})) as { success: boolean; value: number };
|
|
117
|
+
|
|
118
|
+
expect(result.success).toBe(true);
|
|
119
|
+
expect(result.value).toBe(0);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("accepts boundary value 100", async () => {
|
|
123
|
+
const result = (await handleReputationTool("give_feedback", {
|
|
124
|
+
agentId: "11155111:42",
|
|
125
|
+
value: 100,
|
|
126
|
+
})) as { success: boolean; value: number };
|
|
127
|
+
|
|
128
|
+
expect(result.success).toBe(true);
|
|
129
|
+
expect(result.value).toBe(100);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("prepares feedback file when text is provided", async () => {
|
|
133
|
+
const mocks = await getMocks();
|
|
134
|
+
|
|
135
|
+
await handleReputationTool("give_feedback", {
|
|
136
|
+
agentId: "11155111:42",
|
|
137
|
+
value: 75,
|
|
138
|
+
text: "Great agent!",
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(mocks.mockPrepareFeedbackFile).toHaveBeenCalledWith({
|
|
142
|
+
text: "Great agent!",
|
|
143
|
+
mcpTool: undefined,
|
|
144
|
+
a2aSkills: undefined,
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("prepares feedback file when mcpTool is provided", async () => {
|
|
149
|
+
const mocks = await getMocks();
|
|
150
|
+
|
|
151
|
+
await handleReputationTool("give_feedback", {
|
|
152
|
+
agentId: "11155111:42",
|
|
153
|
+
value: 80,
|
|
154
|
+
mcpTool: "search",
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(mocks.mockPrepareFeedbackFile).toHaveBeenCalled();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("passes both tags", async () => {
|
|
161
|
+
const result = (await handleReputationTool("give_feedback", {
|
|
162
|
+
agentId: "11155111:42",
|
|
163
|
+
value: 90,
|
|
164
|
+
tag1: "quality",
|
|
165
|
+
tag2: "reliability",
|
|
166
|
+
})) as { tags: string[] };
|
|
167
|
+
|
|
168
|
+
expect(result.tags).toEqual(["quality", "reliability"]);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("extracts chainId from agentId", async () => {
|
|
172
|
+
const result = (await handleReputationTool("give_feedback", {
|
|
173
|
+
agentId: "8453:100",
|
|
174
|
+
value: 70,
|
|
175
|
+
})) as { chain: string };
|
|
176
|
+
|
|
177
|
+
expect(result.chain).toBe("Base");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// ===========================================================================
|
|
182
|
+
// revoke_feedback
|
|
183
|
+
// ===========================================================================
|
|
184
|
+
describe("revoke_feedback", () => {
|
|
185
|
+
it("revokes feedback successfully", async () => {
|
|
186
|
+
const mocks = await getMocks();
|
|
187
|
+
|
|
188
|
+
const result = (await handleReputationTool("revoke_feedback", {
|
|
189
|
+
agentId: "11155111:42",
|
|
190
|
+
feedbackIndex: 0,
|
|
191
|
+
})) as {
|
|
192
|
+
success: boolean;
|
|
193
|
+
feedbackIndex: number;
|
|
194
|
+
isRevoked: boolean;
|
|
195
|
+
txHash: string;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
expect(result.success).toBe(true);
|
|
199
|
+
expect(result.feedbackIndex).toBe(0);
|
|
200
|
+
expect(result.isRevoked).toBe(true);
|
|
201
|
+
expect(result.txHash).toBe("0xrevoke_tx");
|
|
202
|
+
expect(mocks.mockRevokeFeedback).toHaveBeenCalledWith("11155111:42", 0);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// ===========================================================================
|
|
207
|
+
// requireAuth guard
|
|
208
|
+
// ===========================================================================
|
|
209
|
+
describe("requireAuth guard", () => {
|
|
210
|
+
it("give_feedback includes auth info in response", async () => {
|
|
211
|
+
// Auth is set in beforeEach, so this should succeed
|
|
212
|
+
const result = (await handleReputationTool("give_feedback", {
|
|
213
|
+
agentId: "11155111:42",
|
|
214
|
+
value: 50,
|
|
215
|
+
})) as { success?: boolean; error?: string };
|
|
216
|
+
|
|
217
|
+
// Should succeed since we set derived key in beforeEach
|
|
218
|
+
expect(result.success).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// ===========================================================================
|
|
223
|
+
// Unknown tool
|
|
224
|
+
// ===========================================================================
|
|
225
|
+
it("returns error for unknown tool", async () => {
|
|
226
|
+
const result = (await handleReputationTool("nonexistent", {})) as {
|
|
227
|
+
error: string;
|
|
228
|
+
};
|
|
229
|
+
expect(result.error).toContain("Unknown reputation tool");
|
|
230
|
+
});
|
|
231
|
+
});
|