@agentwonderland/mcp 0.1.22 → 0.1.24

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.
Files changed (74) hide show
  1. package/dist/core/__tests__/card-setup.test.d.ts +1 -0
  2. package/dist/core/__tests__/card-setup.test.js +99 -0
  3. package/dist/core/__tests__/formatters.test.d.ts +1 -0
  4. package/dist/core/__tests__/formatters.test.js +15 -0
  5. package/dist/core/__tests__/passes.test.d.ts +1 -0
  6. package/dist/core/__tests__/passes.test.js +82 -0
  7. package/dist/core/__tests__/payments.test.d.ts +1 -0
  8. package/dist/core/__tests__/payments.test.js +52 -0
  9. package/dist/core/__tests__/principal.test.d.ts +1 -0
  10. package/dist/core/__tests__/principal.test.js +67 -0
  11. package/dist/core/api-client.d.ts +9 -4
  12. package/dist/core/api-client.js +52 -22
  13. package/dist/core/card-setup.d.ts +20 -13
  14. package/dist/core/card-setup.js +87 -30
  15. package/dist/core/config.d.ts +4 -0
  16. package/dist/core/config.js +28 -1
  17. package/dist/core/formatters.d.ts +2 -0
  18. package/dist/core/formatters.js +5 -1
  19. package/dist/core/index.d.ts +2 -0
  20. package/dist/core/index.js +2 -0
  21. package/dist/core/ows-adapter.d.ts +10 -2
  22. package/dist/core/ows-adapter.js +54 -10
  23. package/dist/core/passes.d.ts +40 -0
  24. package/dist/core/passes.js +32 -0
  25. package/dist/core/payments.d.ts +8 -0
  26. package/dist/core/payments.js +121 -16
  27. package/dist/core/principal.d.ts +2 -0
  28. package/dist/core/principal.js +109 -0
  29. package/dist/core/solana-charge.d.ts +9 -0
  30. package/dist/core/solana-charge.js +95 -0
  31. package/dist/core/types.d.ts +10 -0
  32. package/dist/index.js +13 -4
  33. package/dist/prompts/index.js +1 -1
  34. package/dist/resources/wallet.js +8 -1
  35. package/dist/tools/__tests__/_payment-confirmation.test.d.ts +1 -0
  36. package/dist/tools/__tests__/_payment-confirmation.test.js +30 -0
  37. package/dist/tools/_payment-confirmation.d.ts +6 -0
  38. package/dist/tools/_payment-confirmation.js +28 -0
  39. package/dist/tools/agent-info.js +14 -0
  40. package/dist/tools/index.d.ts +1 -0
  41. package/dist/tools/index.js +1 -0
  42. package/dist/tools/passes.d.ts +2 -0
  43. package/dist/tools/passes.js +157 -0
  44. package/dist/tools/run.js +116 -49
  45. package/dist/tools/solve.js +102 -44
  46. package/dist/tools/wallet.js +85 -50
  47. package/package.json +3 -1
  48. package/src/core/__tests__/card-setup.test.ts +118 -0
  49. package/src/core/__tests__/formatters.test.ts +17 -0
  50. package/src/core/__tests__/passes.test.ts +94 -0
  51. package/src/core/__tests__/payments.test.ts +60 -0
  52. package/src/core/__tests__/principal.test.ts +87 -0
  53. package/src/core/api-client.ts +70 -23
  54. package/src/core/card-setup.ts +112 -35
  55. package/src/core/config.ts +33 -2
  56. package/src/core/formatters.ts +7 -1
  57. package/src/core/index.ts +2 -0
  58. package/src/core/ows-adapter.ts +74 -8
  59. package/src/core/passes.ts +74 -0
  60. package/src/core/payments.ts +140 -15
  61. package/src/core/principal.ts +128 -0
  62. package/src/core/solana-charge.ts +149 -0
  63. package/src/core/types.ts +10 -0
  64. package/src/index.ts +13 -4
  65. package/src/prompts/index.ts +1 -1
  66. package/src/resources/wallet.ts +8 -1
  67. package/src/tools/__tests__/_payment-confirmation.test.ts +45 -0
  68. package/src/tools/_payment-confirmation.ts +52 -0
  69. package/src/tools/agent-info.ts +23 -0
  70. package/src/tools/index.ts +1 -0
  71. package/src/tools/passes.ts +234 -0
  72. package/src/tools/run.ts +174 -53
  73. package/src/tools/solve.ts +149 -56
  74. package/src/tools/wallet.ts +102 -52
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerPassTools(server: McpServer): void;
@@ -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,16 @@
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 { getConfiguredMethods, hasWalletConfigured, getWalletAddress } from "../core/payments.js";
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";
7
8
  import { storeFeedbackToken } from "./_token-cache.js";
