@agentwonderland/mcp 0.1.24 → 0.1.26
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/dist/core/__tests__/amount-utils.test.d.ts +1 -0
- package/dist/core/__tests__/amount-utils.test.js +11 -0
- package/dist/core/__tests__/api-client.test.d.ts +1 -0
- package/dist/core/__tests__/api-client.test.js +51 -0
- package/dist/core/__tests__/formatters.test.js +10 -0
- package/dist/core/__tests__/passes-api.test.d.ts +1 -0
- package/dist/core/__tests__/passes-api.test.js +27 -0
- package/dist/core/__tests__/payments.test.js +59 -6
- package/dist/core/__tests__/principal.test.js +41 -4
- package/dist/core/__tests__/solana-charge.test.d.ts +1 -0
- package/dist/core/__tests__/solana-charge.test.js +50 -0
- package/dist/core/__tests__/spend-policy.test.d.ts +1 -0
- package/dist/core/__tests__/spend-policy.test.js +40 -0
- package/dist/core/amount-utils.d.ts +1 -0
- package/dist/core/amount-utils.js +4 -0
- package/dist/core/api-client.d.ts +1 -0
- package/dist/core/api-client.js +8 -3
- package/dist/core/balances.d.ts +1 -0
- package/dist/core/balances.js +56 -0
- package/dist/core/base-charge.js +16 -8
- package/dist/core/config.d.ts +19 -0
- package/dist/core/config.js +22 -0
- package/dist/core/formatters.d.ts +5 -5
- package/dist/core/formatters.js +12 -8
- package/dist/core/passes.d.ts +1 -1
- package/dist/core/passes.js +5 -2
- package/dist/core/payments.d.ts +1 -0
- package/dist/core/payments.js +32 -9
- package/dist/core/principal.d.ts +3 -0
- package/dist/core/principal.js +29 -1
- package/dist/core/settings.d.ts +20 -0
- package/dist/core/settings.js +19 -0
- package/dist/core/solana-charge.d.ts +5 -0
- package/dist/core/solana-charge.js +31 -8
- package/dist/core/spend-policy.d.ts +12 -0
- package/dist/core/spend-policy.js +53 -0
- package/dist/core/tempo-charge.d.ts +7 -0
- package/dist/core/tempo-charge.js +84 -0
- package/dist/core/types.d.ts +1 -2
- package/dist/index.js +9 -5
- package/dist/prompts/index.js +4 -2
- package/dist/resources/agents.js +1 -1
- package/dist/tools/__tests__/jobs.test.d.ts +1 -0
- package/dist/tools/__tests__/jobs.test.js +71 -0
- package/dist/tools/__tests__/run.test.d.ts +1 -0
- package/dist/tools/__tests__/run.test.js +149 -0
- package/dist/tools/__tests__/solve.test.d.ts +1 -0
- package/dist/tools/__tests__/solve.test.js +158 -0
- package/dist/tools/__tests__/wallet.test.d.ts +1 -0
- package/dist/tools/__tests__/wallet.test.js +230 -0
- package/dist/tools/_payment-confirmation.js +1 -1
- package/dist/tools/agent-info.js +2 -2
- package/dist/tools/favorites.js +1 -1
- package/dist/tools/jobs.js +8 -1
- package/dist/tools/observability.d.ts +2 -0
- package/dist/tools/observability.js +20 -0
- package/dist/tools/passes.js +11 -6
- package/dist/tools/run.js +45 -29
- package/dist/tools/solve.js +53 -40
- package/dist/tools/wallet.js +58 -22
- package/package.json +2 -2
- package/src/core/__tests__/amount-utils.test.ts +13 -0
- package/src/core/__tests__/api-client.test.ts +78 -0
- package/src/core/__tests__/formatters.test.ts +12 -0
- package/src/core/__tests__/passes-api.test.ts +33 -0
- package/src/core/__tests__/payments.test.ts +79 -6
- package/src/core/__tests__/principal.test.ts +49 -4
- package/src/core/__tests__/solana-charge.test.ts +59 -0
- package/src/core/__tests__/spend-policy.test.ts +58 -0
- package/src/core/amount-utils.ts +5 -0
- package/src/core/api-client.ts +16 -3
- package/src/core/balances.ts +63 -0
- package/src/core/base-charge.ts +16 -8
- package/src/core/config.ts +45 -0
- package/src/core/formatters.ts +16 -11
- package/src/core/passes.ts +5 -2
- package/src/core/payments.ts +37 -9
- package/src/core/principal.ts +42 -1
- package/src/core/settings.ts +36 -0
- package/src/core/solana-charge.ts +45 -10
- package/src/core/spend-policy.ts +69 -0
- package/src/core/tempo-charge.ts +104 -0
- package/src/core/types.ts +1 -2
- package/src/index.ts +9 -5
- package/src/prompts/index.ts +4 -2
- package/src/resources/agents.ts +1 -1
- package/src/tools/__tests__/jobs.test.ts +89 -0
- package/src/tools/__tests__/run.test.ts +176 -0
- package/src/tools/__tests__/solve.test.ts +186 -0
- package/src/tools/__tests__/wallet.test.ts +289 -0
- package/src/tools/_payment-confirmation.ts +1 -1
- package/src/tools/agent-info.ts +2 -2
- package/src/tools/favorites.ts +1 -4
- package/src/tools/jobs.ts +10 -1
- package/src/tools/observability.ts +43 -0
- package/src/tools/passes.ts +12 -12
- package/src/tools/run.ts +50 -41
- package/src/tools/solve.ts +58 -52
- package/src/tools/wallet.ts +60 -24
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const mockApiGet = vi.fn();
|
|
3
|
+
const mockIsAuthenticated = vi.fn();
|
|
4
|
+
const mockHasWalletConfigured = vi.fn();
|
|
5
|
+
const mockGetWalletAddress = vi.fn();
|
|
6
|
+
const mockFormatRunResult = vi.fn();
|
|
7
|
+
vi.mock("../../core/api-client.js", () => ({
|
|
8
|
+
apiGet: mockApiGet,
|
|
9
|
+
}));
|
|
10
|
+
vi.mock("../../core/config.js", () => ({
|
|
11
|
+
isAuthenticated: mockIsAuthenticated,
|
|
12
|
+
}));
|
|
13
|
+
vi.mock("../../core/payments.js", () => ({
|
|
14
|
+
hasWalletConfigured: mockHasWalletConfigured,
|
|
15
|
+
getWalletAddress: mockGetWalletAddress,
|
|
16
|
+
}));
|
|
17
|
+
vi.mock("../../core/formatters.js", () => ({
|
|
18
|
+
formatRunResult: mockFormatRunResult,
|
|
19
|
+
}));
|
|
20
|
+
function flattenToolText(result) {
|
|
21
|
+
const content = result?.content ?? [];
|
|
22
|
+
return content
|
|
23
|
+
.filter((item) => item?.type === "text")
|
|
24
|
+
.map((item) => item.text ?? "")
|
|
25
|
+
.join("\n\n");
|
|
26
|
+
}
|
|
27
|
+
function makeServerHarness() {
|
|
28
|
+
const handlers = new Map();
|
|
29
|
+
return {
|
|
30
|
+
handlers,
|
|
31
|
+
server: {
|
|
32
|
+
tool(name, _description, _schema, handler) {
|
|
33
|
+
handlers.set(name, handler);
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
describe("job MCP tools", () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.resetModules();
|
|
41
|
+
vi.clearAllMocks();
|
|
42
|
+
mockIsAuthenticated.mockReturnValue(false);
|
|
43
|
+
mockHasWalletConfigured.mockReturnValue(true);
|
|
44
|
+
mockGetWalletAddress.mockResolvedValue("0xabc123");
|
|
45
|
+
mockFormatRunResult.mockReturnValue("formatted job result");
|
|
46
|
+
});
|
|
47
|
+
it("appends the wallet address when looking up a job for an unauthenticated consumer", async () => {
|
|
48
|
+
mockApiGet.mockResolvedValueOnce({ status: "completed", job_id: "job-123" });
|
|
49
|
+
const { registerJobTools } = await import("../jobs.js");
|
|
50
|
+
const harness = makeServerHarness();
|
|
51
|
+
registerJobTools(harness.server);
|
|
52
|
+
const getJob = harness.handlers.get("get_job");
|
|
53
|
+
expect(getJob).toBeDefined();
|
|
54
|
+
const result = await getJob({ job_id: "job-123" });
|
|
55
|
+
const text = flattenToolText(result);
|
|
56
|
+
expect(mockApiGet).toHaveBeenCalledWith("/jobs/job-123?wallet=0xabc123");
|
|
57
|
+
expect(text).toContain("formatted job result");
|
|
58
|
+
});
|
|
59
|
+
it("returns a processing message while the async job is still running", async () => {
|
|
60
|
+
mockApiGet.mockResolvedValueOnce({ status: "processing" });
|
|
61
|
+
const { registerJobTools } = await import("../jobs.js");
|
|
62
|
+
const harness = makeServerHarness();
|
|
63
|
+
registerJobTools(harness.server);
|
|
64
|
+
const getJob = harness.handlers.get("get_job");
|
|
65
|
+
expect(getJob).toBeDefined();
|
|
66
|
+
const result = await getJob({ job_id: "job-456" });
|
|
67
|
+
const text = flattenToolText(result);
|
|
68
|
+
expect(mockApiGet).toHaveBeenCalledWith("/jobs/job-456?wallet=0xabc123");
|
|
69
|
+
expect(text).toContain("Job job-456 is still processing");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const mockApiGet = vi.fn();
|
|
3
|
+
const mockApiPost = vi.fn();
|
|
4
|
+
const mockApiPostWithPayment = vi.fn();
|
|
5
|
+
const mockGetCompatiblePaymentMethods = vi.fn();
|
|
6
|
+
const mockHasWalletConfigured = vi.fn();
|
|
7
|
+
const mockGetConfiguredMethods = vi.fn();
|
|
8
|
+
const mockGetWalletAddress = vi.fn();
|
|
9
|
+
const mockNormalizePaymentMethod = vi.fn();
|
|
10
|
+
const mockRequiresSpendConfirmation = vi.fn();
|
|
11
|
+
const mockGetDefaultTipAmount = vi.fn();
|
|
12
|
+
const mockCanSpend = vi.fn();
|
|
13
|
+
const mockRecordSpend = vi.fn();
|
|
14
|
+
const mockRequiresPolicyConfirmation = vi.fn();
|
|
15
|
+
const mockUploadLocalFiles = vi.fn();
|
|
16
|
+
const mockStoreFeedbackToken = vi.fn();
|
|
17
|
+
const mockGetActiveCreditPack = vi.fn();
|
|
18
|
+
const mockGetCreditPackInventory = vi.fn();
|
|
19
|
+
const mockGetCreditPackProgram = vi.fn();
|
|
20
|
+
const mockGetOrCreatePendingCardSetup = vi.fn();
|
|
21
|
+
const mockFormatCardSetupBlocks = vi.fn();
|
|
22
|
+
vi.mock("../../core/api-client.js", () => ({
|
|
23
|
+
apiGet: mockApiGet,
|
|
24
|
+
apiPost: mockApiPost,
|
|
25
|
+
apiPostWithPayment: mockApiPostWithPayment,
|
|
26
|
+
}));
|
|
27
|
+
vi.mock("../../core/card-setup.js", () => ({
|
|
28
|
+
getOrCreatePendingCardSetup: mockGetOrCreatePendingCardSetup,
|
|
29
|
+
formatCardSetupBlocks: mockFormatCardSetupBlocks,
|
|
30
|
+
}));
|
|
31
|
+
vi.mock("../../core/payments.js", () => ({
|
|
32
|
+
getCompatiblePaymentMethods: mockGetCompatiblePaymentMethods,
|
|
33
|
+
getConfiguredMethods: mockGetConfiguredMethods,
|
|
34
|
+
hasWalletConfigured: mockHasWalletConfigured,
|
|
35
|
+
getWalletAddress: mockGetWalletAddress,
|
|
36
|
+
normalizePaymentMethod: mockNormalizePaymentMethod,
|
|
37
|
+
}));
|
|
38
|
+
vi.mock("../../core/config.js", () => ({
|
|
39
|
+
requiresSpendConfirmation: mockRequiresSpendConfirmation,
|
|
40
|
+
getDefaultTipAmount: mockGetDefaultTipAmount,
|
|
41
|
+
}));
|
|
42
|
+
vi.mock("../../core/spend-policy.js", () => ({
|
|
43
|
+
canSpend: mockCanSpend,
|
|
44
|
+
recordSpend: mockRecordSpend,
|
|
45
|
+
requiresPolicyConfirmation: mockRequiresPolicyConfirmation,
|
|
46
|
+
}));
|
|
47
|
+
vi.mock("../../core/file-upload.js", () => ({
|
|
48
|
+
uploadLocalFiles: mockUploadLocalFiles,
|
|
49
|
+
}));
|
|
50
|
+
vi.mock("../_token-cache.js", () => ({
|
|
51
|
+
storeFeedbackToken: mockStoreFeedbackToken,
|
|
52
|
+
}));
|
|
53
|
+
vi.mock("../../core/passes.js", () => ({
|
|
54
|
+
formatCreditPackOffer: vi.fn(),
|
|
55
|
+
getActiveCreditPack: mockGetActiveCreditPack,
|
|
56
|
+
getCreditPackInventory: mockGetCreditPackInventory,
|
|
57
|
+
getCreditPackProgram: mockGetCreditPackProgram,
|
|
58
|
+
}));
|
|
59
|
+
function flattenToolText(result) {
|
|
60
|
+
const content = result?.content ?? [];
|
|
61
|
+
return content
|
|
62
|
+
.filter((item) => item?.type === "text")
|
|
63
|
+
.map((item) => item.text ?? "")
|
|
64
|
+
.join("\n\n");
|
|
65
|
+
}
|
|
66
|
+
function makeServerHarness() {
|
|
67
|
+
const handlers = new Map();
|
|
68
|
+
return {
|
|
69
|
+
handlers,
|
|
70
|
+
server: {
|
|
71
|
+
tool(name, _description, _schema, handler) {
|
|
72
|
+
handlers.set(name, handler);
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const selectedAgent = {
|
|
78
|
+
id: "agent-1",
|
|
79
|
+
name: "MockFixedPrice",
|
|
80
|
+
pricePerRunUsd: "0.01",
|
|
81
|
+
tags: ["translation"],
|
|
82
|
+
mcpSchema: null,
|
|
83
|
+
};
|
|
84
|
+
describe("run_agent MCP tool", () => {
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
vi.resetModules();
|
|
87
|
+
vi.clearAllMocks();
|
|
88
|
+
mockHasWalletConfigured.mockReturnValue(true);
|
|
89
|
+
mockGetConfiguredMethods.mockReturnValue(["card"]);
|
|
90
|
+
mockGetCompatiblePaymentMethods.mockReturnValue(["card"]);
|
|
91
|
+
mockNormalizePaymentMethod.mockImplementation((method) => method);
|
|
92
|
+
mockRequiresSpendConfirmation.mockReturnValue(true);
|
|
93
|
+
mockGetDefaultTipAmount.mockReturnValue(0);
|
|
94
|
+
mockCanSpend.mockReturnValue({ ok: true, message: "" });
|
|
95
|
+
mockRequiresPolicyConfirmation.mockReturnValue(false);
|
|
96
|
+
mockUploadLocalFiles.mockResolvedValue({ input: { text: "hello", target_language: "es" }, uploads: [] });
|
|
97
|
+
mockGetActiveCreditPack.mockReturnValue(null);
|
|
98
|
+
mockGetCreditPackInventory.mockResolvedValue([]);
|
|
99
|
+
mockGetCreditPackProgram.mockReturnValue(null);
|
|
100
|
+
mockGetOrCreatePendingCardSetup.mockResolvedValue({ url: "https://example.com/card-setup" });
|
|
101
|
+
mockFormatCardSetupBlocks.mockReturnValue(["card setup"]);
|
|
102
|
+
mockGetWalletAddress.mockResolvedValue("0xabc");
|
|
103
|
+
mockApiGet.mockResolvedValue(selectedAgent);
|
|
104
|
+
});
|
|
105
|
+
it("quotes explicit agent execution before running", async () => {
|
|
106
|
+
const { registerRunTools } = await import("../run.js");
|
|
107
|
+
const harness = makeServerHarness();
|
|
108
|
+
registerRunTools(harness.server);
|
|
109
|
+
const runAgent = harness.handlers.get("run_agent");
|
|
110
|
+
expect(runAgent).toBeDefined();
|
|
111
|
+
const result = await runAgent({
|
|
112
|
+
agent_id: selectedAgent.id,
|
|
113
|
+
input: { text: "hello", target_language: "es" },
|
|
114
|
+
pay_with: "card",
|
|
115
|
+
});
|
|
116
|
+
const text = flattenToolText(result);
|
|
117
|
+
expect(text).toContain("Ready to run MockFixedPrice");
|
|
118
|
+
expect(text).toContain('run_agent({ agent_id: "agent-1", input: <same>, pay_with: "card", confirmed: true })');
|
|
119
|
+
expect(mockApiPostWithPayment).not.toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
it("runs the explicitly selected agent when confirmed", async () => {
|
|
122
|
+
mockApiPostWithPayment.mockResolvedValueOnce({
|
|
123
|
+
status: "success",
|
|
124
|
+
job_id: "job-1",
|
|
125
|
+
agent_id: selectedAgent.id,
|
|
126
|
+
agent_name: selectedAgent.name,
|
|
127
|
+
output: { translated_text: "hola" },
|
|
128
|
+
cost: 0.01,
|
|
129
|
+
});
|
|
130
|
+
const { registerRunTools } = await import("../run.js");
|
|
131
|
+
const harness = makeServerHarness();
|
|
132
|
+
registerRunTools(harness.server);
|
|
133
|
+
const runAgent = harness.handlers.get("run_agent");
|
|
134
|
+
expect(runAgent).toBeDefined();
|
|
135
|
+
const result = await runAgent({
|
|
136
|
+
agent_id: selectedAgent.id,
|
|
137
|
+
input: { text: "hello", target_language: "es" },
|
|
138
|
+
pay_with: "card",
|
|
139
|
+
confirmed: true,
|
|
140
|
+
});
|
|
141
|
+
const text = flattenToolText(result);
|
|
142
|
+
expect(mockApiPostWithPayment).toHaveBeenCalledWith("/agents/agent-1/run", { input: { text: "hello", target_language: "es" } }, "card");
|
|
143
|
+
expect(mockRecordSpend).toHaveBeenCalledWith("card", 0.01);
|
|
144
|
+
expect(text).toContain("translated_text");
|
|
145
|
+
expect(text).toContain("✓ MockFixedPrice");
|
|
146
|
+
expect(text).toContain("Paid: $0.01 via card");
|
|
147
|
+
expect(text).toContain("Job ID: job-1");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const mockApiGet = vi.fn();
|
|
3
|
+
const mockApiPost = vi.fn();
|
|
4
|
+
const mockApiPostWithPayment = vi.fn();
|
|
5
|
+
const mockGetCompatiblePaymentMethods = vi.fn();
|
|
6
|
+
const mockHasWalletConfigured = vi.fn();
|
|
7
|
+
const mockGetConfiguredMethods = vi.fn();
|
|
8
|
+
const mockGetAcceptedPaymentMethods = vi.fn();
|
|
9
|
+
const mockGetWalletAddress = vi.fn();
|
|
10
|
+
const mockNormalizePaymentMethod = vi.fn();
|
|
11
|
+
const mockToRegistryPaymentMethod = vi.fn();
|
|
12
|
+
const mockRequiresSpendConfirmation = vi.fn();
|
|
13
|
+
const mockGetDefaultTipAmount = vi.fn();
|
|
14
|
+
const mockCanSpend = vi.fn();
|
|
15
|
+
const mockRecordSpend = vi.fn();
|
|
16
|
+
const mockRequiresPolicyConfirmation = vi.fn();
|
|
17
|
+
const mockUploadLocalFiles = vi.fn();
|
|
18
|
+
const mockStoreFeedbackToken = vi.fn();
|
|
19
|
+
const mockGetActiveCreditPack = vi.fn();
|
|
20
|
+
const mockGetCreditPackInventory = vi.fn();
|
|
21
|
+
const mockGetCreditPackProgram = vi.fn();
|
|
22
|
+
const mockGetOrCreatePendingCardSetup = vi.fn();
|
|
23
|
+
const mockFormatCardSetupBlocks = vi.fn();
|
|
24
|
+
vi.mock("../../core/api-client.js", () => ({
|
|
25
|
+
apiGet: mockApiGet,
|
|
26
|
+
apiPost: mockApiPost,
|
|
27
|
+
apiPostWithPayment: mockApiPostWithPayment,
|
|
28
|
+
}));
|
|
29
|
+
vi.mock("../../core/card-setup.js", () => ({
|
|
30
|
+
getOrCreatePendingCardSetup: mockGetOrCreatePendingCardSetup,
|
|
31
|
+
formatCardSetupBlocks: mockFormatCardSetupBlocks,
|
|
32
|
+
}));
|
|
33
|
+
vi.mock("../../core/payments.js", () => ({
|
|
34
|
+
getCompatiblePaymentMethods: mockGetCompatiblePaymentMethods,
|
|
35
|
+
hasWalletConfigured: mockHasWalletConfigured,
|
|
36
|
+
getConfiguredMethods: mockGetConfiguredMethods,
|
|
37
|
+
getAcceptedPaymentMethods: mockGetAcceptedPaymentMethods,
|
|
38
|
+
getWalletAddress: mockGetWalletAddress,
|
|
39
|
+
normalizePaymentMethod: mockNormalizePaymentMethod,
|
|
40
|
+
toRegistryPaymentMethod: mockToRegistryPaymentMethod,
|
|
41
|
+
}));
|
|
42
|
+
vi.mock("../../core/config.js", () => ({
|
|
43
|
+
requiresSpendConfirmation: mockRequiresSpendConfirmation,
|
|
44
|
+
getDefaultTipAmount: mockGetDefaultTipAmount,
|
|
45
|
+
}));
|
|
46
|
+
vi.mock("../../core/spend-policy.js", () => ({
|
|
47
|
+
canSpend: mockCanSpend,
|
|
48
|
+
recordSpend: mockRecordSpend,
|
|
49
|
+
requiresPolicyConfirmation: mockRequiresPolicyConfirmation,
|
|
50
|
+
}));
|
|
51
|
+
vi.mock("../../core/file-upload.js", () => ({
|
|
52
|
+
uploadLocalFiles: mockUploadLocalFiles,
|
|
53
|
+
}));
|
|
54
|
+
vi.mock("../_token-cache.js", () => ({
|
|
55
|
+
storeFeedbackToken: mockStoreFeedbackToken,
|
|
56
|
+
}));
|
|
57
|
+
vi.mock("../../core/passes.js", () => ({
|
|
58
|
+
getActiveCreditPack: mockGetActiveCreditPack,
|
|
59
|
+
getCreditPackInventory: mockGetCreditPackInventory,
|
|
60
|
+
getCreditPackProgram: mockGetCreditPackProgram,
|
|
61
|
+
}));
|
|
62
|
+
function flattenToolText(result) {
|
|
63
|
+
const content = result?.content ?? [];
|
|
64
|
+
return content
|
|
65
|
+
.filter((item) => item?.type === "text")
|
|
66
|
+
.map((item) => item.text ?? "")
|
|
67
|
+
.join("\n\n");
|
|
68
|
+
}
|
|
69
|
+
function makeServerHarness() {
|
|
70
|
+
const handlers = new Map();
|
|
71
|
+
return {
|
|
72
|
+
handlers,
|
|
73
|
+
server: {
|
|
74
|
+
tool(name, _description, _schema, handler) {
|
|
75
|
+
handlers.set(name, handler);
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const selectedAgent = {
|
|
81
|
+
id: "agent-1",
|
|
82
|
+
name: "MockFixedPrice",
|
|
83
|
+
pricePerRunUsd: "0.01",
|
|
84
|
+
tags: ["translation"],
|
|
85
|
+
mcpSchema: null,
|
|
86
|
+
};
|
|
87
|
+
describe("solve MCP tool", () => {
|
|
88
|
+
beforeEach(() => {
|
|
89
|
+
vi.resetModules();
|
|
90
|
+
vi.clearAllMocks();
|
|
91
|
+
mockHasWalletConfigured.mockReturnValue(true);
|
|
92
|
+
mockGetConfiguredMethods.mockReturnValue(["card"]);
|
|
93
|
+
mockGetAcceptedPaymentMethods.mockReturnValue(["card"]);
|
|
94
|
+
mockGetCompatiblePaymentMethods.mockReturnValue(["card"]);
|
|
95
|
+
mockNormalizePaymentMethod.mockImplementation((method) => method);
|
|
96
|
+
mockToRegistryPaymentMethod.mockImplementation((method) => method);
|
|
97
|
+
mockRequiresSpendConfirmation.mockReturnValue(true);
|
|
98
|
+
mockGetDefaultTipAmount.mockReturnValue(0);
|
|
99
|
+
mockCanSpend.mockReturnValue({ ok: true, message: "" });
|
|
100
|
+
mockRequiresPolicyConfirmation.mockReturnValue(false);
|
|
101
|
+
mockUploadLocalFiles.mockResolvedValue({ input: { text: "hello", target_language: "es" }, uploads: [] });
|
|
102
|
+
mockGetActiveCreditPack.mockReturnValue(null);
|
|
103
|
+
mockGetCreditPackInventory.mockResolvedValue([]);
|
|
104
|
+
mockGetCreditPackProgram.mockReturnValue(null);
|
|
105
|
+
mockGetOrCreatePendingCardSetup.mockResolvedValue({ url: "https://example.com/card-setup" });
|
|
106
|
+
mockFormatCardSetupBlocks.mockReturnValue(["card setup"]);
|
|
107
|
+
mockGetWalletAddress.mockResolvedValue("0xabc");
|
|
108
|
+
});
|
|
109
|
+
it("falls back to a quoted confirmation flow when /solve returns a 402 challenge", async () => {
|
|
110
|
+
mockApiPost.mockRejectedValueOnce(Object.assign(new Error("Payment Required"), { status: 402 }));
|
|
111
|
+
mockApiGet.mockResolvedValueOnce([selectedAgent]);
|
|
112
|
+
const { registerSolveTools } = await import("../solve.js");
|
|
113
|
+
const harness = makeServerHarness();
|
|
114
|
+
registerSolveTools(harness.server);
|
|
115
|
+
const solve = harness.handlers.get("solve");
|
|
116
|
+
expect(solve).toBeDefined();
|
|
117
|
+
const result = await solve({
|
|
118
|
+
intent: "MockFixedPrice",
|
|
119
|
+
input: { text: "hello", target_language: "es" },
|
|
120
|
+
budget: 0.05,
|
|
121
|
+
pay_with: "card",
|
|
122
|
+
});
|
|
123
|
+
const text = flattenToolText(result);
|
|
124
|
+
expect(text).toContain("Best match: MockFixedPrice");
|
|
125
|
+
expect(text).toContain('solve({ intent: "MockFixedPrice"');
|
|
126
|
+
expect(text).toContain('pay_with: "card"');
|
|
127
|
+
expect(mockApiPostWithPayment).not.toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
it("falls back to paid execution when confirmed after a 402 challenge", async () => {
|
|
130
|
+
mockApiPost.mockRejectedValueOnce(Object.assign(new Error("Payment Required"), { status: 402 }));
|
|
131
|
+
mockApiGet.mockResolvedValueOnce([selectedAgent]);
|
|
132
|
+
mockApiPostWithPayment.mockResolvedValueOnce({
|
|
133
|
+
status: "success",
|
|
134
|
+
job_id: "job-1",
|
|
135
|
+
agent_id: selectedAgent.id,
|
|
136
|
+
agent_name: selectedAgent.name,
|
|
137
|
+
output: { translated_text: "hola" },
|
|
138
|
+
cost: 0.01,
|
|
139
|
+
});
|
|
140
|
+
const { registerSolveTools } = await import("../solve.js");
|
|
141
|
+
const harness = makeServerHarness();
|
|
142
|
+
registerSolveTools(harness.server);
|
|
143
|
+
const solve = harness.handlers.get("solve");
|
|
144
|
+
expect(solve).toBeDefined();
|
|
145
|
+
const result = await solve({
|
|
146
|
+
intent: "MockFixedPrice",
|
|
147
|
+
input: { text: "hello", target_language: "es" },
|
|
148
|
+
budget: 0.05,
|
|
149
|
+
pay_with: "card",
|
|
150
|
+
confirmed: true,
|
|
151
|
+
});
|
|
152
|
+
const text = flattenToolText(result);
|
|
153
|
+
expect(mockApiPostWithPayment).toHaveBeenCalledWith("/agents/agent-1/run", { input: { text: "hello", target_language: "es" } }, "card");
|
|
154
|
+
expect(text).toContain("Running MockFixedPrice — best match");
|
|
155
|
+
expect(text).toContain("Job ID: job-1");
|
|
156
|
+
expect(text).toContain("Paid: $0.01 via card");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const state = vi.hoisted(() => ({
|
|
3
|
+
wallets: [],
|
|
4
|
+
addedWallets: [],
|
|
5
|
+
card: null,
|
|
6
|
+
pendingCardSetupToken: null,
|
|
7
|
+
spendPolicies: {},
|
|
8
|
+
consumerPrincipal: "did:pkh:eip155:8453:0xabc",
|
|
9
|
+
owsAvailable: true,
|
|
10
|
+
owsWallets: [],
|
|
11
|
+
owsWalletsByChain: [],
|
|
12
|
+
createdWalletCalls: [],
|
|
13
|
+
importWalletCalls: [],
|
|
14
|
+
createdWalletResult: {
|
|
15
|
+
walletId: "ows-wallet-created",
|
|
16
|
+
address: "0x1111111111111111111111111111111111111111",
|
|
17
|
+
},
|
|
18
|
+
importWalletResult: {
|
|
19
|
+
walletId: "ows-wallet-imported",
|
|
20
|
+
address: "0x2222222222222222222222222222222222222222",
|
|
21
|
+
},
|
|
22
|
+
cardSetup: {
|
|
23
|
+
url: "https://api.agentwonderland.com/card/handoff/setup-token",
|
|
24
|
+
token: "setup-token",
|
|
25
|
+
isNew: true,
|
|
26
|
+
},
|
|
27
|
+
cardSetupBlocks: [
|
|
28
|
+
"Open this setup page to connect your card:\n\nhttps://api.agentwonderland.com/card/handoff/setup-token",
|
|
29
|
+
],
|
|
30
|
+
pollCardSetupCalls: [],
|
|
31
|
+
pollCardSetupResult: null,
|
|
32
|
+
setCardConfigCalls: [],
|
|
33
|
+
}));
|
|
34
|
+
vi.mock("../../core/config.js", () => ({
|
|
35
|
+
addWallet: (wallet) => {
|
|
36
|
+
state.addedWallets.push(wallet);
|
|
37
|
+
const existing = state.wallets.findIndex((entry) => entry.id === wallet.id);
|
|
38
|
+
if (existing >= 0) {
|
|
39
|
+
state.wallets[existing] = wallet;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
state.wallets.push(wallet);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
getCardConfig: () => state.card,
|
|
46
|
+
getPendingCardSetupToken: () => state.pendingCardSetupToken,
|
|
47
|
+
getSpendPolicy: (walletId) => state.spendPolicies[walletId] ?? null,
|
|
48
|
+
getWallets: () => state.wallets,
|
|
49
|
+
setCardConfig: (card) => {
|
|
50
|
+
state.card = card;
|
|
51
|
+
state.setCardConfigCalls.push(card);
|
|
52
|
+
},
|
|
53
|
+
setSpendPolicy: (walletId, policy) => {
|
|
54
|
+
state.spendPolicies[walletId] = policy;
|
|
55
|
+
},
|
|
56
|
+
}));
|
|
57
|
+
vi.mock("../../core/payments.js", () => ({
|
|
58
|
+
getWalletAddress: async () => null,
|
|
59
|
+
}));
|
|
60
|
+
vi.mock("../../core/card-setup.js", () => ({
|
|
61
|
+
formatCardSetupBlocks: () => state.cardSetupBlocks,
|
|
62
|
+
getCardCapabilities: async () => ({
|
|
63
|
+
spt_status: "enabled",
|
|
64
|
+
}),
|
|
65
|
+
getOrCreatePendingCardSetup: async () => state.cardSetup,
|
|
66
|
+
pollCardSetup: async (token, timeoutMs) => {
|
|
67
|
+
state.pollCardSetupCalls.push({ token, timeoutMs });
|
|
68
|
+
return state.pollCardSetupResult;
|
|
69
|
+
},
|
|
70
|
+
}));
|
|
71
|
+
vi.mock("../../core/ows-adapter.js", () => ({
|
|
72
|
+
createOwsWallet: async (name, chain) => {
|
|
73
|
+
state.createdWalletCalls.push({ name, chain });
|
|
74
|
+
return state.createdWalletResult;
|
|
75
|
+
},
|
|
76
|
+
importKeyToOws: async (privateKey, name, chain) => {
|
|
77
|
+
state.importWalletCalls.push({ privateKey, name, chain });
|
|
78
|
+
return state.importWalletResult;
|
|
79
|
+
},
|
|
80
|
+
isOwsAvailable: async () => state.owsAvailable,
|
|
81
|
+
listOwsWallets: async () => state.owsWallets,
|
|
82
|
+
listOwsWalletsByChain: async () => state.owsWalletsByChain,
|
|
83
|
+
}));
|
|
84
|
+
vi.mock("../../core/principal.js", () => ({
|
|
85
|
+
ensureConsumerPrincipal: async () => state.consumerPrincipal,
|
|
86
|
+
getConsumerPrincipal: async () => state.consumerPrincipal,
|
|
87
|
+
}));
|
|
88
|
+
function resetState() {
|
|
89
|
+
state.wallets = [];
|
|
90
|
+
state.addedWallets = [];
|
|
91
|
+
state.card = null;
|
|
92
|
+
state.pendingCardSetupToken = null;
|
|
93
|
+
state.spendPolicies = {};
|
|
94
|
+
state.consumerPrincipal = "did:pkh:eip155:8453:0xabc";
|
|
95
|
+
state.owsAvailable = true;
|
|
96
|
+
state.owsWallets = [];
|
|
97
|
+
state.owsWalletsByChain = [];
|
|
98
|
+
state.createdWalletCalls = [];
|
|
99
|
+
state.importWalletCalls = [];
|
|
100
|
+
state.createdWalletResult = {
|
|
101
|
+
walletId: "ows-wallet-created",
|
|
102
|
+
address: "0x1111111111111111111111111111111111111111",
|
|
103
|
+
};
|
|
104
|
+
state.importWalletResult = {
|
|
105
|
+
walletId: "ows-wallet-imported",
|
|
106
|
+
address: "0x2222222222222222222222222222222222222222",
|
|
107
|
+
};
|
|
108
|
+
state.cardSetup = {
|
|
109
|
+
url: "https://api.agentwonderland.com/card/handoff/setup-token",
|
|
110
|
+
token: "setup-token",
|
|
111
|
+
isNew: true,
|
|
112
|
+
};
|
|
113
|
+
state.cardSetupBlocks = [
|
|
114
|
+
"Open this setup page to connect your card:\n\nhttps://api.agentwonderland.com/card/handoff/setup-token",
|
|
115
|
+
];
|
|
116
|
+
state.pollCardSetupCalls = [];
|
|
117
|
+
state.pollCardSetupResult = null;
|
|
118
|
+
state.setCardConfigCalls = [];
|
|
119
|
+
}
|
|
120
|
+
async function getWalletSetupTool() {
|
|
121
|
+
const tools = new Map();
|
|
122
|
+
const server = {
|
|
123
|
+
tool: (name, _description, _schema, handler) => {
|
|
124
|
+
tools.set(name, handler);
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
const { registerWalletTools } = await import("../wallet.js");
|
|
128
|
+
registerWalletTools(server);
|
|
129
|
+
const walletSetup = tools.get("wallet_setup");
|
|
130
|
+
if (!walletSetup) {
|
|
131
|
+
throw new Error("wallet_setup tool was not registered");
|
|
132
|
+
}
|
|
133
|
+
return walletSetup;
|
|
134
|
+
}
|
|
135
|
+
function flattenText(result) {
|
|
136
|
+
return result.content.map((item) => item.text).join("\n");
|
|
137
|
+
}
|
|
138
|
+
describe("wallet_setup tool", () => {
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
vi.resetModules();
|
|
141
|
+
resetState();
|
|
142
|
+
});
|
|
143
|
+
it("creates an encrypted OWS wallet for Tempo/Base with Base as the default chain", async () => {
|
|
144
|
+
const walletSetup = await getWalletSetupTool();
|
|
145
|
+
const result = await walletSetup({ action: "create", name: "launch-wallet", chain: "base" });
|
|
146
|
+
const text = flattenText(result);
|
|
147
|
+
expect(state.createdWalletCalls).toEqual([
|
|
148
|
+
{ name: "launch-wallet", chain: "evm" },
|
|
149
|
+
]);
|
|
150
|
+
expect(state.addedWallets).toEqual([
|
|
151
|
+
{
|
|
152
|
+
id: "launch-wallet",
|
|
153
|
+
keyType: "ows",
|
|
154
|
+
owsWalletId: "ows-wallet-created",
|
|
155
|
+
chains: ["tempo", "base"],
|
|
156
|
+
defaultChain: "base",
|
|
157
|
+
label: "launch-wallet",
|
|
158
|
+
},
|
|
159
|
+
]);
|
|
160
|
+
expect(text).toContain("Wallet created [encrypted]:");
|
|
161
|
+
expect(text).toContain("Address: 0x1111111111111111111111111111111111111111");
|
|
162
|
+
expect(text).toContain("Chains: tempo, base");
|
|
163
|
+
expect(text).toContain("Consumer principal: did:pkh:eip155:8453:0xabc");
|
|
164
|
+
});
|
|
165
|
+
it("imports a wallet into OWS encrypted storage with Base as the default chain", async () => {
|
|
166
|
+
const walletSetup = await getWalletSetupTool();
|
|
167
|
+
const result = await walletSetup({
|
|
168
|
+
action: "import",
|
|
169
|
+
key: "0x1234",
|
|
170
|
+
name: "imported-wallet",
|
|
171
|
+
chain: "base",
|
|
172
|
+
});
|
|
173
|
+
const text = flattenText(result);
|
|
174
|
+
expect(state.importWalletCalls).toEqual([
|
|
175
|
+
{ privateKey: "0x1234", name: "imported-wallet", chain: "evm" },
|
|
176
|
+
]);
|
|
177
|
+
expect(state.addedWallets).toEqual([
|
|
178
|
+
{
|
|
179
|
+
id: "imported-wallet",
|
|
180
|
+
keyType: "ows",
|
|
181
|
+
owsWalletId: "ows-wallet-imported",
|
|
182
|
+
chains: ["tempo", "base"],
|
|
183
|
+
defaultChain: "base",
|
|
184
|
+
label: "imported-wallet",
|
|
185
|
+
},
|
|
186
|
+
]);
|
|
187
|
+
expect(text).toContain("Key imported to OWS [encrypted]:");
|
|
188
|
+
expect(text).toContain("Address: 0x2222222222222222222222222222222222222222");
|
|
189
|
+
expect(text).toContain("Consumer principal: did:pkh:eip155:8453:0xabc");
|
|
190
|
+
});
|
|
191
|
+
it("starts card setup when no card is connected", async () => {
|
|
192
|
+
const walletSetup = await getWalletSetupTool();
|
|
193
|
+
const result = await walletSetup({ action: "add-card" });
|
|
194
|
+
const text = flattenText(result);
|
|
195
|
+
expect(text).toContain("Open this setup page to connect your card:");
|
|
196
|
+
expect(text).toContain("https://api.agentwonderland.com/card/handoff/setup-token");
|
|
197
|
+
expect(state.pollCardSetupCalls).toEqual([]);
|
|
198
|
+
});
|
|
199
|
+
it("completes a pending card setup when Stripe handoff has finished", async () => {
|
|
200
|
+
state.pendingCardSetupToken = "setup-token";
|
|
201
|
+
state.pollCardSetupResult = {
|
|
202
|
+
brand: "Visa",
|
|
203
|
+
last4: "4242",
|
|
204
|
+
consumerToken: "consumer-token",
|
|
205
|
+
};
|
|
206
|
+
const walletSetup = await getWalletSetupTool();
|
|
207
|
+
const result = await walletSetup({ action: "add-card" });
|
|
208
|
+
const text = flattenText(result);
|
|
209
|
+
expect(state.pollCardSetupCalls).toEqual([
|
|
210
|
+
{ token: "setup-token", timeoutMs: 250 },
|
|
211
|
+
]);
|
|
212
|
+
expect(text).toContain("Connected! Visa ****4242 is ready for payments.");
|
|
213
|
+
expect(text).toContain("Consumer principal: did:pkh:eip155:8453:0xabc");
|
|
214
|
+
});
|
|
215
|
+
it("removes the connected card", async () => {
|
|
216
|
+
state.card = {
|
|
217
|
+
consumerToken: "consumer-token",
|
|
218
|
+
paymentMethodId: "pm_123",
|
|
219
|
+
last4: "4242",
|
|
220
|
+
brand: "Visa",
|
|
221
|
+
};
|
|
222
|
+
const walletSetup = await getWalletSetupTool();
|
|
223
|
+
const result = await walletSetup({ action: "remove-card" });
|
|
224
|
+
const text = flattenText(result);
|
|
225
|
+
expect(state.setCardConfigCalls).toEqual([null]);
|
|
226
|
+
expect(state.card).toBeNull();
|
|
227
|
+
expect(text).toContain("Removed Visa ****4242.");
|
|
228
|
+
expect(text).toContain("Card disconnected from Agent Wonderland.");
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -23,6 +23,6 @@ export function formatPaymentChoicePrompt(subject, methods, commands) {
|
|
|
23
23
|
...commands,
|
|
24
24
|
"",
|
|
25
25
|
`Available methods: ${methods.map((method) => `"${method}"`).join(", ")}`,
|
|
26
|
-
"
|
|
26
|
+
"You can omit pay_with to use the default compatible method.",
|
|
27
27
|
].join("\n");
|
|
28
28
|
}
|
package/dist/tools/agent-info.js
CHANGED
|
@@ -20,7 +20,7 @@ export function registerAgentInfoTools(server) {
|
|
|
20
20
|
"",
|
|
21
21
|
a.description ?? "",
|
|
22
22
|
"",
|
|
23
|
-
`Pricing: ${formatPrice(a.
|
|
23
|
+
`Pricing: ${formatPrice(a.pricePerRunUsd)}`,
|
|
24
24
|
`Reliability: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
|
|
25
25
|
`Avg latency: ${a.avgResponseTimeMs != null ? a.avgResponseTimeMs + "ms" : "N/A"}`,
|
|
26
26
|
...(() => {
|
|
@@ -100,7 +100,7 @@ export function registerAgentInfoTools(server) {
|
|
|
100
100
|
return [
|
|
101
101
|
` ${a.name}`,
|
|
102
102
|
` ${stars(rating)} (${s.ratingCount ?? 0} reviews)${tipCount > 0 ? ` • ${tipCount} tips` : ""}`,
|
|
103
|
-
` ${compactNumber(jobs)} jobs • ${formatPrice(a.
|
|
103
|
+
` ${compactNumber(jobs)} jobs • ${formatPrice(a.pricePerRunUsd)}`,
|
|
104
104
|
` Success: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
|
|
105
105
|
` ${agentWebUrl(a.id)}`,
|
|
106
106
|
"",
|
package/dist/tools/favorites.js
CHANGED
|
@@ -34,7 +34,7 @@ export function registerFavoriteTools(server) {
|
|
|
34
34
|
const agent = await apiGet(`/agents/${id}`);
|
|
35
35
|
const rating = stars(agent.avgRating);
|
|
36
36
|
const jobs = compactNumber(agent.totalExecutions);
|
|
37
|
-
const price = formatPrice(agent.
|
|
37
|
+
const price = formatPrice(agent.pricePerRunUsd);
|
|
38
38
|
lines.push(`${agent.name} ${rating} ${jobs} jobs | ${price}`);
|
|
39
39
|
lines.push(` ID: ${id}`);
|
|
40
40
|
if (agent.description)
|
package/dist/tools/jobs.js
CHANGED
|
@@ -11,7 +11,14 @@ export function registerJobTools(server) {
|
|
|
11
11
|
server.tool("get_job", "Get the status and output of a job by ID. Use to poll async jobs until they complete.", {
|
|
12
12
|
job_id: z.string().describe("Job ID (UUID)"),
|
|
13
13
|
}, async ({ job_id }) => {
|
|
14
|
-
|
|
14
|
+
let url = `/jobs/${job_id}`;
|
|
15
|
+
if (!isAuthenticated() && hasWalletConfigured()) {
|
|
16
|
+
const address = await getWalletAddress();
|
|
17
|
+
if (address) {
|
|
18
|
+
url += `?wallet=${encodeURIComponent(address)}`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const result = await apiGet(url);
|
|
15
22
|
if (result.status === "processing") {
|
|
16
23
|
return text(`Job ${job_id} is still processing...`);
|
|
17
24
|
}
|