@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.
Files changed (95) hide show
  1. package/dist/core/__tests__/amount-utils.test.d.ts +1 -0
  2. package/dist/core/__tests__/amount-utils.test.js +11 -0
  3. package/dist/core/__tests__/card-setup.test.d.ts +1 -0
  4. package/dist/core/__tests__/card-setup.test.js +99 -0
  5. package/dist/core/__tests__/formatters.test.d.ts +1 -0
  6. package/dist/core/__tests__/formatters.test.js +15 -0
  7. package/dist/core/__tests__/passes.test.d.ts +1 -0
  8. package/dist/core/__tests__/passes.test.js +82 -0
  9. package/dist/core/__tests__/payments.test.d.ts +1 -0
  10. package/dist/core/__tests__/payments.test.js +101 -0
  11. package/dist/core/__tests__/principal.test.d.ts +1 -0
  12. package/dist/core/__tests__/principal.test.js +67 -0
  13. package/dist/core/__tests__/spend-policy.test.d.ts +1 -0
  14. package/dist/core/__tests__/spend-policy.test.js +40 -0
  15. package/dist/core/amount-utils.d.ts +1 -0
  16. package/dist/core/amount-utils.js +4 -0
  17. package/dist/core/api-client.d.ts +9 -4
  18. package/dist/core/api-client.js +52 -22
  19. package/dist/core/base-charge.js +3 -2
  20. package/dist/core/card-setup.d.ts +20 -13
  21. package/dist/core/card-setup.js +85 -29
  22. package/dist/core/config.d.ts +22 -0
  23. package/dist/core/config.js +46 -2
  24. package/dist/core/formatters.d.ts +4 -3
  25. package/dist/core/formatters.js +10 -8
  26. package/dist/core/index.d.ts +2 -0
  27. package/dist/core/index.js +2 -0
  28. package/dist/core/ows-adapter.d.ts +10 -2
  29. package/dist/core/ows-adapter.js +54 -10
  30. package/dist/core/passes.d.ts +40 -0
  31. package/dist/core/passes.js +32 -0
  32. package/dist/core/payments.d.ts +8 -0
  33. package/dist/core/payments.js +111 -17
  34. package/dist/core/principal.d.ts +2 -0
  35. package/dist/core/principal.js +109 -0
  36. package/dist/core/solana-charge.d.ts +9 -0
  37. package/dist/core/solana-charge.js +96 -0
  38. package/dist/core/spend-policy.d.ts +12 -0
  39. package/dist/core/spend-policy.js +53 -0
  40. package/dist/core/types.d.ts +11 -2
  41. package/dist/index.js +11 -3
  42. package/dist/prompts/index.js +4 -2
  43. package/dist/resources/agents.js +1 -1
  44. package/dist/resources/wallet.js +8 -1
  45. package/dist/tools/__tests__/_payment-confirmation.test.d.ts +1 -0
  46. package/dist/tools/__tests__/_payment-confirmation.test.js +30 -0
  47. package/dist/tools/_payment-confirmation.d.ts +6 -0
  48. package/dist/tools/_payment-confirmation.js +28 -0
  49. package/dist/tools/agent-info.js +16 -2
  50. package/dist/tools/favorites.js +1 -1
  51. package/dist/tools/index.d.ts +1 -0
  52. package/dist/tools/index.js +1 -0
  53. package/dist/tools/observability.d.ts +2 -0
  54. package/dist/tools/observability.js +20 -0
  55. package/dist/tools/passes.d.ts +2 -0
  56. package/dist/tools/passes.js +157 -0
  57. package/dist/tools/run.js +127 -53
  58. package/dist/tools/solve.js +115 -51
  59. package/dist/tools/wallet.js +110 -59
  60. package/package.json +3 -1
  61. package/src/core/__tests__/amount-utils.test.ts +13 -0
  62. package/src/core/__tests__/card-setup.test.ts +118 -0
  63. package/src/core/__tests__/formatters.test.ts +17 -0
  64. package/src/core/__tests__/passes.test.ts +94 -0
  65. package/src/core/__tests__/payments.test.ts +122 -0
  66. package/src/core/__tests__/principal.test.ts +87 -0
  67. package/src/core/__tests__/spend-policy.test.ts +58 -0
  68. package/src/core/amount-utils.ts +5 -0
  69. package/src/core/api-client.ts +70 -23
  70. package/src/core/base-charge.ts +3 -2
  71. package/src/core/card-setup.ts +109 -34
  72. package/src/core/config.ts +74 -3
  73. package/src/core/formatters.ts +13 -9
  74. package/src/core/index.ts +2 -0
  75. package/src/core/ows-adapter.ts +74 -8
  76. package/src/core/passes.ts +74 -0
  77. package/src/core/payments.ts +130 -17
  78. package/src/core/principal.ts +128 -0
  79. package/src/core/solana-charge.ts +150 -0
  80. package/src/core/spend-policy.ts +69 -0
  81. package/src/core/types.ts +11 -2
  82. package/src/index.ts +11 -3
  83. package/src/prompts/index.ts +4 -2
  84. package/src/resources/agents.ts +1 -1
  85. package/src/resources/wallet.ts +8 -1
  86. package/src/tools/__tests__/_payment-confirmation.test.ts +45 -0
  87. package/src/tools/_payment-confirmation.ts +52 -0
  88. package/src/tools/agent-info.ts +25 -2
  89. package/src/tools/favorites.ts +1 -4
  90. package/src/tools/index.ts +1 -0
  91. package/src/tools/observability.ts +43 -0
  92. package/src/tools/passes.ts +228 -0
  93. package/src/tools/run.ts +174 -57
  94. package/src/tools/solve.ts +147 -59
  95. package/src/tools/wallet.ts +132 -62
