@agentwonderland/mcp 0.1.23 → 0.1.25
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__/card-setup.test.d.ts +1 -0
- package/dist/core/__tests__/card-setup.test.js +99 -0
- package/dist/core/__tests__/formatters.test.d.ts +1 -0
- package/dist/core/__tests__/formatters.test.js +15 -0
- package/dist/core/__tests__/passes.test.d.ts +1 -0
- package/dist/core/__tests__/passes.test.js +82 -0
- package/dist/core/__tests__/payments.test.d.ts +1 -0
- package/dist/core/__tests__/payments.test.js +101 -0
- package/dist/core/__tests__/principal.test.d.ts +1 -0
- package/dist/core/__tests__/principal.test.js +67 -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 +9 -4
- package/dist/core/api-client.js +52 -22
- package/dist/core/base-charge.js +3 -2
- package/dist/core/card-setup.d.ts +20 -13
- package/dist/core/card-setup.js +85 -29
- package/dist/core/config.d.ts +22 -0
- package/dist/core/config.js +46 -2
- package/dist/core/formatters.d.ts +4 -3
- package/dist/core/formatters.js +10 -8
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/ows-adapter.d.ts +10 -2
- package/dist/core/ows-adapter.js +54 -10
- package/dist/core/passes.d.ts +40 -0
- package/dist/core/passes.js +32 -0
- package/dist/core/payments.d.ts +8 -0
- package/dist/core/payments.js +111 -17
- package/dist/core/principal.d.ts +2 -0
- package/dist/core/principal.js +109 -0
- package/dist/core/solana-charge.d.ts +9 -0
- package/dist/core/solana-charge.js +96 -0
- package/dist/core/spend-policy.d.ts +12 -0
- package/dist/core/spend-policy.js +53 -0
- package/dist/core/types.d.ts +11 -2
- package/dist/index.js +11 -3
- package/dist/prompts/index.js +4 -2
- package/dist/resources/agents.js +1 -1
- package/dist/resources/wallet.js +8 -1
- package/dist/tools/__tests__/_payment-confirmation.test.d.ts +1 -0
- package/dist/tools/__tests__/_payment-confirmation.test.js +30 -0
- package/dist/tools/_payment-confirmation.d.ts +6 -0
- package/dist/tools/_payment-confirmation.js +28 -0
- package/dist/tools/agent-info.js +16 -2
- package/dist/tools/favorites.js +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/observability.d.ts +2 -0
- package/dist/tools/observability.js +20 -0
- package/dist/tools/passes.d.ts +2 -0
- package/dist/tools/passes.js +157 -0
- package/dist/tools/run.js +127 -53
- package/dist/tools/solve.js +115 -51
- package/dist/tools/wallet.js +110 -59
- package/package.json +3 -1
- package/src/core/__tests__/amount-utils.test.ts +13 -0
- package/src/core/__tests__/card-setup.test.ts +118 -0
- package/src/core/__tests__/formatters.test.ts +17 -0
- package/src/core/__tests__/passes.test.ts +94 -0
- package/src/core/__tests__/payments.test.ts +122 -0
- package/src/core/__tests__/principal.test.ts +87 -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 +70 -23
- package/src/core/base-charge.ts +3 -2
- package/src/core/card-setup.ts +109 -34
- package/src/core/config.ts +74 -3
- package/src/core/formatters.ts +13 -9
- package/src/core/index.ts +2 -0
- package/src/core/ows-adapter.ts +74 -8
- package/src/core/passes.ts +74 -0
- package/src/core/payments.ts +130 -17
- package/src/core/principal.ts +128 -0
- package/src/core/solana-charge.ts +150 -0
- package/src/core/spend-policy.ts +69 -0
- package/src/core/types.ts +11 -2
- package/src/index.ts +11 -3
- package/src/prompts/index.ts +4 -2
- package/src/resources/agents.ts +1 -1
- package/src/resources/wallet.ts +8 -1
- package/src/tools/__tests__/_payment-confirmation.test.ts +45 -0
- package/src/tools/_payment-confirmation.ts +52 -0
- package/src/tools/agent-info.ts +25 -2
- package/src/tools/favorites.ts +1 -4
- package/src/tools/index.ts +1 -0
- package/src/tools/observability.ts +43 -0
- package/src/tools/passes.ts +228 -0
- package/src/tools/run.ts +174 -57
- package/src/tools/solve.ts +147 -59
- package/src/tools/wallet.ts +132 -62
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { formatPaymentChoicePrompt, formatRunConfirmationCommand, formatSolveConfirmationCommand, makeSolvePendingKey, resolveConfirmationMethod, } from "../_payment-confirmation.js";
|
|
3
|
+
describe("payment confirmation helpers", () => {
|
|
4
|
+
it("prefers the explicit method, then pending, then configured default", () => {
|
|
5
|
+
expect(resolveConfirmationMethod("card", "tempo", ["tempo", "card"])).toBe("card");
|
|
6
|
+
expect(resolveConfirmationMethod(undefined, "card", ["tempo", "card"])).toBe("card");
|
|
7
|
+
expect(resolveConfirmationMethod(undefined, undefined, ["card", "tempo"])).toBe("card");
|
|
8
|
+
});
|
|
9
|
+
it("includes pay_with in run confirmation commands when present", () => {
|
|
10
|
+
expect(formatRunConfirmationCommand("agent_123", "card")).toContain('pay_with: "card"');
|
|
11
|
+
expect(formatRunConfirmationCommand("agent_123", undefined)).not.toContain("pay_with");
|
|
12
|
+
});
|
|
13
|
+
it("includes pay_with in solve confirmation commands when present", () => {
|
|
14
|
+
expect(formatSolveConfirmationCommand("translate", 1, "card")).toContain('pay_with: "card"');
|
|
15
|
+
expect(formatSolveConfirmationCommand("translate", 1, undefined)).not.toContain("pay_with");
|
|
16
|
+
});
|
|
17
|
+
it("uses a stable solve pending key for the same request", () => {
|
|
18
|
+
const input = { text: "hello", target_language: "es" };
|
|
19
|
+
expect(makeSolvePendingKey("translate", input, 1)).toBe(makeSolvePendingKey("translate", input, 1));
|
|
20
|
+
});
|
|
21
|
+
it("formats a payment choice prompt with explicit options", () => {
|
|
22
|
+
const prompt = formatPaymentChoicePrompt("Echo Agent", ["card", "tempo"], [
|
|
23
|
+
' run_agent({ agent_id: "agent_123", input: <same>, pay_with: "card" })',
|
|
24
|
+
' run_agent({ agent_id: "agent_123", input: <same>, pay_with: "tempo" })',
|
|
25
|
+
]);
|
|
26
|
+
expect(prompt).toContain("Multiple payment methods are available for Echo Agent.");
|
|
27
|
+
expect(prompt).toContain('Available methods: "card", "tempo"');
|
|
28
|
+
expect(prompt).toContain('pay_with: "card"');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function resolveConfirmationMethod(requestedMethod: string | undefined, pendingMethod: string | undefined, compatibleMethods: string[]): string | undefined;
|
|
2
|
+
export declare function formatPaymentLabel(method: string | undefined): string;
|
|
3
|
+
export declare function formatRunConfirmationCommand(agentId: string, method: string | undefined): string;
|
|
4
|
+
export declare function formatSolveConfirmationCommand(intent: string, budget: number, method: string | undefined): string;
|
|
5
|
+
export declare function makeSolvePendingKey(intent: string, input: Record<string, unknown>, budget: number): string;
|
|
6
|
+
export declare function formatPaymentChoicePrompt(subject: string, methods: string[], commands: string[]): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function resolveConfirmationMethod(requestedMethod, pendingMethod, compatibleMethods) {
|
|
2
|
+
return requestedMethod ?? pendingMethod ?? compatibleMethods[0];
|
|
3
|
+
}
|
|
4
|
+
export function formatPaymentLabel(method) {
|
|
5
|
+
return method ?? "auto";
|
|
6
|
+
}
|
|
7
|
+
export function formatRunConfirmationCommand(agentId, method) {
|
|
8
|
+
const payWith = method ? `, pay_with: "${method}"` : "";
|
|
9
|
+
return ` run_agent({ agent_id: "${agentId}", input: <same>${payWith}, confirmed: true })`;
|
|
10
|
+
}
|
|
11
|
+
export function formatSolveConfirmationCommand(intent, budget, method) {
|
|
12
|
+
const payWith = method ? `, pay_with: "${method}"` : "";
|
|
13
|
+
return ` solve({ intent: "${intent}", input: <same>, budget: ${budget}${payWith}, confirmed: true })`;
|
|
14
|
+
}
|
|
15
|
+
export function makeSolvePendingKey(intent, input, budget) {
|
|
16
|
+
return JSON.stringify({ intent, input, budget });
|
|
17
|
+
}
|
|
18
|
+
export function formatPaymentChoicePrompt(subject, methods, commands) {
|
|
19
|
+
return [
|
|
20
|
+
`Multiple payment methods are available for ${subject}.`,
|
|
21
|
+
"",
|
|
22
|
+
"Ask the user which payment method they want to use, then call one of:",
|
|
23
|
+
...commands,
|
|
24
|
+
"",
|
|
25
|
+
`Available methods: ${methods.map((method) => `"${method}"`).join(", ")}`,
|
|
26
|
+
"For fully agentic execution, include pay_with explicitly.",
|
|
27
|
+
].join("\n");
|
|
28
|
+
}
|
package/dist/tools/agent-info.js
CHANGED
|
@@ -13,13 +13,14 @@ export function registerAgentInfoTools(server) {
|
|
|
13
13
|
const s = (a.stats ?? {});
|
|
14
14
|
const payment = (a.payment ?? {});
|
|
15
15
|
const _pricing = (payment.pricing ?? {});
|
|
16
|
+
const creditPacks = payment.credit_packs;
|
|
16
17
|
const lines = [
|
|
17
18
|
`${a.name}`,
|
|
18
19
|
`${stars(a.avgRating ?? s.avgRating)} (${s.ratingCount ?? 0} reviews) • ${compactNumber((s.completedJobs ?? a.totalExecutions ?? 0))} jobs`,
|
|
19
20
|
"",
|
|
20
21
|
a.description ?? "",
|
|
21
22
|
"",
|
|
22
|
-
`Pricing: ${formatPrice(a.
|
|
23
|
+
`Pricing: ${formatPrice(a.pricePerRunUsd)}`,
|
|
23
24
|
`Reliability: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
|
|
24
25
|
`Avg latency: ${a.avgResponseTimeMs != null ? a.avgResponseTimeMs + "ms" : "N/A"}`,
|
|
25
26
|
...(() => {
|
|
@@ -32,6 +33,19 @@ export function registerAgentInfoTools(server) {
|
|
|
32
33
|
const hint = outputTypeHint(a.tags);
|
|
33
34
|
return hint ? [`Output: ${hint}`] : [];
|
|
34
35
|
})(),
|
|
36
|
+
...(() => {
|
|
37
|
+
if (!creditPacks?.packs?.length)
|
|
38
|
+
return [];
|
|
39
|
+
const packLines = [
|
|
40
|
+
"",
|
|
41
|
+
`Discounted credit packs: ${creditPacks.unit_type ?? "run"}s`,
|
|
42
|
+
];
|
|
43
|
+
for (const pack of creditPacks.packs) {
|
|
44
|
+
packLines.push(` ${pack.name ?? pack.key}: ${pack.included_units ?? 0} units for $${pack.price_usd ?? "0.00"}`);
|
|
45
|
+
}
|
|
46
|
+
packLines.push(" Use buy_agent_credit_pack to purchase one.");
|
|
47
|
+
return packLines;
|
|
48
|
+
})(),
|
|
35
49
|
];
|
|
36
50
|
// Feedback summary (rating + tips)
|
|
37
51
|
const feedbackSummary = formatFeedbackSummary(s);
|
|
@@ -86,7 +100,7 @@ export function registerAgentInfoTools(server) {
|
|
|
86
100
|
return [
|
|
87
101
|
` ${a.name}`,
|
|
88
102
|
` ${stars(rating)} (${s.ratingCount ?? 0} reviews)${tipCount > 0 ? ` • ${tipCount} tips` : ""}`,
|
|
89
|
-
` ${compactNumber(jobs)} jobs • ${formatPrice(a.
|
|
103
|
+
` ${compactNumber(jobs)} jobs • ${formatPrice(a.pricePerRunUsd)}`,
|
|
90
104
|
` Success: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
|
|
91
105
|
` ${agentWebUrl(a.id)}`,
|
|
92
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/index.d.ts
CHANGED
package/dist/tools/index.js
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { apiPost } from "../core/api-client.js";
|
|
2
|
+
function text(t) {
|
|
3
|
+
return { content: [{ type: "text", text: t }] };
|
|
4
|
+
}
|
|
5
|
+
export function registerObservabilityTools(server) {
|
|
6
|
+
server.tool("open_observability_dashboard", "Generate a secure one-click sign-in URL for the Agent Wonderland web observability dashboard. The dashboard shows your agent runs, spend, rebates, and recent activity.", {}, async () => {
|
|
7
|
+
const result = await apiPost("/observability/link", {}, { ensureConsumerPrincipal: true });
|
|
8
|
+
const lines = [
|
|
9
|
+
"Your secure observability link is ready:",
|
|
10
|
+
result.url,
|
|
11
|
+
"",
|
|
12
|
+
`Expires: ${result.expires_at}`,
|
|
13
|
+
];
|
|
14
|
+
if (result.consumer_principal) {
|
|
15
|
+
lines.push(`Consumer principal: ${result.consumer_principal}`);
|
|
16
|
+
}
|
|
17
|
+
lines.push("", "Open the link in your browser to view usage metrics, spend, rebates, and recent runs.");
|
|
18
|
+
return text(lines.join("\n"));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
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";
|
|
5
|
+
import { requiresSpendConfirmation } from "../core/config.js";
|
|
6
|
+
import { ensureConsumerPrincipal } from "../core/principal.js";
|
|
7
|
+
import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
|
|
8
|
+
import { formatPaymentChoicePrompt, formatPaymentLabel, resolveConfirmationMethod, } from "./_payment-confirmation.js";
|
|
9
|
+
const pendingCreditPackPurchases = new Map();
|
|
10
|
+
function text(t) {
|
|
11
|
+
return { content: [{ type: "text", text: t }] };
|
|
12
|
+
}
|
|
13
|
+
function multiText(...blocks) {
|
|
14
|
+
return { content: blocks.map((t) => ({ type: "text", text: t })) };
|
|
15
|
+
}
|
|
16
|
+
async function getAgent(agentId) {
|
|
17
|
+
return apiGet(`/agents/${agentId}`);
|
|
18
|
+
}
|
|
19
|
+
function findOffer(agent, packId) {
|
|
20
|
+
const program = getCreditPackProgram(agent);
|
|
21
|
+
const pack = program?.packs?.find((candidate) => candidate.key === packId);
|
|
22
|
+
if (!pack)
|
|
23
|
+
return null;
|
|
24
|
+
const price = Number(pack.price_usd ?? "0");
|
|
25
|
+
const units = pack.included_units ?? 0;
|
|
26
|
+
return {
|
|
27
|
+
pack_id: packId,
|
|
28
|
+
label: pack.name ?? pack.key ?? "Credit Pack",
|
|
29
|
+
included_units: units,
|
|
30
|
+
price_usd: price.toFixed(2),
|
|
31
|
+
effective_price_per_unit_usd: units > 0 ? (price / units).toFixed(6) : undefined,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function registerPassTools(server) {
|
|
35
|
+
server.tool("buy_agent_credit_pack", "Purchase a discounted prepaid credit pack for an agent. Credit packs are agent-specific and automatically cover future runs until the included units run out.", {
|
|
36
|
+
agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
|
|
37
|
+
pack_id: z.string().optional().describe("Specific pack key to buy, like 'starter' or 'growth'. If omitted and only one pack exists, it is selected automatically."),
|
|
38
|
+
pay_with: z.string().optional().describe("Payment method — wallet ID, chain name, or 'card'. Auto-detected if omitted."),
|
|
39
|
+
confirmed: z.boolean().optional().describe("Set to true to confirm the purchase after seeing the quote."),
|
|
40
|
+
}, async ({ agent_id, pack_id, pay_with, confirmed }) => {
|
|
41
|
+
if (!hasWalletConfigured()) {
|
|
42
|
+
try {
|
|
43
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
44
|
+
return multiText(...formatCardSetupBlocks(url));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return text("No payment method configured.\n\n" +
|
|
48
|
+
"To add a credit card: wallet_setup({ action: \"add-card\" })\n" +
|
|
49
|
+
"To use crypto: wallet_setup({ action: \"create\" })");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const agent = await getAgent(agent_id);
|
|
53
|
+
const agentName = agent.name ?? agent_id;
|
|
54
|
+
const principal = await ensureConsumerPrincipal();
|
|
55
|
+
const program = getCreditPackProgram(agent);
|
|
56
|
+
const offers = (program?.packs ?? [])
|
|
57
|
+
.map((pack) => findOffer(agent, pack.key ?? ""))
|
|
58
|
+
.filter((offer) => !!offer);
|
|
59
|
+
if (offers.length === 0) {
|
|
60
|
+
return text(`${agentName} does not currently offer discounted credit packs.`);
|
|
61
|
+
}
|
|
62
|
+
const configuredMethods = getConfiguredMethods();
|
|
63
|
+
const compatibleMethods = getCompatiblePaymentMethods(agent, configuredMethods);
|
|
64
|
+
const pending = pendingCreditPackPurchases.get(agent.id);
|
|
65
|
+
const requestedMethod = pay_with ?? pending?.method;
|
|
66
|
+
const normalizedRequestedMethod = requestedMethod ? normalizePaymentMethod(requestedMethod) : null;
|
|
67
|
+
if (requestedMethod && !normalizedRequestedMethod) {
|
|
68
|
+
return text(`Payment method "${requestedMethod}" is not configured.\n\n` +
|
|
69
|
+
"Use wallet_status to review your current payment methods.");
|
|
70
|
+
}
|
|
71
|
+
if (normalizedRequestedMethod && !compatibleMethods.includes(normalizedRequestedMethod)) {
|
|
72
|
+
return text(`This agent cannot be paid with "${requestedMethod}".\n\n` +
|
|
73
|
+
`Available payment methods for this agent: ${compatibleMethods.join(", ") || "none"}.`);
|
|
74
|
+
}
|
|
75
|
+
if (!requestedMethod && compatibleMethods.length === 0) {
|
|
76
|
+
return text(`No compatible payment methods are configured for ${agentName}.\n\n` +
|
|
77
|
+
`Your configured methods: ${configuredMethods.join(", ") || "none"}`);
|
|
78
|
+
}
|
|
79
|
+
if (!requestedMethod && compatibleMethods.length > 1) {
|
|
80
|
+
return text(formatPaymentChoicePrompt(`${agentName} credit-pack purchase`, compatibleMethods, compatibleMethods.map((method) => ` buy_agent_credit_pack({ agent_id: "${agent.id}"${pack_id ? `, pack_id: "${pack_id}"` : ""}, pay_with: "${method}" })`)));
|
|
81
|
+
}
|
|
82
|
+
let resolvedOffer;
|
|
83
|
+
if (pack_id) {
|
|
84
|
+
const offer = offers.find((candidate) => candidate.pack_id === pack_id);
|
|
85
|
+
if (!offer) {
|
|
86
|
+
return text(`Unknown credit pack "${pack_id}" for ${agentName}.\n\n` +
|
|
87
|
+
`Available packs:\n${offers.map((offer) => ` ${formatCreditPackOffer(offer)}`).join("\n")}`);
|
|
88
|
+
}
|
|
89
|
+
resolvedOffer = offer;
|
|
90
|
+
}
|
|
91
|
+
else if (offers.length === 1) {
|
|
92
|
+
resolvedOffer = offers[0];
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
return text([
|
|
96
|
+
`Multiple credit packs are available for ${agentName}.`,
|
|
97
|
+
"",
|
|
98
|
+
...offers.map((offer) => ` ${offer.pack_id}: ${formatCreditPackOffer(offer)}`),
|
|
99
|
+
"",
|
|
100
|
+
"Choose one by calling:",
|
|
101
|
+
` buy_agent_credit_pack({ agent_id: "${agent.id}", pack_id: "${offers[0].pack_id}" })`,
|
|
102
|
+
].join("\n"));
|
|
103
|
+
}
|
|
104
|
+
const method = resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
|
|
105
|
+
if (requiresSpendConfirmation() && !confirmed) {
|
|
106
|
+
pendingCreditPackPurchases.set(agent.id, {
|
|
107
|
+
agentId: agent.id,
|
|
108
|
+
agentName,
|
|
109
|
+
offer: resolvedOffer,
|
|
110
|
+
method,
|
|
111
|
+
});
|
|
112
|
+
return text([
|
|
113
|
+
`Ready to buy a credit pack for ${agentName}`,
|
|
114
|
+
"",
|
|
115
|
+
formatCreditPackOffer(resolvedOffer),
|
|
116
|
+
`Payment: ${formatPaymentLabel(method)}`,
|
|
117
|
+
`Consumer principal: ${principal}`,
|
|
118
|
+
"",
|
|
119
|
+
"To proceed, call:",
|
|
120
|
+
` buy_agent_credit_pack({ agent_id: "${agent.id}", pack_id: "${resolvedOffer.pack_id}", pay_with: "${method}", confirmed: true })`,
|
|
121
|
+
"",
|
|
122
|
+
"To cancel, do nothing.",
|
|
123
|
+
].join("\n"));
|
|
124
|
+
}
|
|
125
|
+
const result = await apiPostWithPayment(`/agents/${agent.id}/credit-packs/purchase`, { pack_id: resolvedOffer.pack_id }, method, { ensureConsumerPrincipal: true });
|
|
126
|
+
pendingCreditPackPurchases.delete(agent.id);
|
|
127
|
+
return text([
|
|
128
|
+
`Credit pack purchased for ${agentName}`,
|
|
129
|
+
"",
|
|
130
|
+
formatCreditPackOffer(result.offer),
|
|
131
|
+
formatCreditPack(result.credit_pack),
|
|
132
|
+
`Consumer principal: ${result.consumer_principal}`,
|
|
133
|
+
"",
|
|
134
|
+
"Future runs through run_agent will automatically use this credit pack while units remain.",
|
|
135
|
+
].join("\n"));
|
|
136
|
+
});
|
|
137
|
+
server.tool("list_agent_credit_packs", "Show discounted credit-pack offers for an agent plus any balances available under the current consumer principal.", {
|
|
138
|
+
agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
|
|
139
|
+
}, async ({ agent_id }) => {
|
|
140
|
+
const agent = await getAgent(agent_id);
|
|
141
|
+
const result = await apiGet(`/agents/${agent.id}/credit-packs`, { ensureConsumerPrincipal: true });
|
|
142
|
+
const lines = [
|
|
143
|
+
`Credit packs for ${agent.name}`,
|
|
144
|
+
...(result.consumer_principal ? [`Consumer principal: ${result.consumer_principal}`] : []),
|
|
145
|
+
];
|
|
146
|
+
if (result.offers.length > 0) {
|
|
147
|
+
lines.push("", "Available packs:", ...result.offers.map((offer) => ` ${offer.pack_id}: ${formatCreditPackOffer(offer)}`));
|
|
148
|
+
}
|
|
149
|
+
if (result.balances.length > 0) {
|
|
150
|
+
lines.push("", "Your balances:", ...result.balances.map((pack) => ` ${formatCreditPack(pack)}`));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
lines.push("", "No purchased credit packs found for this agent.");
|
|
154
|
+
}
|
|
155
|
+
return text(lines.join("\n"));
|
|
156
|
+
});
|
|
157
|
+
}
|
package/dist/tools/run.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
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
|
-
import {
|
|
4
|
+
import { formatCreditPackOffer, getActiveCreditPack, getCreditPackInventory, getCreditPackProgram, } from "../core/passes.js";
|
|
5
|
+
import { getCompatiblePaymentMethods, getConfiguredMethods, hasWalletConfigured, getWalletAddress, normalizePaymentMethod, } from "../core/payments.js";
|
|
5
6
|
import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
|
|
6
7
|
import { formatRunResult } from "../core/formatters.js";
|
|
8
|
+
import { canSpend, recordSpend, requiresPolicyConfirmation } from "../core/spend-policy.js";
|
|
7
9
|
import { storeFeedbackToken } from "./_token-cache.js";
|
|
8
|
-
import {
|
|
10
|
+
import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
|
|
11
|
+
import { formatPaymentLabel, formatRunConfirmationCommand, resolveConfirmationMethod, } from "./_payment-confirmation.js";
|
|
9
12
|
const POLL_INTERVAL_MS = 3000;
|
|
10
|
-
const POLL_MAX_MS = 120000;
|
|
11
|
-
async function pollJobUntilDone(jobId
|
|
13
|
+
const POLL_MAX_MS = 120000;
|
|
14
|
+
async function pollJobUntilDone(jobId) {
|
|
12
15
|
const deadline = Date.now() + POLL_MAX_MS;
|
|
13
16
|
const walletAddress = await getWalletAddress();
|
|
14
17
|
const walletParam = walletAddress ? `?wallet=${walletAddress}` : "";
|
|
@@ -22,10 +25,9 @@ async function pollJobUntilDone(jobId, agentName) {
|
|
|
22
25
|
if (job.status === "failed") {
|
|
23
26
|
return { status: "failed", output: job.output, error_code: job.error_code };
|
|
24
27
|
}
|
|
25
|
-
// Still processing — continue polling
|
|
26
28
|
}
|
|
27
29
|
catch {
|
|
28
|
-
// Ignore poll errors
|
|
30
|
+
// Ignore poll errors until timeout.
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
33
|
return { status: "failed", error_code: "POLL_TIMEOUT" };
|
|
@@ -36,20 +38,36 @@ function text(t) {
|
|
|
36
38
|
function multiText(...blocks) {
|
|
37
39
|
return { content: blocks.map((t) => ({ type: "text", text: t })) };
|
|
38
40
|
}
|
|
39
|
-
// Pending confirmations: agent_id → { agent, input, method }
|
|
40
41
|
const pendingRuns = new Map();
|
|
42
|
+
function buildCreditPackOfferLines(agent) {
|
|
43
|
+
const program = getCreditPackProgram(agent);
|
|
44
|
+
if (!program?.packs?.length)
|
|
45
|
+
return [];
|
|
46
|
+
return [
|
|
47
|
+
"Discounted credit packs:",
|
|
48
|
+
...program.packs.map((pack) => formatCreditPackOffer({
|
|
49
|
+
pack_id: pack.key ?? "",
|
|
50
|
+
label: pack.name ?? pack.key ?? "Credit Pack",
|
|
51
|
+
included_units: pack.included_units ?? 0,
|
|
52
|
+
price_usd: pack.price_usd ?? "0.00",
|
|
53
|
+
effective_price_per_unit_usd: (pack.included_units ?? 0) > 0 && pack.price_usd
|
|
54
|
+
? (Number(pack.price_usd) / (pack.included_units ?? 1)).toFixed(6)
|
|
55
|
+
: undefined,
|
|
56
|
+
})).map((line) => ` ${line}`),
|
|
57
|
+
` Buy one with buy_agent_credit_pack({ agent_id: "${agent.id}", pack_id: "<pack_id>" })`,
|
|
58
|
+
];
|
|
59
|
+
}
|
|
41
60
|
export function registerRunTools(server) {
|
|
42
|
-
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
|
|
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.", {
|
|
43
62
|
agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
|
|
44
63
|
input: z.record(z.unknown()).describe("Input payload for the agent"),
|
|
45
|
-
pay_with: z.string().
|
|
64
|
+
pay_with: z.string().trim().min(1).describe("Required payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Use wallet_status to see configured options."),
|
|
46
65
|
confirmed: z.boolean().optional().describe("Set to true to confirm spending after seeing the price quote."),
|
|
47
66
|
}, async ({ agent_id, input, pay_with, confirmed }) => {
|
|
48
67
|
if (!hasWalletConfigured()) {
|
|
49
68
|
try {
|
|
50
|
-
const {
|
|
51
|
-
|
|
52
|
-
return multiText(...blocks);
|
|
69
|
+
const { url } = await getOrCreatePendingCardSetup();
|
|
70
|
+
return multiText(...formatCardSetupBlocks(url));
|
|
53
71
|
}
|
|
54
72
|
catch {
|
|
55
73
|
return text("No payment method configured.\n\n" +
|
|
@@ -57,7 +75,6 @@ export function registerRunTools(server) {
|
|
|
57
75
|
"To use crypto: wallet_setup({ action: \"create\" })");
|
|
58
76
|
}
|
|
59
77
|
}
|
|
60
|
-
// Resolve agent and fetch details
|
|
61
78
|
let agent;
|
|
62
79
|
try {
|
|
63
80
|
agent = await apiGet(`/agents/${agent_id}`);
|
|
@@ -65,28 +82,54 @@ export function registerRunTools(server) {
|
|
|
65
82
|
catch {
|
|
66
83
|
return text(`Agent "${agent_id}" not found. Use search_agents to find available agents.`);
|
|
67
84
|
}
|
|
68
|
-
const price = parseFloat(agent.
|
|
85
|
+
const price = parseFloat(agent.pricePerRunUsd ?? "0.01");
|
|
69
86
|
const agentName = agent.name ?? agent_id;
|
|
70
|
-
|
|
71
|
-
|
|
87
|
+
const creditPackInventory = await getCreditPackInventory(agent.id);
|
|
88
|
+
const activeCreditPack = getActiveCreditPack(creditPackInventory);
|
|
89
|
+
const compatibleMethods = getCompatiblePaymentMethods(agent, getConfiguredMethods());
|
|
90
|
+
const pending = pendingRuns.get(agent.id);
|
|
91
|
+
const requestedMethod = pay_with;
|
|
92
|
+
const normalizedRequestedMethod = normalizePaymentMethod(requestedMethod);
|
|
93
|
+
if (!normalizedRequestedMethod) {
|
|
94
|
+
return text(`Payment method "${requestedMethod}" is not configured.\n\n` +
|
|
95
|
+
"Use wallet_status to review your current payment methods.");
|
|
96
|
+
}
|
|
97
|
+
if (!compatibleMethods.includes(normalizedRequestedMethod)) {
|
|
98
|
+
return text(`This agent cannot be paid with "${requestedMethod}".\n\n` +
|
|
99
|
+
`Available payment methods for this agent: ${compatibleMethods.join(", ") || "none"}.\n` +
|
|
100
|
+
"Use get_agent to inspect the agent details or choose another payment method.");
|
|
101
|
+
}
|
|
102
|
+
const method = resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
|
|
103
|
+
const spendCheckMethod = method ?? normalizedRequestedMethod;
|
|
104
|
+
if (!activeCreditPack) {
|
|
105
|
+
const spendCheck = canSpend({
|
|
106
|
+
method: spendCheckMethod,
|
|
107
|
+
amountUsd: price,
|
|
108
|
+
});
|
|
109
|
+
if (!spendCheck.ok) {
|
|
110
|
+
return text(spendCheck.message);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const needsPolicyConfirmation = !activeCreditPack && requiresPolicyConfirmation(spendCheckMethod, price);
|
|
114
|
+
if (!activeCreditPack && (requiresSpendConfirmation() || needsPolicyConfirmation) && !confirmed) {
|
|
72
115
|
pendingRuns.set(agent.id, {
|
|
73
116
|
agent: { id: agent.id, name: agentName, price },
|
|
74
117
|
input,
|
|
75
|
-
method
|
|
118
|
+
method,
|
|
76
119
|
});
|
|
77
|
-
|
|
120
|
+
const quoteLines = [
|
|
78
121
|
`Ready to run ${agentName}`,
|
|
79
122
|
"",
|
|
80
123
|
` Cost: $${price.toFixed(2)}`,
|
|
81
|
-
` Payment: ${
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"",
|
|
86
|
-
|
|
87
|
-
|
|
124
|
+
` Payment: ${formatPaymentLabel(method)}`,
|
|
125
|
+
];
|
|
126
|
+
const creditPackLines = buildCreditPackOfferLines(agent);
|
|
127
|
+
if (creditPackLines.length > 0) {
|
|
128
|
+
quoteLines.push("", ...creditPackLines);
|
|
129
|
+
}
|
|
130
|
+
quoteLines.push("", "To proceed, call:", formatRunConfirmationCommand(agent.id, method), "", "To cancel, do nothing.");
|
|
131
|
+
return text(quoteLines.join("\n"));
|
|
88
132
|
}
|
|
89
|
-
const method = pay_with;
|
|
90
133
|
let processedInput;
|
|
91
134
|
let uploadSummary = "";
|
|
92
135
|
try {
|
|
@@ -94,7 +137,7 @@ export function registerRunTools(server) {
|
|
|
94
137
|
processedInput = uploadResult.input;
|
|
95
138
|
if (uploadResult.uploads.length > 0) {
|
|
96
139
|
uploadSummary = uploadResult.uploads
|
|
97
|
-
.map((
|
|
140
|
+
.map((upload) => `Uploaded ${upload.field}: ${upload.originalPath} → ${upload.url}`)
|
|
98
141
|
.join("\n");
|
|
99
142
|
}
|
|
100
143
|
}
|
|
@@ -103,18 +146,41 @@ export function registerRunTools(server) {
|
|
|
103
146
|
return text(`Error: ${msg}`);
|
|
104
147
|
}
|
|
105
148
|
let result;
|
|
149
|
+
let usedPaidMethod = !activeCreditPack;
|
|
106
150
|
try {
|
|
107
|
-
|
|
151
|
+
if (activeCreditPack) {
|
|
152
|
+
try {
|
|
153
|
+
result = await apiPost(`/agents/${agent.id}/run`, { input: processedInput }, { ensureConsumerPrincipal: true });
|
|
154
|
+
}
|
|
155
|
+
catch (packErr) {
|
|
156
|
+
const packApiErr = packErr;
|
|
157
|
+
if (packApiErr?.status !== 402)
|
|
158
|
+
throw packErr;
|
|
159
|
+
result = await apiPostWithPayment(`/agents/${agent.id}/run`, { input: processedInput }, method);
|
|
160
|
+
usedPaidMethod = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
result = await apiPostWithPayment(`/agents/${agent.id}/run`, { input: processedInput }, method);
|
|
165
|
+
}
|
|
166
|
+
if (usedPaidMethod) {
|
|
167
|
+
recordSpend(spendCheckMethod, price);
|
|
168
|
+
}
|
|
108
169
|
}
|
|
109
170
|
catch (err) {
|
|
110
171
|
const apiErr = err;
|
|
111
172
|
if (apiErr?.status === 402) {
|
|
112
173
|
const allMethods = getConfiguredMethods();
|
|
113
174
|
const methodName = method ?? allMethods[0] ?? "auto";
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"
|
|
175
|
+
const creditPackLines = buildCreditPackOfferLines(agent);
|
|
176
|
+
return text([
|
|
177
|
+
`Payment failed — "${methodName}" payment was rejected.`,
|
|
178
|
+
"",
|
|
179
|
+
"Check your payment method and try again.",
|
|
180
|
+
...(allMethods.length > 0 ? [`Configured methods: ${allMethods.join(", ")}`] : []),
|
|
181
|
+
"Use wallet_status to check your current payment methods.",
|
|
182
|
+
...(creditPackLines.length > 0 ? ["", ...creditPackLines] : []),
|
|
183
|
+
].join("\n"));
|
|
118
184
|
}
|
|
119
185
|
const msg = apiErr?.message ?? "Failed to run agent";
|
|
120
186
|
if (msg.includes("Missing required field") || msg.includes("validation failed")) {
|
|
@@ -122,7 +188,6 @@ export function registerRunTools(server) {
|
|
|
122
188
|
}
|
|
123
189
|
return text(`Error: ${msg}`);
|
|
124
190
|
}
|
|
125
|
-
// Clean up pending confirmation
|
|
126
191
|
pendingRuns.delete(agent.id);
|
|
127
192
|
const formatted = formatRunResult(result, {
|
|
128
193
|
paymentMethod: method ?? getConfiguredMethods()[0],
|
|
@@ -134,22 +199,23 @@ export function registerRunTools(server) {
|
|
|
134
199
|
}
|
|
135
200
|
const actualCost = result.cost;
|
|
136
201
|
const status = result.status;
|
|
137
|
-
|
|
202
|
+
const usedCreditPack = result.consumption_mode === "credit_pack";
|
|
138
203
|
if (status === "processing") {
|
|
139
|
-
const pollResult = await pollJobUntilDone(jobId
|
|
204
|
+
const pollResult = await pollJobUntilDone(jobId);
|
|
140
205
|
if (pollResult.status === "completed") {
|
|
141
206
|
const asyncFormatted = formatRunResult({
|
|
142
207
|
...result,
|
|
143
208
|
status: "success",
|
|
144
209
|
output: pollResult.output,
|
|
145
210
|
}, { paymentMethod: method ?? getConfiguredMethods()[0] });
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
211
|
+
const asyncSpendLine = usedCreditPack
|
|
212
|
+
? "This run used your available credit pack."
|
|
213
|
+
: actualCost != null
|
|
214
|
+
? `Paid $${actualCost.toFixed(actualCost < 0.01 ? 4 : 2)}.`
|
|
215
|
+
: "This run completed successfully.";
|
|
150
216
|
const asyncFeedbackAsk = [
|
|
151
|
-
|
|
152
|
-
`"${
|
|
217
|
+
"IMPORTANT: After presenting the result above, you MUST ask the user:",
|
|
218
|
+
`"${asyncSpendLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
|
|
153
219
|
"",
|
|
154
220
|
`If the user wants to rate: rate_agent({ job_id: "${jobId}", rating: <1-5>, comment: "..." })`,
|
|
155
221
|
`If the user wants to tip: tip_agent({ job_id: "${jobId}", agent_id: "${resultAgentId}", amount: 0.25 })`,
|
|
@@ -157,16 +223,16 @@ export function registerRunTools(server) {
|
|
|
157
223
|
].join("\n");
|
|
158
224
|
return multiText(uploadSummary ? `${uploadSummary}\n\n${asyncFormatted}` : asyncFormatted, asyncFeedbackAsk);
|
|
159
225
|
}
|
|
160
|
-
// Async agent failed
|
|
161
226
|
const failedFormatted = formatRunResult({
|
|
162
227
|
...result,
|
|
163
228
|
status: "failed",
|
|
164
229
|
output: pollResult.output,
|
|
165
230
|
error_code: pollResult.error_code,
|
|
166
231
|
}, { paymentMethod: method ?? getConfiguredMethods()[0] });
|
|
167
|
-
return multiText(uploadSummary ? `${uploadSummary}\n\n${failedFormatted}` : failedFormatted,
|
|
232
|
+
return multiText(uploadSummary ? `${uploadSummary}\n\n${failedFormatted}` : failedFormatted, usedCreditPack
|
|
233
|
+
? "The agent execution failed and your reserved credit-pack unit was released automatically."
|
|
234
|
+
: "The agent execution failed. A refund has been initiated automatically.");
|
|
168
235
|
}
|
|
169
|
-
// Auto-tip if configured and run succeeded
|
|
170
236
|
const defaultTip = getDefaultTipAmount();
|
|
171
237
|
let tipLine = "";
|
|
172
238
|
if (status === "success" && defaultTip > 0 && result.feedback_token) {
|
|
@@ -184,17 +250,21 @@ export function registerRunTools(server) {
|
|
|
184
250
|
}
|
|
185
251
|
}
|
|
186
252
|
if (status !== "success") {
|
|
187
|
-
return multiText(uploadSummary ? `${uploadSummary}\n\n${formatted}` : formatted,
|
|
253
|
+
return multiText(uploadSummary ? `${uploadSummary}\n\n${formatted}` : formatted, usedCreditPack
|
|
254
|
+
? "The agent execution failed and your reserved credit-pack unit was released automatically."
|
|
255
|
+
: "The agent execution failed. A refund has been initiated automatically.");
|
|
188
256
|
}
|
|
189
|
-
const costLine =
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
257
|
+
const costLine = usedCreditPack
|
|
258
|
+
? ""
|
|
259
|
+
: actualCost != null
|
|
260
|
+
? `Paid $${actualCost.toFixed(actualCost < 0.01 ? 4 : 2)}.`
|
|
261
|
+
: "";
|
|
262
|
+
const spendLine = usedCreditPack ? "This run used your available credit pack." : (costLine || "This run completed successfully.");
|
|
193
263
|
const feedbackAsk = [
|
|
194
|
-
`--- Job ${jobId} | ${agentName} | ${costLine} ---`,
|
|
264
|
+
`--- Job ${jobId} | ${agentName} | ${usedCreditPack ? "covered by credit pack" : (costLine || "completed")} ---`,
|
|
195
265
|
"",
|
|
196
|
-
|
|
197
|
-
`"${
|
|
266
|
+
"IMPORTANT: After presenting the result above, you MUST ask the user:",
|
|
267
|
+
`"${spendLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
|
|
198
268
|
tipLine,
|
|
199
269
|
"",
|
|
200
270
|
`If the user wants to rate: rate_agent({ job_id: "${jobId}", rating: <1-5>, comment: "..." })`,
|
|
@@ -203,6 +273,10 @@ export function registerRunTools(server) {
|
|
|
203
273
|
] : []),
|
|
204
274
|
`To save for later: favorite_agent({ agent_id: "${resultAgentId}" })`,
|
|
205
275
|
].join("\n");
|
|
206
|
-
|
|
276
|
+
const supplementalLines = !usedCreditPack
|
|
277
|
+
? buildCreditPackOfferLines(agent)
|
|
278
|
+
: [];
|
|
279
|
+
const primaryBlock = uploadSummary ? `${uploadSummary}\n\n${formatted}` : formatted;
|
|
280
|
+
return multiText(supplementalLines.length > 0 ? `${primaryBlock}\n\n${supplementalLines.join("\n")}` : primaryBlock, feedbackAsk);
|
|
207
281
|
});
|
|
208
282
|
}
|