8
- import { initiateCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
9
+ import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
10
+ import { formatPaymentChoicePrompt, formatPaymentLabel, formatRunConfirmationCommand, resolveConfirmationMethod, } from "./_payment-confirmation.js";
9
11
  const POLL_INTERVAL_MS = 3000;
10
- const POLL_MAX_MS = 120000; // 2 minutes
11
- async function pollJobUntilDone(jobId, agentName) {
12
+ const POLL_MAX_MS = 120000;
13
+ async function pollJobUntilDone(jobId) {
12
14
  const deadline = Date.now() + POLL_MAX_MS;
13
15
  const walletAddress = await getWalletAddress();
14
16
  const walletParam = walletAddress ? `?wallet=${walletAddress}` : "";
@@ -22,10 +24,9 @@ async function pollJobUntilDone(jobId, agentName) {
22
24
  if (job.status === "failed") {
23
25
  return { status: "failed", output: job.output, error_code: job.error_code };
24
26
  }
25
- // Still processing — continue polling
26
27
  }
27
28
  catch {
28
- // Ignore poll errors, keep trying until deadline
29
+ // Ignore poll errors until timeout.
29
30
  }
30
31
  }
31
32
  return { status: "failed", error_code: "POLL_TIMEOUT" };
@@ -36,10 +37,27 @@ function text(t) {
36
37
  function multiText(...blocks) {
37
38
  return { content: blocks.map((t) => ({ type: "text", text: t })) };
38
39
  }
39
- // Pending confirmations: agent_id → { agent, input, method }
40
40
  const pendingRuns = new Map();
41
+ function buildCreditPackOfferLines(agent) {
42
+ const program = getCreditPackProgram(agent);
43
+ if (!program?.packs?.length)
44
+ return [];
45
+ return [
46
+ "Discounted credit packs:",
47
+ ...program.packs.map((pack) => formatCreditPackOffer({
48
+ pack_id: pack.key ?? "",
49
+ label: pack.name ?? pack.key ?? "Credit Pack",
50
+ included_units: pack.included_units ?? 0,
51
+ price_usd: pack.price_usd ?? "0.00",
52
+ effective_price_per_unit_usd: (pack.included_units ?? 0) > 0 && pack.price_usd
53
+ ? (Number(pack.price_usd) / (pack.included_units ?? 1)).toFixed(6)
54
+ : undefined,
55
+ })).map((line) => ` ${line}`),
56
+ ` Buy one with buy_agent_credit_pack({ agent_id: "${agent.id}", pack_id: "<pack_id>" })`,
57
+ ];
58
+ }
41
59
  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 just pass the path directly, no base64 encoding needed.", {
60
+ 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
61
  agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
44
62
  input: z.record(z.unknown()).describe("Input payload for the agent"),
45
63
  pay_with: z.string().optional().describe("Payment method — wallet ID, chain name (tempo, base, etc.), or 'card'. Auto-detected if omitted."),
@@ -47,9 +65,8 @@ export function registerRunTools(server) {
47
65
  }, async ({ agent_id, input, pay_with, confirmed }) => {
48
66
  if (!hasWalletConfigured()) {
49
67
  try {
50
- const { qr, url } = await initiateCardSetup();
51
- const blocks = formatCardSetupBlocks(qr, url);
52
- return multiText(...blocks);
68
+ const { url } = await getOrCreatePendingCardSetup();
69
+ return multiText(...formatCardSetupBlocks(url));
53
70
  }
54
71
  catch {
55
72
  return text("No payment method configured.\n\n" +
@@ -57,7 +74,6 @@ export function registerRunTools(server) {
57
74
  "To use crypto: wallet_setup({ action: \"create\" })");
58
75
  }
59
76
  }
60
- // Resolve agent and fetch details
61
77
  let agent;
62
78
  try {
63
79
  agent = await apiGet(`/agents/${agent_id}`);
@@ -67,26 +83,53 @@ export function registerRunTools(server) {
67
83
  }
68
84
  const price = parseFloat(agent.pricePer1kTokens ?? "0.01");
69
85
  const agentName = agent.name ?? agent_id;
70
- // Confirmation step: show price and wait for confirmed: true
71
- if (requiresSpendConfirmation() && !confirmed) {
86
+ const creditPackInventory = await getCreditPackInventory(agent.id);
87
+ const activeCreditPack = getActiveCreditPack(creditPackInventory);
88
+ const configuredMethods = getConfiguredMethods();
89
+ const compatibleMethods = getCompatiblePaymentMethods(agent, configuredMethods);
90
+ const pending = pendingRuns.get(agent.id);
91
+ const requestedMethod = pay_with ?? pending?.method;
92
+ const normalizedRequestedMethod = requestedMethod ? normalizePaymentMethod(requestedMethod) : null;
93
+ if (!activeCreditPack && requestedMethod && !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 (!activeCreditPack && normalizedRequestedMethod && !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
+ if (!activeCreditPack && !requestedMethod && compatibleMethods.length === 0) {
103
+ return text(`No compatible payment methods are configured for ${agentName}.\n\n` +
104
+ `Your configured methods: ${configuredMethods.join(", ") || "none"}\n` +
105
+ `Agent accepts: ${agent.payment?.accepted_payments?.join(", ") || "unknown"}\n` +
106
+ "Use wallet_status to review your current setup.");
107
+ }
108
+ if (!activeCreditPack && !requestedMethod && compatibleMethods.length > 1) {
109
+ return text(formatPaymentChoicePrompt(agentName, compatibleMethods, compatibleMethods.map((method) => formatRunConfirmationCommand(agent.id, method).replace(", confirmed: true", ""))));
110
+ }
111
+ const method = activeCreditPack
112
+ ? undefined
113
+ : resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
114
+ if (!activeCreditPack && requiresSpendConfirmation() && !confirmed) {
72
115
  pendingRuns.set(agent.id, {
73
116
  agent: { id: agent.id, name: agentName, price },
74
117
  input,
75
- method: pay_with,
118
+ method,
76
119
  });
77
- return text([
120
+ const quoteLines = [
78
121
  `Ready to run ${agentName}`,
79
122
  "",
80
123
  ` Cost: $${price.toFixed(2)}`,
81
- ` Payment: ${pay_with ?? getConfiguredMethods()[0] ?? "auto"}`,
82
- "",
83
- "To proceed, call:",
84
- ` run_agent({ agent_id: "${agent.id}", input: <same>, confirmed: true })`,
85
- "",
86
- "To cancel, do nothing.",
87
- ].join("\n"));
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((u) => `Uploaded ${u.field}: ${u.originalPath} → ${u.url}`)
140
+ .map((upload) => `Uploaded ${upload.field}: ${upload.originalPath} → ${upload.url}`)
98
141
  .join("\n");
99
142
  }
100
143
  }
@@ -104,14 +147,28 @@ export function registerRunTools(server) {
104
147
  }
105
148
  let result;
106
149
  try {
107
- result = await apiPostWithPayment(`/agents/${agent.id}/run`, { input: processedInput }, method);
150
+ result = activeCreditPack
151
+ ? await apiPost(`/agents/${agent.id}/run`, { input: processedInput }, { ensureConsumerPrincipal: true })
152
+ : await apiPostWithPayment(`/agents/${agent.id}/run`, { input: processedInput }, method);
108
153
  }
109
154
  catch (err) {
110
155
  const apiErr = err;
156
+ if (activeCreditPack && apiErr?.status === 402) {
157
+ return text(`Your available credit packs for ${agentName} could not cover this run.\n\n` +
158
+ "Use list_agent_credit_packs to inspect your balances, or retry with a payment method.");
159
+ }
111
160
  if (apiErr?.status === 402) {
112
- return text("Payment failed — your wallet may not have enough USDC.\n\n" +
113
- "Check your balance and fund your wallet, then try again.\n" +
114
- "Use wallet_status to check your current payment methods.");
161
+ const allMethods = getConfiguredMethods();
162
+ const methodName = method ?? allMethods[0] ?? "auto";
163
+ const creditPackLines = buildCreditPackOfferLines(agent);
164
+ return text([
165
+ `Payment failed — "${methodName}" payment was rejected.`,
166
+ "",
167
+ "Check your payment method and try again.",
168
+ ...(allMethods.length > 0 ? [`Configured methods: ${allMethods.join(", ")}`] : []),
169
+ "Use wallet_status to check your current payment methods.",
170
+ ...(creditPackLines.length > 0 ? ["", ...creditPackLines] : []),
171
+ ].join("\n"));
115
172
  }
116
173
  const msg = apiErr?.message ?? "Failed to run agent";
117
174
  if (msg.includes("Missing required field") || msg.includes("validation failed")) {
@@ -119,7 +176,6 @@ export function registerRunTools(server) {
119
176
  }
120
177
  return text(`Error: ${msg}`);
121
178
  }
122
- // Clean up pending confirmation
123
179
  pendingRuns.delete(agent.id);
124
180
  const formatted = formatRunResult(result, {
125
181
  paymentMethod: method ?? getConfiguredMethods()[0],
@@ -131,22 +187,23 @@ export function registerRunTools(server) {
131
187
  }
132
188
  const actualCost = result.cost;
133
189
  const status = result.status;
134
- // Async agent poll until complete
190
+ const usedCreditPack = result.consumption_mode === "credit_pack";
135
191
  if (status === "processing") {
136
- const pollResult = await pollJobUntilDone(jobId, agentName);
192
+ const pollResult = await pollJobUntilDone(jobId);
137
193
  if (pollResult.status === "completed") {
138
194
  const asyncFormatted = formatRunResult({
139
195
  ...result,
140
196
  status: "success",
141
197
  output: pollResult.output,
142
198
  }, { paymentMethod: method ?? getConfiguredMethods()[0] });
143
- if (result.feedback_token) {
144
- storeFeedbackToken(jobId, result.feedback_token, resultAgentId);
145
- }
146
- const asyncCostLine = actualCost != null ? `Paid $${actualCost.toFixed(actualCost < 0.01 ? 4 : 2)}.` : "";
199
+ const asyncSpendLine = usedCreditPack
200
+ ? "This run used your available credit pack."
201
+ : actualCost != null
202
+ ? `Paid $${actualCost.toFixed(actualCost < 0.01 ? 4 : 2)}.`
203
+ : "This run completed successfully.";
147
204
  const asyncFeedbackAsk = [
148
- `IMPORTANT: After presenting the result above, you MUST ask the user:`,
149
- `"${asyncCostLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
205
+ "IMPORTANT: After presenting the result above, you MUST ask the user:",
206
+ `"${asyncSpendLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
150
207
  "",
151
208
  `If the user wants to rate: rate_agent({ job_id: "${jobId}", rating: <1-5>, comment: "..." })`,
152
209
  `If the user wants to tip: tip_agent({ job_id: "${jobId}", agent_id: "${resultAgentId}", amount: 0.25 })`,
@@ -154,16 +211,16 @@ export function registerRunTools(server) {
154
211
  ].join("\n");
155
212
  return multiText(uploadSummary ? `${uploadSummary}\n\n${asyncFormatted}` : asyncFormatted, asyncFeedbackAsk);
156
213
  }
157
- // Async agent failed
158
214
  const failedFormatted = formatRunResult({
159
215
  ...result,
160
216
  status: "failed",
161
217
  output: pollResult.output,
162
218
  error_code: pollResult.error_code,
163
219
  }, { paymentMethod: method ?? getConfiguredMethods()[0] });
164
- return multiText(uploadSummary ? `${uploadSummary}\n\n${failedFormatted}` : failedFormatted, "The agent execution failed. A refund has been initiated automatically.");
220
+ return multiText(uploadSummary ? `${uploadSummary}\n\n${failedFormatted}` : failedFormatted, usedCreditPack
221
+ ? "The agent execution failed and your reserved credit-pack unit was released automatically."
222
+ : "The agent execution failed. A refund has been initiated automatically.");
165
223
  }
166
- // Auto-tip if configured and run succeeded
167
224
  const defaultTip = getDefaultTipAmount();
168
225
  let tipLine = "";
169
226
  if (status === "success" && defaultTip > 0 && result.feedback_token) {
@@ -181,15 +238,21 @@ export function registerRunTools(server) {
181
238
  }
182
239
  }
183
240
  if (status !== "success") {
184
- return multiText(uploadSummary ? `${uploadSummary}\n\n${formatted}` : formatted, "The agent execution failed. A refund has been initiated automatically.");
241
+ return multiText(uploadSummary ? `${uploadSummary}\n\n${formatted}` : formatted, usedCreditPack
242
+ ? "The agent execution failed and your reserved credit-pack unit was released automatically."
243
+ : "The agent execution failed. A refund has been initiated automatically.");
185
244
  }
186
- const costLine = actualCost != null ? `Paid $${actualCost.toFixed(actualCost < 0.01 ? 4 : 2)}.` : "";
187
- // Separate the feedback ask into its own content block so the LLM
188
- // surfaces it to the user as a distinct follow-up question, rather than
189
- // burying it inside a collapsed tool output.
245
+ const costLine = usedCreditPack
246
+ ? ""
247
+ : actualCost != null
248
+ ? `Paid $${actualCost.toFixed(actualCost < 0.01 ? 4 : 2)}.`
249
+ : "";
250
+ const spendLine = usedCreditPack ? "This run used your available credit pack." : (costLine || "This run completed successfully.");
190
251
  const feedbackAsk = [
191
- `IMPORTANT: After presenting the result above, you MUST ask the user:`,
192
- `"${costLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
252
+ `--- Job ${jobId} | ${agentName} | ${usedCreditPack ? "covered by credit pack" : (costLine || "completed")} ---`,
253
+ "",
254
+ "IMPORTANT: After presenting the result above, you MUST ask the user:",
255
+ `"${spendLine} Was this result helpful? I can rate it (1-5 stars) or tip the builder for you."`,
193
256
  tipLine,
194
257
  "",
195
258
  `If the user wants to rate: rate_agent({ job_id: "${jobId}", rating: <1-5>, comment: "..." })`,
@@ -198,6 +261,10 @@ export function registerRunTools(server) {
198
261
  ] : []),
199
262
  `To save for later: favorite_agent({ agent_id: "${resultAgentId}" })`,
200
263
  ].join("\n");
201
- return multiText(uploadSummary ? `${uploadSummary}\n\n${formatted}` : formatted, feedbackAsk);
264
+ const supplementalLines = !usedCreditPack
265
+ ? buildCreditPackOfferLines(agent)
266
+ : [];
267
+ const primaryBlock = uploadSummary ? `${uploadSummary}\n\n${formatted}` : formatted;
268
+ return multiText(supplementalLines.length > 0 ? `${primaryBlock}\n\n${supplementalLines.join("\n")}` : primaryBlock, feedbackAsk);
202
269
  });
203
270
  }