@@ -1,11 +1,13 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { getWallets, getCardConfig } from "../core/config.js";
2
+ import { getWallets, getCardConfig, getPendingCardSetupToken } from "../core/config.js";
3
+ import { getCardCapabilities } from "../core/card-setup.js";
3
4
  import { getWalletAddress, getConfiguredMethods } from "../core/payments.js";
4
5
 
5
6
  export function registerWalletResources(server: McpServer) {
6
7
  server.resource("wallet-config", "aw://wallet", async () => {
7
8
  const wallets = getWallets();
8
9
  const card = getCardConfig();
10
+ const pendingCardSetupToken = getPendingCardSetupToken();
9
11
  const methods = getConfiguredMethods();
10
12
 
11
13
  const lines = ["Wallet Configuration", ""];
@@ -16,6 +18,11 @@ export function registerWalletResources(server: McpServer) {
16
18
  }
17
19
  if (card) {
18
20
  lines.push(`Card: ${card.brand} ****${card.last4}`);
21
+ const capabilities = await getCardCapabilities();
22
+ lines.push(`Card MPP: ${capabilities.spt_status}${capabilities.message ? ` — ${capabilities.message}` : ""}`);
23
+ }
24
+ if (pendingCardSetupToken) {
25
+ lines.push("Card setup: pending confirmation");
19
26
  }
20
27
  lines.push("", `Configured methods: ${methods.join(", ") || "none"}`);
21
28
 
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ formatPaymentChoicePrompt,
5
+ formatRunConfirmationCommand,
6
+ formatSolveConfirmationCommand,
7
+ makeSolvePendingKey,
8
+ resolveConfirmationMethod,
9
+ } from "../_payment-confirmation.js";
10
+
11
+ describe("payment confirmation helpers", () => {
12
+ it("prefers the explicit method, then pending, then configured default", () => {
13
+ expect(resolveConfirmationMethod("card", "tempo", ["tempo", "card"])).toBe("card");
14
+ expect(resolveConfirmationMethod(undefined, "card", ["tempo", "card"])).toBe("card");
15
+ expect(resolveConfirmationMethod(undefined, undefined, ["card", "tempo"])).toBe("card");
16
+ });
17
+
18
+ it("includes pay_with in run confirmation commands when present", () => {
19
+ expect(formatRunConfirmationCommand("agent_123", "card")).toContain('pay_with: "card"');
20
+ expect(formatRunConfirmationCommand("agent_123", undefined)).not.toContain("pay_with");
21
+ });
22
+
23
+ it("includes pay_with in solve confirmation commands when present", () => {
24
+ expect(formatSolveConfirmationCommand("translate", 1, "card")).toContain('pay_with: "card"');
25
+ expect(formatSolveConfirmationCommand("translate", 1, undefined)).not.toContain("pay_with");
26
+ });
27
+
28
+ it("uses a stable solve pending key for the same request", () => {
29
+ const input = { text: "hello", target_language: "es" };
30
+ expect(makeSolvePendingKey("translate", input, 1)).toBe(
31
+ makeSolvePendingKey("translate", input, 1),
32
+ );
33
+ });
34
+
35
+ it("formats a payment choice prompt with explicit options", () => {
36
+ const prompt = formatPaymentChoicePrompt("Echo Agent", ["card", "tempo"], [
37
+ ' run_agent({ agent_id: "agent_123", input: <same>, pay_with: "card" })',
38
+ ' run_agent({ agent_id: "agent_123", input: <same>, pay_with: "tempo" })',
39
+ ]);
40
+
41
+ expect(prompt).toContain("Multiple payment methods are available for Echo Agent.");
42
+ expect(prompt).toContain('Available methods: "card", "tempo"');
43
+ expect(prompt).toContain('pay_with: "card"');
44
+ });
45
+ });
@@ -0,0 +1,52 @@
1
+ export function resolveConfirmationMethod(
2
+ requestedMethod: string | undefined,
3
+ pendingMethod: string | undefined,
4
+ compatibleMethods: string[],
5
+ ): string | undefined {
6
+ return requestedMethod ?? pendingMethod ?? compatibleMethods[0];
7
+ }
8
+
9
+ export function formatPaymentLabel(method: string | undefined): string {
10
+ return method ?? "auto";
11
+ }
12
+
13
+ export function formatRunConfirmationCommand(
14
+ agentId: string,
15
+ method: string | undefined,
16
+ ): string {
17
+ const payWith = method ? `, pay_with: "${method}"` : "";
18
+ return ` run_agent({ agent_id: "${agentId}", input: <same>${payWith}, confirmed: true })`;
19
+ }
20
+
21
+ export function formatSolveConfirmationCommand(
22
+ intent: string,
23
+ budget: number,
24
+ method: string | undefined,
25
+ ): string {
26
+ const payWith = method ? `, pay_with: "${method}"` : "";
27
+ return ` solve({ intent: "${intent}", input: <same>, budget: ${budget}${payWith}, confirmed: true })`;
28
+ }
29
+
30
+ export function makeSolvePendingKey(
31
+ intent: string,
32
+ input: Record<string, unknown>,
33
+ budget: number,
34
+ ): string {
35
+ return JSON.stringify({ intent, input, budget });
36
+ }
37
+
38
+ export function formatPaymentChoicePrompt(
39
+ subject: string,
40
+ methods: string[],
41
+ commands: string[],
42
+ ): string {
43
+ return [
44
+ `Multiple payment methods are available for ${subject}.`,
45
+ "",
46
+ "Ask the user which payment method they want to use, then call one of:",
47
+ ...commands,
48
+ "",
49
+ `Available methods: ${methods.map((method) => `"${method}"`).join(", ")}`,
50
+ "For fully agentic execution, include pay_with explicitly.",
51
+ ].join("\n");
52
+ }
@@ -21,6 +21,15 @@ export function registerAgentInfoTools(server: McpServer): void {
21
21
  const s = (a.stats ?? {}) as Record<string, unknown>;
22
22
  const payment = (a.payment ?? {}) as Record<string, unknown>;
23
23
  const _pricing = (payment.pricing ?? {}) as Record<string, unknown>;
24
+ const creditPacks = payment.credit_packs as {
25
+ unit_type?: string;
26
+ packs?: Array<{
27
+ key?: string;
28
+ name?: string;
29
+ included_units?: number;
30
+ price_usd?: string;
31
+ }>;
32
+ } | null | undefined;
24
33
 
25
34
  const lines = [
26
35
  `${a.name}`,
@@ -28,7 +37,7 @@ export function registerAgentInfoTools(server: McpServer): void {
28
37
  "",
29
38
  (a.description as string) ?? "",
30
39
  "",
31
- `Pricing: ${formatPrice(a.pricePer1kTokens, a.pricingModel)}`,
40
+ `Pricing: ${formatPrice(a.pricePerRunUsd)}`,
32
41
  `Reliability: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
33
42
  `Avg latency: ${(a.avgResponseTimeMs as number) != null ? a.avgResponseTimeMs + "ms" : "N/A"}`,
34
43
  ...(() => {
@@ -41,6 +50,20 @@ export function registerAgentInfoTools(server: McpServer): void {
41
50
  const hint = outputTypeHint(a.tags as string[]);
42
51
  return hint ? [`Output: ${hint}`] : [];
43
52
  })(),
53
+ ...(() => {
54
+ if (!creditPacks?.packs?.length) return [];
55
+ const packLines = [
56
+ "",
57
+ `Discounted credit packs: ${creditPacks.unit_type ?? "run"}s`,
58
+ ];
59
+ for (const pack of creditPacks.packs) {
60
+ packLines.push(
61
+ ` ${pack.name ?? pack.key}: ${pack.included_units ?? 0} units for $${pack.price_usd ?? "0.00"}`,
62
+ );
63
+ }
64
+ packLines.push(" Use buy_agent_credit_pack to purchase one.");
65
+ return packLines;
66
+ })(),
44
67
  ];
45
68
 
46
69
  // Feedback summary (rating + tips)
@@ -107,7 +130,7 @@ export function registerAgentInfoTools(server: McpServer): void {
107
130
  return [
108
131
  ` ${a.name}`,
109
132
  ` ${stars(rating)} (${s.ratingCount ?? 0} reviews)${tipCount > 0 ? ` • ${tipCount} tips` : ""}`,
110
- ` ${compactNumber(jobs)} jobs • ${formatPrice(a.pricePer1kTokens, a.pricingModel)}`,
133
+ ` ${compactNumber(jobs)} jobs • ${formatPrice(a.pricePerRunUsd)}`,
111
134
  ` Success: ${a.successRate != null ? (Number(a.successRate) * 100).toFixed(0) + "%" : "N/A"}`,
112
135
  ` ${agentWebUrl(a.id)}`,
113
136
  "",
@@ -54,10 +54,7 @@ export function registerFavoriteTools(server: McpServer) {
54
54
  const agent = await apiGet<Record<string, unknown>>(`/agents/${id}`);
55
55
  const rating = stars(agent.avgRating as number | null | undefined);
56
56
  const jobs = compactNumber(agent.totalExecutions as number | null | undefined);
57
- const price = formatPrice(
58
- agent.pricePer1kTokens as string | null | undefined,
59
- agent.pricingModel as string | null | undefined,
60
- );
57
+ const price = formatPrice(agent.pricePerRunUsd as string | null | undefined);
61
58
  lines.push(`${agent.name} ${rating} ${jobs} jobs | ${price}`);
62
59
  lines.push(` ID: ${id}`);
63
60
  if (agent.description) lines.push(` ${agent.description}`);
@@ -7,3 +7,4 @@ export { registerRateTools } from "./rate.js";
7
7
  export { registerWalletTools } from "./wallet.js";
8
8
  export { registerFavoriteTools } from "./favorites.js";
9
9
  export { registerTipTools } from "./tip.js";
10
+ export { registerPassTools } from "./passes.js";
@@ -0,0 +1,43 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { apiPost } from "../core/api-client.js";
3
+
4
+ function text(t: string) {
5
+ return { content: [{ type: "text" as const, text: t }] };
6
+ }
7
+
8
+ export function registerObservabilityTools(server: McpServer): void {
9
+ server.tool(
10
+ "open_observability_dashboard",
11
+ "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.",
12
+ {},
13
+ async () => {
14
+ const result = await apiPost<{
15
+ url: string;
16
+ expires_at: string;
17
+ consumer_principal?: string;
18
+ }>(
19
+ "/observability/link",
20
+ {},
21
+ { ensureConsumerPrincipal: true },
22
+ );
23
+
24
+ const lines = [
25
+ "Your secure observability link is ready:",
26
+ result.url,
27
+ "",
28
+ `Expires: ${result.expires_at}`,
29
+ ];
30
+
31
+ if (result.consumer_principal) {
32
+ lines.push(`Consumer principal: ${result.consumer_principal}`);
33
+ }
34
+
35
+ lines.push(
36
+ "",
37
+ "Open the link in your browser to view usage metrics, spend, rebates, and recent runs.",
38
+ );
39
+
40
+ return text(lines.join("\n"));
41
+ },
42
+ );
43
+ }
@@ -0,0 +1,228 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { apiGet, apiPostWithPayment } from "../core/api-client.js";
4
+ import {
5
+ formatCreditPack,
6
+ formatCreditPackOffer,
7
+ getCreditPackProgram,
8
+ } from "../core/passes.js";
9
+ import {
10
+ getCompatiblePaymentMethods,
11
+ getConfiguredMethods,
12
+ hasWalletConfigured,
13
+ normalizePaymentMethod,
14
+ } from "../core/payments.js";
15
+ import { requiresSpendConfirmation } from "../core/config.js";
16
+ import { ensureConsumerPrincipal } from "../core/principal.js";
17
+ import { getOrCreatePendingCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
18
+ import {
19
+ formatPaymentChoicePrompt,
20
+ formatPaymentLabel,
21
+ resolveConfirmationMethod,
22
+ } from "./_payment-confirmation.js";
23
+ import type { AgentRecord } from "../core/types.js";
24
+ import type { CreditPackInventory, CreditPackOffer, CreditPackRecord } from "../core/passes.js";
25
+
26
+ const pendingCreditPackPurchases = new Map<string, {
27
+ agentId: string;
28
+ agentName: string;
29
+ offer: CreditPackOffer;
30
+ method?: string;
31
+ }>();
32
+
33
+ function text(t: string) {
34
+ return { content: [{ type: "text" as const, text: t }] };
35
+ }
36
+
37
+ function multiText(...blocks: string[]) {
38
+ return { content: blocks.map((t) => ({ type: "text" as const, text: t })) };
39
+ }
40
+
41
+ async function getAgent(agentId: string): Promise<AgentRecord> {
42
+ return apiGet<AgentRecord>(`/agents/${agentId}`);
43
+ }
44
+
45
+ function findOffer(agent: AgentRecord, packId: string): CreditPackOffer | null {
46
+ const program = getCreditPackProgram(agent);
47
+ const pack = program?.packs?.find((candidate) => candidate.key === packId);
48
+ if (!pack) return null;
49
+
50
+ const price = Number(pack.price_usd ?? "0");
51
+ const units = pack.included_units ?? 0;
52
+ return {
53
+ pack_id: packId,
54
+ label: pack.name ?? pack.key ?? "Credit Pack",
55
+ included_units: units,
56
+ price_usd: price.toFixed(2),
57
+ effective_price_per_unit_usd: units > 0 ? (price / units).toFixed(6) : undefined,
58
+ };
59
+ }
60
+
61
+ export function registerPassTools(server: McpServer): void {
62
+ server.tool(
63
+ "buy_agent_credit_pack",
64
+ "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.",
65
+ {
66
+ agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
67
+ 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."),
68
+ pay_with: z.string().optional().describe("Payment method — wallet ID, chain name, or 'card'. Auto-detected if omitted."),
69
+ confirmed: z.boolean().optional().describe("Set to true to confirm the purchase after seeing the quote."),
70
+ },
71
+ async ({ agent_id, pack_id, pay_with, confirmed }) => {
72
+ if (!hasWalletConfigured()) {
73
+ try {
74
+ const { url } = await getOrCreatePendingCardSetup();
75
+ return multiText(...formatCardSetupBlocks(url));
76
+ } catch {
77
+ return text(
78
+ "No payment method configured.\n\n" +
79
+ "To add a credit card: wallet_setup({ action: \"add-card\" })\n" +
80
+ "To use crypto: wallet_setup({ action: \"create\" })",
81
+ );
82
+ }
83
+ }
84
+
85
+ const agent = await getAgent(agent_id);
86
+ const agentName = agent.name ?? agent_id;
87
+ const principal = await ensureConsumerPrincipal();
88
+ const program = getCreditPackProgram(agent);
89
+ const offers = (program?.packs ?? [])
90
+ .map((pack) => findOffer(agent, pack.key ?? ""))
91
+ .filter((offer): offer is CreditPackOffer => !!offer);
92
+
93
+ if (offers.length === 0) {
94
+ return text(`${agentName} does not currently offer discounted credit packs.`);
95
+ }
96
+
97
+ const configuredMethods = getConfiguredMethods();
98
+ const compatibleMethods = getCompatiblePaymentMethods(agent, configuredMethods);
99
+ const pending = pendingCreditPackPurchases.get(agent.id);
100
+ const requestedMethod = pay_with ?? pending?.method;
101
+ const normalizedRequestedMethod = requestedMethod ? normalizePaymentMethod(requestedMethod) : null;
102
+
103
+ if (requestedMethod && !normalizedRequestedMethod) {
104
+ return text(
105
+ `Payment method "${requestedMethod}" is not configured.\n\n` +
106
+ "Use wallet_status to review your current payment methods.",
107
+ );
108
+ }
109
+
110
+ if (normalizedRequestedMethod && !compatibleMethods.includes(normalizedRequestedMethod)) {
111
+ return text(
112
+ `This agent cannot be paid with "${requestedMethod}".\n\n` +
113
+ `Available payment methods for this agent: ${compatibleMethods.join(", ") || "none"}.`,
114
+ );
115
+ }
116
+
117
+ if (!requestedMethod && compatibleMethods.length === 0) {
118
+ return text(
119
+ `No compatible payment methods are configured for ${agentName}.\n\n` +
120
+ `Your configured methods: ${configuredMethods.join(", ") || "none"}`,
121
+ );
122
+ }
123
+
124
+ if (!requestedMethod && compatibleMethods.length > 1) {
125
+ return text(
126
+ formatPaymentChoicePrompt(
127
+ `${agentName} credit-pack purchase`,
128
+ compatibleMethods,
129
+ compatibleMethods.map((method) => ` buy_agent_credit_pack({ agent_id: "${agent.id}"${pack_id ? `, pack_id: "${pack_id}"` : ""}, pay_with: "${method}" })`),
130
+ ),
131
+ );
132
+ }
133
+
134
+ let resolvedOffer: CreditPackOffer;
135
+ if (pack_id) {
136
+ const offer = offers.find((candidate) => candidate.pack_id === pack_id);
137
+ if (!offer) {
138
+ return text(
139
+ `Unknown credit pack "${pack_id}" for ${agentName}.\n\n` +
140
+ `Available packs:\n${offers.map((offer) => ` ${formatCreditPackOffer(offer)}`).join("\n")}`,
141
+ );
142
+ }
143
+ resolvedOffer = offer;
144
+ } else if (offers.length === 1) {
145
+ resolvedOffer = offers[0]!;
146
+ } else {
147
+ return text([
148
+ `Multiple credit packs are available for ${agentName}.`,
149
+ "",
150
+ ...offers.map((offer) => ` ${offer.pack_id}: ${formatCreditPackOffer(offer)}`),
151
+ "",
152
+ "Choose one by calling:",
153
+ ` buy_agent_credit_pack({ agent_id: "${agent.id}", pack_id: "${offers[0]!.pack_id}" })`,
154
+ ].join("\n"));
155
+ }
156
+
157
+ const method = resolveConfirmationMethod(pay_with, pending?.method, compatibleMethods);
158
+ if (requiresSpendConfirmation() && !confirmed) {
159
+ pendingCreditPackPurchases.set(agent.id, {
160
+ agentId: agent.id,
161
+ agentName,
162
+ offer: resolvedOffer,
163
+ method,
164
+ });
165
+
166
+ return text([
167
+ `Ready to buy a credit pack for ${agentName}`,
168
+ "",
169
+ formatCreditPackOffer(resolvedOffer),
170
+ `Payment: ${formatPaymentLabel(method)}`,
171
+ `Consumer principal: ${principal}`,
172
+ "",
173
+ "To proceed, call:",
174
+ ` buy_agent_credit_pack({ agent_id: "${agent.id}", pack_id: "${resolvedOffer.pack_id}", pay_with: "${method}", confirmed: true })`,
175
+ "",
176
+ "To cancel, do nothing.",
177
+ ].join("\n"));
178
+ }
179
+
180
+ const result = await apiPostWithPayment<{
181
+ consumer_principal: string;
182
+ offer: CreditPackOffer;
183
+ credit_pack: CreditPackRecord;
184
+ }>(`/agents/${agent.id}/credit-packs/purchase`, { pack_id: resolvedOffer.pack_id }, method, { ensureConsumerPrincipal: true });
185
+
186
+ pendingCreditPackPurchases.delete(agent.id);
187
+
188
+ return text([
189
+ `Credit pack purchased for ${agentName}`,
190
+ "",
191
+ formatCreditPackOffer(result.offer),
192
+ formatCreditPack(result.credit_pack),
193
+ `Consumer principal: ${result.consumer_principal}`,
194
+ "",
195
+ "Future runs through run_agent will automatically use this credit pack while units remain.",
196
+ ].join("\n"));
197
+ },
198
+ );
199
+
200
+ server.tool(
201
+ "list_agent_credit_packs",
202
+ "Show discounted credit-pack offers for an agent plus any balances available under the current consumer principal.",
203
+ {
204
+ agent_id: z.string().describe("Agent ID (UUID, slug, or name)"),
205
+ },
206
+ async ({ agent_id }) => {
207
+ const agent = await getAgent(agent_id);
208
+ const result = await apiGet<CreditPackInventory>(`/agents/${agent.id}/credit-packs`, { ensureConsumerPrincipal: true });
209
+
210
+ const lines = [
211
+ `Credit packs for ${agent.name}`,
212
+ ...(result.consumer_principal ? [`Consumer principal: ${result.consumer_principal}`] : []),
213
+ ];
214
+
215
+ if (result.offers.length > 0) {
216
+ lines.push("", "Available packs:", ...result.offers.map((offer) => ` ${offer.pack_id}: ${formatCreditPackOffer(offer)}`));
217
+ }
218
+
219
+ if (result.balances.length > 0) {
220
+ lines.push("", "Your balances:", ...result.balances.map((pack) => ` ${formatCreditPack(pack)}`));
221
+ } else {
222
+ lines.push("", "No purchased credit packs found for this agent.");
223
+ }
224
+
225
+ return text(lines.join("\n"));
226
+ },
227
+ );
228
+ }