@agentwonderland/mcp 0.1.25 → 0.1.27
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__/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 +10 -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/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 +13 -6
- package/dist/core/formatters.d.ts +3 -2
- package/dist/core/formatters.js +7 -1
- 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 +20 -7
- 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 +29 -7
- package/dist/core/tempo-charge.d.ts +7 -0
- package/dist/core/tempo-charge.js +84 -0
- package/dist/index.js +5 -7
- package/dist/prompts/index.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 +14 -28
- package/dist/tools/jobs.js +82 -16
- package/dist/tools/passes.js +30 -14
- package/dist/tools/run.js +35 -20
- package/dist/tools/search.js +9 -8
- package/dist/tools/solve.js +45 -25
- package/dist/tools/wallet.js +35 -15
- package/package.json +2 -2
- 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 +17 -6
- package/src/core/__tests__/principal.test.ts +49 -4
- package/src/core/__tests__/solana-charge.test.ts +59 -0
- package/src/core/api-client.ts +16 -3
- package/src/core/balances.ts +63 -0
- package/src/core/base-charge.ts +13 -6
- package/src/core/formatters.ts +10 -3
- package/src/core/passes.ts +5 -2
- package/src/core/payments.ts +22 -7
- package/src/core/principal.ts +42 -1
- package/src/core/settings.ts +36 -0
- package/src/core/solana-charge.ts +43 -9
- package/src/core/tempo-charge.ts +104 -0
- package/src/index.ts +5 -7
- package/src/prompts/index.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 +15 -38
- package/src/tools/jobs.ts +79 -17
- package/src/tools/passes.ts +30 -14
- package/src/tools/run.ts +38 -20
- package/src/tools/search.ts +10 -9
- package/src/tools/solve.ts +48 -25
- package/src/tools/wallet.ts +33 -17
- package/src/tools/observability.ts +0 -43
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const mockApiGet = vi.fn();
|
|
4
|
+
const mockIsAuthenticated = vi.fn();
|
|
5
|
+
const mockHasWalletConfigured = vi.fn();
|
|
6
|
+
const mockGetWalletAddress = vi.fn();
|
|
7
|
+
const mockFormatRunResult = vi.fn();
|
|
8
|
+
|
|
9
|
+
vi.mock("../../core/api-client.js", () => ({
|
|
10
|
+
apiGet: mockApiGet,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock("../../core/config.js", () => ({
|
|
14
|
+
isAuthenticated: mockIsAuthenticated,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock("../../core/payments.js", () => ({
|
|
18
|
+
hasWalletConfigured: mockHasWalletConfigured,
|
|
19
|
+
getWalletAddress: mockGetWalletAddress,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
vi.mock("../../core/formatters.js", () => ({
|
|
23
|
+
formatRunResult: mockFormatRunResult,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
function flattenToolText(result: unknown): string {
|
|
27
|
+
const content = (result as { content?: Array<{ type?: string; text?: string }> })?.content ?? [];
|
|
28
|
+
return content
|
|
29
|
+
.filter((item) => item?.type === "text")
|
|
30
|
+
.map((item) => item.text ?? "")
|
|
31
|
+
.join("\n\n");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function makeServerHarness() {
|
|
35
|
+
const handlers = new Map<string, (args: Record<string, unknown>) => Promise<unknown>>();
|
|
36
|
+
return {
|
|
37
|
+
handlers,
|
|
38
|
+
server: {
|
|
39
|
+
tool(name: string, _description: string, _schema: unknown, handler: (args: Record<string, unknown>) => Promise<unknown>) {
|
|
40
|
+
handlers.set(name, handler);
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("job MCP tools", () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
vi.resetModules();
|
|
49
|
+
vi.clearAllMocks();
|
|
50
|
+
mockIsAuthenticated.mockReturnValue(false);
|
|
51
|
+
mockHasWalletConfigured.mockReturnValue(true);
|
|
52
|
+
mockGetWalletAddress.mockResolvedValue("0xabc123");
|
|
53
|
+
mockFormatRunResult.mockReturnValue("formatted job result");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("appends the wallet address when looking up a job for an unauthenticated consumer", async () => {
|
|
57
|
+
mockApiGet.mockResolvedValueOnce({ status: "completed", job_id: "job-123" });
|
|
58
|
+
|
|
59
|
+
const { registerJobTools } = await import("../jobs.js");
|
|
60
|
+
const harness = makeServerHarness();
|
|
61
|
+
registerJobTools(harness.server as never);
|
|
62
|
+
|
|
63
|
+
const getJob = harness.handlers.get("get_job");
|
|
64
|
+
expect(getJob).toBeDefined();
|
|
65
|
+
|
|
66
|
+
const result = await getJob!({ job_id: "job-123" });
|
|
67
|
+
const text = flattenToolText(result);
|
|
68
|
+
|
|
69
|
+
expect(mockApiGet).toHaveBeenCalledWith("/jobs/job-123?wallet=0xabc123");
|
|
70
|
+
expect(text).toContain("formatted job result");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("returns a processing message while the async job is still running", async () => {
|
|
74
|
+
mockApiGet.mockResolvedValueOnce({ status: "processing" });
|
|
75
|
+
|
|
76
|
+
const { registerJobTools } = await import("../jobs.js");
|
|
77
|
+
const harness = makeServerHarness();
|
|
78
|
+
registerJobTools(harness.server as never);
|
|
79
|
+
|
|
80
|
+
const getJob = harness.handlers.get("get_job");
|
|
81
|
+
expect(getJob).toBeDefined();
|
|
82
|
+
|
|
83
|
+
const result = await getJob!({ job_id: "job-456" });
|
|
84
|
+
const text = flattenToolText(result);
|
|
85
|
+
|
|
86
|
+
expect(mockApiGet).toHaveBeenCalledWith("/jobs/job-456?wallet=0xabc123");
|
|
87
|
+
expect(text).toContain("Job job-456 is still processing");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const mockApiGet = vi.fn();
|
|
4
|
+
const mockApiPost = vi.fn();
|
|
5
|
+
const mockApiPostWithPayment = vi.fn();
|
|
6
|
+
const mockGetCompatiblePaymentMethods = vi.fn();
|
|
7
|
+
const mockHasWalletConfigured = vi.fn();
|
|
8
|
+
const mockGetConfiguredMethods = vi.fn();
|
|
9
|
+
const mockGetWalletAddress = vi.fn();
|
|
10
|
+
const mockNormalizePaymentMethod = vi.fn();
|
|
11
|
+
const mockRequiresSpendConfirmation = vi.fn();
|
|
12
|
+
const mockGetDefaultTipAmount = vi.fn();
|
|
13
|
+
const mockCanSpend = vi.fn();
|
|
14
|
+
const mockRecordSpend = vi.fn();
|
|
15
|
+
const mockRequiresPolicyConfirmation = vi.fn();
|
|
16
|
+
const mockUploadLocalFiles = vi.fn();
|
|
17
|
+
const mockStoreFeedbackToken = vi.fn();
|
|
18
|
+
const mockGetActiveCreditPack = vi.fn();
|
|
19
|
+
const mockGetCreditPackInventory = vi.fn();
|
|
20
|
+
const mockGetCreditPackProgram = vi.fn();
|
|
21
|
+
const mockGetOrCreatePendingCardSetup = vi.fn();
|
|
22
|
+
const mockFormatCardSetupBlocks = vi.fn();
|
|
23
|
+
|
|
24
|
+
vi.mock("../../core/api-client.js", () => ({
|
|
25
|
+
apiGet: mockApiGet,
|
|
26
|
+
apiPost: mockApiPost,
|
|
27
|
+
apiPostWithPayment: mockApiPostWithPayment,
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
vi.mock("../../core/card-setup.js", () => ({
|
|
31
|
+
getOrCreatePendingCardSetup: mockGetOrCreatePendingCardSetup,
|
|
32
|
+
formatCardSetupBlocks: mockFormatCardSetupBlocks,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
vi.mock("../../core/payments.js", () => ({
|
|
36
|
+
getCompatiblePaymentMethods: mockGetCompatiblePaymentMethods,
|
|
37
|
+
getConfiguredMethods: mockGetConfiguredMethods,
|
|
38
|
+
hasWalletConfigured: mockHasWalletConfigured,
|
|
39
|
+
getWalletAddress: mockGetWalletAddress,
|
|
40
|
+
normalizePaymentMethod: mockNormalizePaymentMethod,
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
vi.mock("../../core/config.js", () => ({
|
|
44
|
+
requiresSpendConfirmation: mockRequiresSpendConfirmation,
|
|
45
|
+
getDefaultTipAmount: mockGetDefaultTipAmount,
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
vi.mock("../../core/spend-policy.js", () => ({
|
|
49
|
+
canSpend: mockCanSpend,
|
|
50
|
+
recordSpend: mockRecordSpend,
|
|
51
|
+
requiresPolicyConfirmation: mockRequiresPolicyConfirmation,
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
vi.mock("../../core/file-upload.js", () => ({
|
|
55
|
+
uploadLocalFiles: mockUploadLocalFiles,
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
vi.mock("../_token-cache.js", () => ({
|
|
59
|
+
storeFeedbackToken: mockStoreFeedbackToken,
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
vi.mock("../../core/passes.js", () => ({
|
|
63
|
+
formatCreditPackOffer: vi.fn(),
|
|
64
|
+
getActiveCreditPack: mockGetActiveCreditPack,
|
|
65
|
+
getCreditPackInventory: mockGetCreditPackInventory,
|
|
66
|
+
getCreditPackProgram: mockGetCreditPackProgram,
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
function flattenToolText(result: unknown): string {
|
|
70
|
+
const content = (result as { content?: Array<{ type?: string; text?: string }> })?.content ?? [];
|
|
71
|
+
return content
|
|
72
|
+
.filter((item) => item?.type === "text")
|
|
73
|
+
.map((item) => item.text ?? "")
|
|
74
|
+
.join("\n\n");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function makeServerHarness() {
|
|
78
|
+
const handlers = new Map<string, (args: Record<string, unknown>) => Promise<unknown>>();
|
|
79
|
+
return {
|
|
80
|
+
handlers,
|
|
81
|
+
server: {
|
|
82
|
+
tool(name: string, _description: string, _schema: unknown, handler: (args: Record<string, unknown>) => Promise<unknown>) {
|
|
83
|
+
handlers.set(name, handler);
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const selectedAgent = {
|
|
90
|
+
id: "agent-1",
|
|
91
|
+
name: "MockFixedPrice",
|
|
92
|
+
pricePerRunUsd: "0.01",
|
|
93
|
+
tags: ["translation"],
|
|
94
|
+
mcpSchema: null,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
describe("run_agent MCP tool", () => {
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
vi.resetModules();
|
|
100
|
+
vi.clearAllMocks();
|
|
101
|
+
|
|
102
|
+
mockHasWalletConfigured.mockReturnValue(true);
|
|
103
|
+
mockGetConfiguredMethods.mockReturnValue(["card"]);
|
|
104
|
+
mockGetCompatiblePaymentMethods.mockReturnValue(["card"]);
|
|
105
|
+
mockNormalizePaymentMethod.mockImplementation((method: string) => method);
|
|
106
|
+
mockRequiresSpendConfirmation.mockReturnValue(true);
|
|
107
|
+
mockGetDefaultTipAmount.mockReturnValue(0);
|
|
108
|
+
mockCanSpend.mockReturnValue({ ok: true, message: "" });
|
|
109
|
+
mockRequiresPolicyConfirmation.mockReturnValue(false);
|
|
110
|
+
mockUploadLocalFiles.mockResolvedValue({ input: { text: "hello", target_language: "es" }, uploads: [] });
|
|
111
|
+
mockGetActiveCreditPack.mockReturnValue(null);
|
|
112
|
+
mockGetCreditPackInventory.mockResolvedValue([]);
|
|
113
|
+
mockGetCreditPackProgram.mockReturnValue(null);
|
|
114
|
+
mockGetOrCreatePendingCardSetup.mockResolvedValue({ url: "https://example.com/card-setup" });
|
|
115
|
+
mockFormatCardSetupBlocks.mockReturnValue(["card setup"]);
|
|
116
|
+
mockGetWalletAddress.mockResolvedValue("0xabc");
|
|
117
|
+
mockApiGet.mockResolvedValue(selectedAgent);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("quotes explicit agent execution before running", async () => {
|
|
121
|
+
const { registerRunTools } = await import("../run.js");
|
|
122
|
+
const harness = makeServerHarness();
|
|
123
|
+
registerRunTools(harness.server as never);
|
|
124
|
+
|
|
125
|
+
const runAgent = harness.handlers.get("run_agent");
|
|
126
|
+
expect(runAgent).toBeDefined();
|
|
127
|
+
|
|
128
|
+
const result = await runAgent!({
|
|
129
|
+
agent_id: selectedAgent.id,
|
|
130
|
+
input: { text: "hello", target_language: "es" },
|
|
131
|
+
pay_with: "card",
|
|
132
|
+
});
|
|
133
|
+
const text = flattenToolText(result);
|
|
134
|
+
|
|
135
|
+
expect(text).toContain("Ready to run MockFixedPrice");
|
|
136
|
+
expect(text).toContain('run_agent({ agent_id: "agent-1", input: <same>, pay_with: "card", confirmed: true })');
|
|
137
|
+
expect(mockApiPostWithPayment).not.toHaveBeenCalled();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("runs the explicitly selected agent when confirmed", async () => {
|
|
141
|
+
mockApiPostWithPayment.mockResolvedValueOnce({
|
|
142
|
+
status: "success",
|
|
143
|
+
job_id: "job-1",
|
|
144
|
+
agent_id: selectedAgent.id,
|
|
145
|
+
agent_name: selectedAgent.name,
|
|
146
|
+
output: { translated_text: "hola" },
|
|
147
|
+
cost: 0.01,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const { registerRunTools } = await import("../run.js");
|
|
151
|
+
const harness = makeServerHarness();
|
|
152
|
+
registerRunTools(harness.server as never);
|
|
153
|
+
|
|
154
|
+
const runAgent = harness.handlers.get("run_agent");
|
|
155
|
+
expect(runAgent).toBeDefined();
|
|
156
|
+
|
|
157
|
+
const result = await runAgent!({
|
|
158
|
+
agent_id: selectedAgent.id,
|
|
159
|
+
input: { text: "hello", target_language: "es" },
|
|
160
|
+
pay_with: "card",
|
|
161
|
+
confirmed: true,
|
|
162
|
+
});
|
|
163
|
+
const text = flattenToolText(result);
|
|
164
|
+
|
|
165
|
+
expect(mockApiPostWithPayment).toHaveBeenCalledWith(
|
|
166
|
+
"/agents/agent-1/run",
|
|
167
|
+
{ input: { text: "hello", target_language: "es" } },
|
|
168
|
+
"card",
|
|
169
|
+
);
|
|
170
|
+
expect(mockRecordSpend).toHaveBeenCalledWith("card", 0.01);
|
|
171
|
+
expect(text).toContain("translated_text");
|
|
172
|
+
expect(text).toContain("✓ MockFixedPrice");
|
|
173
|
+
expect(text).toContain("Paid: $0.01 via card");
|
|
174
|
+
expect(text).toContain("Job ID: job-1");
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const mockApiGet = vi.fn();
|
|
4
|
+
const mockApiPost = vi.fn();
|
|
5
|
+
const mockApiPostWithPayment = vi.fn();
|
|
6
|
+
const mockGetCompatiblePaymentMethods = vi.fn();
|
|
7
|
+
const mockHasWalletConfigured = vi.fn();
|
|
8
|
+
const mockGetConfiguredMethods = vi.fn();
|
|
9
|
+
const mockGetAcceptedPaymentMethods = vi.fn();
|
|
10
|
+
const mockGetWalletAddress = vi.fn();
|
|
11
|
+
const mockNormalizePaymentMethod = vi.fn();
|
|
12
|
+
const mockToRegistryPaymentMethod = vi.fn();
|
|
13
|
+
const mockRequiresSpendConfirmation = vi.fn();
|
|
14
|
+
const mockGetDefaultTipAmount = vi.fn();
|
|
15
|
+
const mockCanSpend = vi.fn();
|
|
16
|
+
const mockRecordSpend = vi.fn();
|
|
17
|
+
const mockRequiresPolicyConfirmation = vi.fn();
|
|
18
|
+
const mockUploadLocalFiles = vi.fn();
|
|
19
|
+
const mockStoreFeedbackToken = vi.fn();
|
|
20
|
+
const mockGetActiveCreditPack = vi.fn();
|
|
21
|
+
const mockGetCreditPackInventory = vi.fn();
|
|
22
|
+
const mockGetCreditPackProgram = vi.fn();
|
|
23
|
+
const mockGetOrCreatePendingCardSetup = vi.fn();
|
|
24
|
+
const mockFormatCardSetupBlocks = vi.fn();
|
|
25
|
+
|
|
26
|
+
vi.mock("../../core/api-client.js", () => ({
|
|
27
|
+
apiGet: mockApiGet,
|
|
28
|
+
apiPost: mockApiPost,
|
|
29
|
+
apiPostWithPayment: mockApiPostWithPayment,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
vi.mock("../../core/card-setup.js", () => ({
|
|
33
|
+
getOrCreatePendingCardSetup: mockGetOrCreatePendingCardSetup,
|
|
34
|
+
formatCardSetupBlocks: mockFormatCardSetupBlocks,
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
vi.mock("../../core/payments.js", () => ({
|
|
38
|
+
getCompatiblePaymentMethods: mockGetCompatiblePaymentMethods,
|
|
39
|
+
hasWalletConfigured: mockHasWalletConfigured,
|
|
40
|
+
getConfiguredMethods: mockGetConfiguredMethods,
|
|
41
|
+
getAcceptedPaymentMethods: mockGetAcceptedPaymentMethods,
|
|
42
|
+
getWalletAddress: mockGetWalletAddress,
|
|
43
|
+
normalizePaymentMethod: mockNormalizePaymentMethod,
|
|
44
|
+
toRegistryPaymentMethod: mockToRegistryPaymentMethod,
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
vi.mock("../../core/config.js", () => ({
|
|
48
|
+
requiresSpendConfirmation: mockRequiresSpendConfirmation,
|
|
49
|
+
getDefaultTipAmount: mockGetDefaultTipAmount,
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
vi.mock("../../core/spend-policy.js", () => ({
|
|
53
|
+
canSpend: mockCanSpend,
|
|
54
|
+
recordSpend: mockRecordSpend,
|
|
55
|
+
requiresPolicyConfirmation: mockRequiresPolicyConfirmation,
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
vi.mock("../../core/file-upload.js", () => ({
|
|
59
|
+
uploadLocalFiles: mockUploadLocalFiles,
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
vi.mock("../_token-cache.js", () => ({
|
|
63
|
+
storeFeedbackToken: mockStoreFeedbackToken,
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
vi.mock("../../core/passes.js", () => ({
|
|
67
|
+
getActiveCreditPack: mockGetActiveCreditPack,
|
|
68
|
+
getCreditPackInventory: mockGetCreditPackInventory,
|
|
69
|
+
getCreditPackProgram: mockGetCreditPackProgram,
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
function flattenToolText(result: unknown): string {
|
|
73
|
+
const content = (result as { content?: Array<{ type?: string; text?: string }> })?.content ?? [];
|
|
74
|
+
return content
|
|
75
|
+
.filter((item) => item?.type === "text")
|
|
76
|
+
.map((item) => item.text ?? "")
|
|
77
|
+
.join("\n\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function makeServerHarness() {
|
|
81
|
+
const handlers = new Map<string, (args: Record<string, unknown>) => Promise<unknown>>();
|
|
82
|
+
return {
|
|
83
|
+
handlers,
|
|
84
|
+
server: {
|
|
85
|
+
tool(name: string, _description: string, _schema: unknown, handler: (args: Record<string, unknown>) => Promise<unknown>) {
|
|
86
|
+
handlers.set(name, handler);
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const selectedAgent = {
|
|
93
|
+
id: "agent-1",
|
|
94
|
+
name: "MockFixedPrice",
|
|
95
|
+
pricePerRunUsd: "0.01",
|
|
96
|
+
tags: ["translation"],
|
|
97
|
+
mcpSchema: null,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
describe("solve MCP tool", () => {
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
vi.resetModules();
|
|
103
|
+
vi.clearAllMocks();
|
|
104
|
+
|
|
105
|
+
mockHasWalletConfigured.mockReturnValue(true);
|
|
106
|
+
mockGetConfiguredMethods.mockReturnValue(["card"]);
|
|
107
|
+
mockGetAcceptedPaymentMethods.mockReturnValue(["card"]);
|
|
108
|
+
mockGetCompatiblePaymentMethods.mockReturnValue(["card"]);
|
|
109
|
+
mockNormalizePaymentMethod.mockImplementation((method: string) => method);
|
|
110
|
+
mockToRegistryPaymentMethod.mockImplementation((method: string) => method);
|
|
111
|
+
mockRequiresSpendConfirmation.mockReturnValue(true);
|
|
112
|
+
mockGetDefaultTipAmount.mockReturnValue(0);
|
|
113
|
+
mockCanSpend.mockReturnValue({ ok: true, message: "" });
|
|
114
|
+
mockRequiresPolicyConfirmation.mockReturnValue(false);
|
|
115
|
+
mockUploadLocalFiles.mockResolvedValue({ input: { text: "hello", target_language: "es" }, uploads: [] });
|
|
116
|
+
mockGetActiveCreditPack.mockReturnValue(null);
|
|
117
|
+
mockGetCreditPackInventory.mockResolvedValue([]);
|
|
118
|
+
mockGetCreditPackProgram.mockReturnValue(null);
|
|
119
|
+
mockGetOrCreatePendingCardSetup.mockResolvedValue({ url: "https://example.com/card-setup" });
|
|
120
|
+
mockFormatCardSetupBlocks.mockReturnValue(["card setup"]);
|
|
121
|
+
mockGetWalletAddress.mockResolvedValue("0xabc");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("falls back to a quoted confirmation flow when /solve returns a 402 challenge", async () => {
|
|
125
|
+
mockApiPost.mockRejectedValueOnce(Object.assign(new Error("Payment Required"), { status: 402 }));
|
|
126
|
+
mockApiGet.mockResolvedValueOnce([selectedAgent]);
|
|
127
|
+
|
|
128
|
+
const { registerSolveTools } = await import("../solve.js");
|
|
129
|
+
const harness = makeServerHarness();
|
|
130
|
+
registerSolveTools(harness.server as never);
|
|
131
|
+
|
|
132
|
+
const solve = harness.handlers.get("solve");
|
|
133
|
+
expect(solve).toBeDefined();
|
|
134
|
+
|
|
135
|
+
const result = await solve!({
|
|
136
|
+
intent: "MockFixedPrice",
|
|
137
|
+
input: { text: "hello", target_language: "es" },
|
|
138
|
+
budget: 0.05,
|
|
139
|
+
pay_with: "card",
|
|
140
|
+
});
|
|
141
|
+
const text = flattenToolText(result);
|
|
142
|
+
|
|
143
|
+
expect(text).toContain("Best match: MockFixedPrice");
|
|
144
|
+
expect(text).toContain('solve({ intent: "MockFixedPrice"');
|
|
145
|
+
expect(text).toContain('pay_with: "card"');
|
|
146
|
+
expect(mockApiPostWithPayment).not.toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("falls back to paid execution when confirmed after a 402 challenge", async () => {
|
|
150
|
+
mockApiPost.mockRejectedValueOnce(Object.assign(new Error("Payment Required"), { status: 402 }));
|
|
151
|
+
mockApiGet.mockResolvedValueOnce([selectedAgent]);
|
|
152
|
+
mockApiPostWithPayment.mockResolvedValueOnce({
|
|
153
|
+
status: "success",
|
|
154
|
+
job_id: "job-1",
|
|
155
|
+
agent_id: selectedAgent.id,
|
|
156
|
+
agent_name: selectedAgent.name,
|
|
157
|
+
output: { translated_text: "hola" },
|
|
158
|
+
cost: 0.01,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const { registerSolveTools } = await import("../solve.js");
|
|
162
|
+
const harness = makeServerHarness();
|
|
163
|
+
registerSolveTools(harness.server as never);
|
|
164
|
+
|
|
165
|
+
const solve = harness.handlers.get("solve");
|
|
166
|
+
expect(solve).toBeDefined();
|
|
167
|
+
|
|
168
|
+
const result = await solve!({
|
|
169
|
+
intent: "MockFixedPrice",
|
|
170
|
+
input: { text: "hello", target_language: "es" },
|
|
171
|
+
budget: 0.05,
|
|
172
|
+
pay_with: "card",
|
|
173
|
+
confirmed: true,
|
|
174
|
+
});
|
|
175
|
+
const text = flattenToolText(result);
|
|
176
|
+
|
|
177
|
+
expect(mockApiPostWithPayment).toHaveBeenCalledWith(
|
|
178
|
+
"/agents/agent-1/run",
|
|
179
|
+
{ input: { text: "hello", target_language: "es" } },
|
|
180
|
+
"card",
|
|
181
|
+
);
|
|
182
|
+
expect(text).toContain("Running MockFixedPrice — best match");
|
|
183
|
+
expect(text).toContain("Job ID: job-1");
|
|
184
|
+
expect(text).toContain("Paid: $0.01 via card");
|
|
185
|
+
});
|
|
186
|
+
});
|