@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,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
|
@@ -14,14 +14,26 @@ export function registerAgentInfoTools(server) {
|
|
|
14
14
|
const payment = (a.payment ?? {});
|
|
15
15
|
const _pricing = (payment.pricing ?? {});
|
|
16
16
|
const creditPacks = payment.credit_packs;
|
|
17
|
+
const totalJobs = (s.completedJobs ?? a.totalExecutions ?? 0);
|
|
18
|
+
const acceptedPayments = payment.accepted_payments ?? [];
|
|
19
|
+
const paymentLabelMap = {
|
|
20
|
+
tempo_usdc: "tempo",
|
|
21
|
+
base_usdc: "base",
|
|
22
|
+
solana_usdc: "solana",
|
|
23
|
+
stripe_card: "card",
|
|
24
|
+
};
|
|
25
|
+
const paymentLabels = acceptedPayments.map((m) => paymentLabelMap[m] ?? m);
|
|
17
26
|
const lines = [
|
|
18
27
|
`${a.name}`,
|
|
19
|
-
`${stars(a.avgRating ?? s.avgRating)} (${s.ratingCount ?? 0} reviews) • ${compactNumber(
|
|
28
|
+
`${stars(a.avgRating ?? s.avgRating)} (${s.ratingCount ?? 0} reviews) • ${compactNumber(totalJobs)} jobs`,
|
|
20
29
|
"",
|
|
21
30
|
a.description ?? "",
|
|
22
31
|
"",
|
|
23
32
|
`Pricing: ${formatPrice(a.pricePerRunUsd)}`,
|
|
24
|
-
|
|
33
|
+
...(paymentLabels.length > 0 ? [`Accepted payments: ${paymentLabels.join(", ")}`] : []),
|
|
34
|
+
...(totalJobs > 0 && a.successRate != null
|
|
35
|
+
? [`Reliability: ${(Number(a.successRate) * 100).toFixed(0)}% (${compactNumber(totalJobs)} runs)`]
|
|
36
|
+
: []),
|
|
25
37
|
`Avg latency: ${a.avgResponseTimeMs != null ? a.avgResponseTimeMs + "ms" : "N/A"}`,
|
|
26
38
|
...(() => {
|
|
27
39
|
const lastActive = formatLastActive(a.lastActiveAt);
|
|
@@ -82,30 +94,4 @@ export function registerAgentInfoTools(server) {
|
|
|
82
94
|
lines.push("", `ID: ${a.id}`, `View: ${agentWebUrl(a.id)}`);
|
|
83
95
|
return text(lines.join("\n"));
|
|
84
96
|
});
|
|
85
|
-
// ── compare_agents ──────────────────────────────────────────────
|
|
86
|
-
server.tool("compare_agents", "Compare multiple agents side-by-side on rating, price, success rate, and job count.", {
|
|
87
|
-
agent_ids: z
|
|
88
|
-
.array(z.string())
|
|
89
|
-
.min(2)
|
|
90
|
-
.max(5)
|
|
91
|
-
.describe("Agent IDs to compare (2-5)"),
|
|
92
|
-
}, async ({ agent_ids }) => {
|
|
93
|
-
const agents = await Promise.all(agent_ids.map((id) => apiGet(`/agents/${id}`)));
|
|
94
|
-
const header = "Agent Comparison:\n";
|
|
95
|
-
const lines = agents.map((a) => {
|
|
96
|
-
const s = (a.stats ?? {});
|
|
97
|
-
const rating = a.avgRating ?? s.avgRating;
|
|
98
|
-
const jobs = (s.completedJobs ?? a.totalExecutions ?? 0);
|
|
99
|
-
const tipCount = (s.tipCount ?? 0);
|
|
100
|
-
return [
|
|
101
|
-
` ${a.name}`,
|
|
102
|
-
` ${stars(rating)} (${s.ratingCount ?? 0} reviews)${tipCount > 0 ? ` • ${tipCount} tips` : ""}`,
|
|
103
|
-
` ${compactNumber(jobs)} jobs • ${formatPrice(a.pricePerRunUsd)}`,
|
|
104
|
-
` Success: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
|
|
105
|
-
` ${agentWebUrl(a.id)}`,
|
|
106
|
-
"",
|
|
107
|
-
].join("\n");
|
|
108
|
-
});
|
|
109
|
-
return text(header + lines.join("\n"));
|
|
110
|
-
});
|
|
111
97
|
}
|
package/dist/tools/jobs.js
CHANGED
|
@@ -6,32 +6,93 @@ import { formatRunResult } from "../core/formatters.js";
|
|
|
6
6
|
function text(t) {
|
|
7
7
|
return { content: [{ type: "text", text: t }] };
|
|
8
8
|
}
|
|
9
|
+
async function getConsumerWalletAddresses() {
|
|
10
|
+
const addresses = new Set();
|
|
11
|
+
for (const chain of ["tempo", "base", "solana"]) {
|
|
12
|
+
const addr = await getWalletAddress(chain);
|
|
13
|
+
if (addr)
|
|
14
|
+
addresses.add(addr);
|
|
15
|
+
}
|
|
16
|
+
return [...addresses];
|
|
17
|
+
}
|
|
18
|
+
function formatRelativeTime(iso) {
|
|
19
|
+
if (!iso)
|
|
20
|
+
return "";
|
|
21
|
+
const t = new Date(iso).getTime();
|
|
22
|
+
if (!Number.isFinite(t))
|
|
23
|
+
return "";
|
|
24
|
+
const diffSec = Math.max(0, Math.floor((Date.now() - t) / 1000));
|
|
25
|
+
if (diffSec < 60)
|
|
26
|
+
return `${diffSec}s ago`;
|
|
27
|
+
if (diffSec < 3600)
|
|
28
|
+
return `${Math.floor(diffSec / 60)}m ago`;
|
|
29
|
+
if (diffSec < 86400)
|
|
30
|
+
return `${Math.floor(diffSec / 3600)}h ago`;
|
|
31
|
+
return `${Math.floor(diffSec / 86400)}d ago`;
|
|
32
|
+
}
|
|
9
33
|
export function registerJobTools(server) {
|
|
10
34
|
// ── get_job ─────────────────────────────────────────────────────
|
|
11
35
|
server.tool("get_job", "Get the status and output of a job by ID. Use to poll async jobs until they complete.", {
|
|
12
36
|
job_id: z.string().describe("Job ID (UUID)"),
|
|
13
37
|
}, async ({ job_id }) => {
|
|
14
|
-
const
|
|
38
|
+
const walletLookup = !isAuthenticated() && hasWalletConfigured();
|
|
39
|
+
const addresses = walletLookup ? await getConsumerWalletAddresses() : [""];
|
|
40
|
+
let result = null;
|
|
41
|
+
for (const addr of addresses) {
|
|
42
|
+
const url = addr ? `/jobs/${job_id}?wallet=${encodeURIComponent(addr)}` : `/jobs/${job_id}`;
|
|
43
|
+
try {
|
|
44
|
+
result = await apiGet(url);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
const status = err.status;
|
|
49
|
+
if (status !== 404)
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!result) {
|
|
54
|
+
return text(`Job ${job_id} not found. Either the job ID is wrong or it was paid with a wallet not configured here.`);
|
|
55
|
+
}
|
|
15
56
|
if (result.status === "processing") {
|
|
16
57
|
return text(`Job ${job_id} is still processing...`);
|
|
17
58
|
}
|
|
18
59
|
return text(formatRunResult(result));
|
|
19
60
|
});
|
|
20
|
-
// ── list_jobs
|
|
21
|
-
server.tool("list_jobs", "List your recent jobs with status, cost, and
|
|
22
|
-
limit: z.coerce.number().optional().default(10).describe("Max results (1-50)"),
|
|
61
|
+
// ── list_jobs ───────────────────────────────────────────────────
|
|
62
|
+
server.tool("list_jobs", "List your recent jobs across all configured payment wallets, with status, cost, agent, method, and age.", {
|
|
63
|
+
limit: z.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results (1-50)"),
|
|
23
64
|
}, async ({ limit }) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
65
|
+
const requestedLimit = limit ?? 10;
|
|
66
|
+
const walletLookup = !isAuthenticated() && hasWalletConfigured();
|
|
67
|
+
const addresses = walletLookup ? await getConsumerWalletAddresses() : [""];
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
const collected = [];
|
|
70
|
+
for (const addr of addresses) {
|
|
71
|
+
const url = addr
|
|
72
|
+
? `/jobs?wallet=${encodeURIComponent(addr)}&limit=${requestedLimit}`
|
|
73
|
+
: `/jobs?limit=${requestedLimit}`;
|
|
74
|
+
try {
|
|
75
|
+
const jobs = await apiGet(url);
|
|
76
|
+
for (const j of jobs) {
|
|
77
|
+
const id = j.job_id;
|
|
78
|
+
if (!seen.has(id)) {
|
|
79
|
+
seen.add(id);
|
|
80
|
+
collected.push(j);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Skip address that errored; continue with others.
|
|
30
86
|
}
|
|
31
87
|
}
|
|
32
|
-
|
|
33
|
-
if (jobs.length === 0)
|
|
88
|
+
if (collected.length === 0)
|
|
34
89
|
return text("No jobs found.");
|
|
90
|
+
collected.sort((a, b) => {
|
|
91
|
+
const aTime = new Date(a.created_at ?? 0).getTime();
|
|
92
|
+
const bTime = new Date(b.created_at ?? 0).getTime();
|
|
93
|
+
return bTime - aTime;
|
|
94
|
+
});
|
|
95
|
+
const jobs = collected.slice(0, requestedLimit);
|
|
35
96
|
const lines = [`Recent jobs (${jobs.length}):`];
|
|
36
97
|
for (const j of jobs) {
|
|
37
98
|
const status = j.status === "completed"
|
|
@@ -39,10 +100,15 @@ export function registerJobTools(server) {
|
|
|
39
100
|
: j.status === "processing"
|
|
40
101
|
? "\u2026"
|
|
41
102
|
: "\u2717";
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
103
|
+
const amount = (j.settled_amount ?? j.estimated_cost);
|
|
104
|
+
const cost = amount != null ? `$${Number(amount).toFixed(4)}` : "";
|
|
105
|
+
const method = j.settlement_trace
|
|
106
|
+
?.payment_attempt?.payment_method;
|
|
107
|
+
const methodLabel = method ? `[${method.replace(/_usdc$/, "").replace("stripe_", "")}]` : "";
|
|
108
|
+
const shortId = j.job_id?.slice(0, 8);
|
|
109
|
+
const shortAgent = j.agent_id ? String(j.agent_id).slice(0, 8) : "?";
|
|
110
|
+
const when = formatRelativeTime(j.created_at);
|
|
111
|
+
lines.push(` ${status} ${shortId} agent=${shortAgent} ${cost} ${methodLabel} ${when}`.trimEnd());
|
|
46
112
|
}
|
|
47
113
|
return text(lines.join("\n"));
|
|
48
114
|
});
|
package/dist/tools/passes.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { apiGet, apiPostWithPayment } from "../core/api-client.js";
|
|
3
|
-
import { formatCreditPack, formatCreditPackOffer, getCreditPackProgram, } from "../core/passes.js";
|
|
4
|
-
import { getCompatiblePaymentMethods, getConfiguredMethods, hasWalletConfigured, normalizePaymentMethod, } from "../core/payments.js";
|
|
3
|
+
import { formatCreditPack, formatCreditPackOffer, getCreditPackInventory, getCreditPackProgram, } from "../core/passes.js";
|
|
4
|
+
import { getCompatiblePaymentMethods, getConfiguredMethods, hasWalletConfigured, normalizePaymentMethod, isCardPaymentEnabled, } from "../core/payments.js";
|
|
5
5
|
import { requiresSpendConfirmation } from "../core/config.js";
|
|
6
|
-
import {
|
|
6
|
+
import { ensureConsumerPrincipalForMethod } from "../core/principal.js";
|
|
7
7
|
import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
|
|
8
8
|
import { formatPaymentChoicePrompt, formatPaymentLabel, resolveConfirmationMethod, } from "./_payment-confirmation.js";
|
|
9
9
|
const pendingCreditPackPurchases = new Map();
|
|
@@ -39,19 +39,29 @@ export function registerPassTools(server) {
|
|
|
39
39
|
confirmed: z.boolean().optional().describe("Set to true to confirm the purchase after seeing the quote."),
|
|
40
40
|
}, async ({ agent_id, pack_id, pay_with, confirmed }) => {
|
|
41
41
|
if (!hasWalletConfigured()) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
if (isCardPaymentEnabled()) {
|
|
43
|
+
try {
|
|
44
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
45
|
+
return multiText(...formatCardSetupBlocks(url));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Fall through to the setup message below.
|
|
49
|
+
}
|
|
45
50
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
const setupLines = [
|
|
52
|
+
"No payment method configured.",
|
|
53
|
+
"",
|
|
54
|
+
"Supported rails: Tempo USDC, Base USDC, Solana USDC.",
|
|
55
|
+
"Run wallet_setup({ action: \"create\" }) to create a crypto wallet,",
|
|
56
|
+
"or wallet_setup({ action: \"import\" }) with an existing key.",
|
|
57
|
+
];
|
|
58
|
+
if (isCardPaymentEnabled()) {
|
|
59
|
+
setupLines.push("", "Or wallet_setup({ action: \"add-card\" }) to connect a credit card.");
|
|
50
60
|
}
|
|
61
|
+
return text(setupLines.join("\n"));
|
|
51
62
|
}
|
|
52
63
|
const agent = await getAgent(agent_id);
|
|
53
64
|
const agentName = agent.name ?? agent_id;
|
|
54
|
-
const principal = await ensureConsumerPrincipal();
|
|
55
65
|
const program = getCreditPackProgram(agent);
|
|
56
66
|
const offers = (program?.packs ?? [])
|
|
57
67
|
.map((pack) => findOffer(agent, pack.key ?? ""))
|
|
@@ -102,6 +112,7 @@ export function registerPassTools(server) {
|
|
|
102
112
|
].join("\n"));
|
|
103
113
|
}
|
|
104
114
|
const method = resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
|
|
115
|
+
const principal = await ensureConsumerPrincipalForMethod(method);
|
|
105
116
|
if (requiresSpendConfirmation() && !confirmed) {
|
|
106
117
|
pendingCreditPackPurchases.set(agent.id, {
|
|
107
118
|
agentId: agent.id,
|
|
@@ -131,14 +142,19 @@ export function registerPassTools(server) {
|
|
|
131
142
|
formatCreditPack(result.credit_pack),
|
|
132
143
|
`Consumer principal: ${result.consumer_principal}`,
|
|
133
144
|
"",
|
|
134
|
-
"Future runs
|
|
145
|
+
"Future runs for this agent will automatically use this credit pack while units remain.",
|
|
146
|
+
"That includes run_agent, and solve whenever it selects this same agent.",
|
|
135
147
|
].join("\n"));
|
|
136
148
|
});
|
|
137
149
|
server.tool("list_agent_credit_packs", "Show discounted credit-pack offers for an agent plus any balances available under the current consumer principal.", {
|
|
138
150
|
agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
|
|
139
|
-
|
|
151
|
+
pay_with: z.string().optional().describe("Optional payment method context used to inspect the matching consumer principal."),
|
|
152
|
+
}, async ({ agent_id, pay_with }) => {
|
|
140
153
|
const agent = await getAgent(agent_id);
|
|
141
|
-
const result = await
|
|
154
|
+
const result = await getCreditPackInventory(agent.id, pay_with);
|
|
155
|
+
if (!result) {
|
|
156
|
+
return text(`Could not load credit-pack inventory for ${agent.name}.`);
|
|
157
|
+
}
|
|
142
158
|
const lines = [
|
|
143
159
|
`Credit packs for ${agent.name}`,
|
|
144
160
|
...(result.consumer_principal ? [`Consumer principal: ${result.consumer_principal}`] : []),
|
package/dist/tools/run.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { apiGet, apiPost, apiPostWithPayment } from "../core/api-client.js";
|
|
3
3
|
import { uploadLocalFiles } from "../core/file-upload.js";
|
|
4
4
|
import { formatCreditPackOffer, getActiveCreditPack, getCreditPackInventory, getCreditPackProgram, } from "../core/passes.js";
|
|
5
|
-
import { getCompatiblePaymentMethods, getConfiguredMethods, hasWalletConfigured, getWalletAddress, normalizePaymentMethod, } from "../core/payments.js";
|
|
5
|
+
import { getCompatiblePaymentMethods, getConfiguredMethods, hasWalletConfigured, getWalletAddress, normalizePaymentMethod, isCardPaymentEnabled, } from "../core/payments.js";
|
|
6
6
|
import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
|
|
7
7
|
import { formatRunResult } from "../core/formatters.js";
|
|
8
8
|
import { canSpend, recordSpend, requiresPolicyConfirmation } from "../core/spend-policy.js";
|
|
@@ -11,9 +11,9 @@ import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card
|
|
|
11
11
|
import { formatPaymentLabel, formatRunConfirmationCommand, resolveConfirmationMethod, } from "./_payment-confirmation.js";
|
|
12
12
|
const POLL_INTERVAL_MS = 3000;
|
|
13
13
|
const POLL_MAX_MS = 120000;
|
|
14
|
-
async function pollJobUntilDone(jobId) {
|
|
14
|
+
async function pollJobUntilDone(jobId, paymentMethod) {
|
|
15
15
|
const deadline = Date.now() + POLL_MAX_MS;
|
|
16
|
-
const walletAddress = await getWalletAddress();
|
|
16
|
+
const walletAddress = await getWalletAddress(paymentMethod);
|
|
17
17
|
const walletParam = walletAddress ? `?wallet=${walletAddress}` : "";
|
|
18
18
|
while (Date.now() < deadline) {
|
|
19
19
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
@@ -61,19 +61,30 @@ export function registerRunTools(server) {
|
|
|
61
61
|
server.tool("run_agent", "Run an AI agent from the marketplace. Pays automatically via configured wallet. Returns the agent's output, cost, and job ID for tracking. If spending confirmation is enabled, first call returns a price quote — call again with confirmed: true to execute. Local file paths in the input (e.g. /Users/.../photo.jpg) are automatically uploaded to temporary storage and replaced with download URLs before execution.", {
|
|
62
62
|
agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
|
|
63
63
|
input: z.record(z.unknown()).describe("Input payload for the agent"),
|
|
64
|
-
pay_with: z.string().trim().min(1).describe("
|
|
64
|
+
pay_with: z.string().trim().min(1).optional().describe("Payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Auto-detected if omitted."),
|
|
65
65
|
confirmed: z.boolean().optional().describe("Set to true to confirm spending after seeing the price quote."),
|
|
66
66
|
}, async ({ agent_id, input, pay_with, confirmed }) => {
|
|
67
67
|
if (!hasWalletConfigured()) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
if (isCardPaymentEnabled()) {
|
|
69
|
+
try {
|
|
70
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
71
|
+
return multiText(...formatCardSetupBlocks(url));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Fall through to the setup message below.
|
|
75
|
+
}
|
|
71
76
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
const setupLines = [
|
|
78
|
+
"No payment method configured.",
|
|
79
|
+
"",
|
|
80
|
+
"Supported rails: Tempo USDC, Base USDC, Solana USDC.",
|
|
81
|
+
"Run wallet_setup({ action: \"create\" }) to create a crypto wallet,",
|
|
82
|
+
"or wallet_setup({ action: \"import\" }) with an existing key.",
|
|
83
|
+
];
|
|
84
|
+
if (isCardPaymentEnabled()) {
|
|
85
|
+
setupLines.push("", "Or wallet_setup({ action: \"add-card\" }) to connect a credit card.");
|
|
76
86
|
}
|
|
87
|
+
return text(setupLines.join("\n"));
|
|
77
88
|
}
|
|
78
89
|
let agent;
|
|
79
90
|
try {
|
|
@@ -84,23 +95,27 @@ export function registerRunTools(server) {
|
|
|
84
95
|
}
|
|
85
96
|
const price = parseFloat(agent.pricePerRunUsd ?? "0.01");
|
|
86
97
|
const agentName = agent.name ?? agent_id;
|
|
87
|
-
const creditPackInventory = await getCreditPackInventory(agent.id);
|
|
88
|
-
const activeCreditPack = getActiveCreditPack(creditPackInventory);
|
|
89
98
|
const compatibleMethods = getCompatiblePaymentMethods(agent, getConfiguredMethods());
|
|
90
99
|
const pending = pendingRuns.get(agent.id);
|
|
91
|
-
const requestedMethod = pay_with;
|
|
92
|
-
const normalizedRequestedMethod = normalizePaymentMethod(requestedMethod);
|
|
93
|
-
|
|
100
|
+
const requestedMethod = pay_with ?? pending?.method;
|
|
101
|
+
const normalizedRequestedMethod = requestedMethod ? normalizePaymentMethod(requestedMethod) : null;
|
|
102
|
+
const creditPackInventory = await getCreditPackInventory(agent.id, requestedMethod);
|
|
103
|
+
const activeCreditPack = getActiveCreditPack(creditPackInventory);
|
|
104
|
+
if (requestedMethod && !normalizedRequestedMethod) {
|
|
94
105
|
return text(`Payment method "${requestedMethod}" is not configured.\n\n` +
|
|
95
106
|
"Use wallet_status to review your current payment methods.");
|
|
96
107
|
}
|
|
97
|
-
if (!compatibleMethods.includes(normalizedRequestedMethod)) {
|
|
108
|
+
if (normalizedRequestedMethod && !compatibleMethods.includes(normalizedRequestedMethod)) {
|
|
98
109
|
return text(`This agent cannot be paid with "${requestedMethod}".\n\n` +
|
|
99
110
|
`Available payment methods for this agent: ${compatibleMethods.join(", ") || "none"}.\n` +
|
|
100
111
|
"Use get_agent to inspect the agent details or choose another payment method.");
|
|
101
112
|
}
|
|
102
|
-
|
|
103
|
-
|
|
113
|
+
if (!activeCreditPack && compatibleMethods.length === 0) {
|
|
114
|
+
return text(`No compatible payment methods are configured for ${agentName}.\n\n` +
|
|
115
|
+
"Use wallet_status to review your current payment methods.");
|
|
116
|
+
}
|
|
117
|
+
const method = resolveConfirmationMethod(requestedMethod, pending?.method, compatibleMethods);
|
|
118
|
+
const spendCheckMethod = method ?? normalizedRequestedMethod ?? compatibleMethods[0];
|
|
104
119
|
if (!activeCreditPack) {
|
|
105
120
|
const spendCheck = canSpend({
|
|
106
121
|
method: spendCheckMethod,
|
|
@@ -201,7 +216,7 @@ export function registerRunTools(server) {
|
|
|
201
216
|
const status = result.status;
|
|
202
217
|
const usedCreditPack = result.consumption_mode === "credit_pack";
|
|
203
218
|
if (status === "processing") {
|
|
204
|
-
const pollResult = await pollJobUntilDone(jobId);
|
|
219
|
+
const pollResult = await pollJobUntilDone(jobId, method);
|
|
205
220
|
if (pollResult.status === "completed") {
|
|
206
221
|
const asyncFormatted = formatRunResult({
|
|
207
222
|
...result,
|
package/dist/tools/search.js
CHANGED
|
@@ -16,14 +16,15 @@ export function registerSearchTools(server) {
|
|
|
16
16
|
sort: z.enum(["relevance", "price", "rating", "popularity", "newest"]).optional()
|
|
17
17
|
.describe("Sort results by: relevance (default), price, rating, popularity, or newest"),
|
|
18
18
|
}, async ({ query, tag, limit, max_price, min_rating, sort }) => {
|
|
19
|
-
const requestedLimit = limit ?? 10;
|
|
19
|
+
const requestedLimit = Math.max(1, Math.min(50, limit ?? 10));
|
|
20
20
|
const params = new URLSearchParams();
|
|
21
21
|
if (query)
|
|
22
22
|
params.set("q", query);
|
|
23
23
|
if (tag)
|
|
24
24
|
params.set("tag", tag);
|
|
25
|
-
//
|
|
26
|
-
|
|
25
|
+
// min_rating is filtered client-side on avgRating. Request extra candidates
|
|
26
|
+
// so the post-filter result still has a chance of meeting requestedLimit.
|
|
27
|
+
const apiLimit = min_rating ? Math.min(100, requestedLimit * 3) : requestedLimit;
|
|
27
28
|
params.set("limit", String(apiLimit));
|
|
28
29
|
if (max_price)
|
|
29
30
|
params.set("price_max", String(max_price));
|
|
@@ -45,13 +46,13 @@ export function registerSearchTools(server) {
|
|
|
45
46
|
params.set("accepted_payment_methods", acceptedMethods.join(","));
|
|
46
47
|
}
|
|
47
48
|
let agents = await apiGet(`/agents?${params}`);
|
|
48
|
-
// Client-side min_rating filter
|
|
49
|
+
// Client-side min_rating filter — avgRating is the 1-5 user-facing score.
|
|
50
|
+
// Agents with no ratings yet (avgRating == null) are excluded when a
|
|
51
|
+
// minimum is requested, matching user expectation of "at least N stars".
|
|
49
52
|
if (min_rating) {
|
|
50
53
|
agents = agents.filter((a) => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const stars = rating <= 1 ? rating * 5 : rating;
|
|
54
|
-
return stars >= min_rating;
|
|
54
|
+
const avg = a.avgRating ?? a.stats?.avgRating;
|
|
55
|
+
return typeof avg === "number" && avg >= min_rating;
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
// Trim to requested limit after filtering
|