@agentwonderland/mcp 0.1.23 → 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.
- 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 +52 -0
- package/dist/core/__tests__/principal.test.d.ts +1 -0
- package/dist/core/__tests__/principal.test.js +67 -0
- package/dist/core/api-client.d.ts +9 -4
- package/dist/core/api-client.js +52 -22
- package/dist/core/card-setup.d.ts +20 -13
- package/dist/core/card-setup.js +85 -29
- package/dist/core/config.d.ts +3 -0
- package/dist/core/config.js +24 -2
- package/dist/core/formatters.d.ts +2 -0
- package/dist/core/formatters.js +5 -1
- 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 +97 -13
- 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 +95 -0
- package/dist/core/types.d.ts +10 -0
- package/dist/index.js +8 -3
- package/dist/prompts/index.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 +14 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/passes.d.ts +2 -0
- package/dist/tools/passes.js +157 -0
- package/dist/tools/run.js +113 -51
- package/dist/tools/solve.js +102 -44
- package/dist/tools/wallet.js +85 -50
- package/package.json +3 -1
- 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 +60 -0
- package/src/core/__tests__/principal.test.ts +87 -0
- package/src/core/api-client.ts +70 -23
- package/src/core/card-setup.ts +109 -34
- package/src/core/config.ts +29 -3
- package/src/core/formatters.ts +7 -1
- 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 +113 -13
- package/src/core/principal.ts +128 -0
- package/src/core/solana-charge.ts +149 -0
- package/src/core/types.ts +10 -0
- package/src/index.ts +8 -3
- package/src/prompts/index.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 +23 -0
- package/src/tools/index.ts +1 -0
- package/src/tools/passes.ts +234 -0
- package/src/tools/run.ts +171 -55
- package/src/tools/solve.ts +149 -56
- package/src/tools/wallet.ts +102 -52
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const mockApiGet = vi.fn();
|
|
3
|
+
const mockApiPost = vi.fn();
|
|
4
|
+
class MockApiError extends Error {
|
|
5
|
+
status;
|
|
6
|
+
constructor(status, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.name = "ApiError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
let pendingToken = null;
|
|
13
|
+
const savedCards = [];
|
|
14
|
+
vi.mock("../api-client.js", () => ({
|
|
15
|
+
ApiError: MockApiError,
|
|
16
|
+
apiGet: (...args) => mockApiGet(...args),
|
|
17
|
+
apiPost: (...args) => mockApiPost(...args),
|
|
18
|
+
}));
|
|
19
|
+
vi.mock("../config.js", () => ({
|
|
20
|
+
getApiUrl: () => "http://api.test",
|
|
21
|
+
getPendingCardSetupToken: () => pendingToken,
|
|
22
|
+
setCardConfig: (card) => {
|
|
23
|
+
savedCards.push(card);
|
|
24
|
+
},
|
|
25
|
+
setPendingCardSetupToken: (token) => {
|
|
26
|
+
pendingToken = token;
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
describe("card setup helpers", () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
vi.clearAllMocks();
|
|
32
|
+
pendingToken = null;
|
|
33
|
+
savedCards.length = 0;
|
|
34
|
+
});
|
|
35
|
+
it("reuses the existing pending setup token", async () => {
|
|
36
|
+
pendingToken = "tok_existing";
|
|
37
|
+
const { getOrCreatePendingCardSetup } = await import("../card-setup.js");
|
|
38
|
+
const result = await getOrCreatePendingCardSetup();
|
|
39
|
+
expect(mockApiPost).not.toHaveBeenCalled();
|
|
40
|
+
expect(result).toMatchObject({
|
|
41
|
+
token: "tok_existing",
|
|
42
|
+
url: "http://api.test/card/handoff/tok_existing",
|
|
43
|
+
isNew: false,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it("stores a newly created setup token for later resume", async () => {
|
|
47
|
+
mockApiPost.mockResolvedValueOnce({ token: "tok_new" });
|
|
48
|
+
const { getOrCreatePendingCardSetup } = await import("../card-setup.js");
|
|
49
|
+
const result = await getOrCreatePendingCardSetup();
|
|
50
|
+
expect(mockApiPost).toHaveBeenCalledWith("/card/setup", {});
|
|
51
|
+
expect(pendingToken).toBe("tok_new");
|
|
52
|
+
expect(result).toMatchObject({
|
|
53
|
+
token: "tok_new",
|
|
54
|
+
url: "http://api.test/card/handoff/tok_new",
|
|
55
|
+
isNew: true,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
it("persists the card and clears pending setup on successful completion", async () => {
|
|
59
|
+
pendingToken = "tok_ready";
|
|
60
|
+
mockApiGet.mockResolvedValueOnce({
|
|
61
|
+
status: "complete",
|
|
62
|
+
card_last4: "4242",
|
|
63
|
+
card_brand: "visa",
|
|
64
|
+
consumer_token: "consumer_123",
|
|
65
|
+
payment_method_id: "pm_123",
|
|
66
|
+
});
|
|
67
|
+
const { pollCardSetup } = await import("../card-setup.js");
|
|
68
|
+
const result = await pollCardSetup("tok_ready", 50);
|
|
69
|
+
expect(result).toEqual({
|
|
70
|
+
last4: "4242",
|
|
71
|
+
brand: "visa",
|
|
72
|
+
consumerToken: "consumer_123",
|
|
73
|
+
});
|
|
74
|
+
expect(savedCards).toEqual([{
|
|
75
|
+
consumerToken: "consumer_123",
|
|
76
|
+
paymentMethodId: "pm_123",
|
|
77
|
+
last4: "4242",
|
|
78
|
+
brand: "visa",
|
|
79
|
+
}]);
|
|
80
|
+
expect(pendingToken).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
it("clears expired pending setup tokens", async () => {
|
|
83
|
+
pendingToken = "tok_expired";
|
|
84
|
+
mockApiGet.mockRejectedValueOnce(new MockApiError(404, "Setup not found"));
|
|
85
|
+
const { pollCardSetup } = await import("../card-setup.js");
|
|
86
|
+
const result = await pollCardSetup("tok_expired", 50);
|
|
87
|
+
expect(result).toBeNull();
|
|
88
|
+
expect(pendingToken).toBeNull();
|
|
89
|
+
});
|
|
90
|
+
it("formats card setup as a single visible message with the setup page URL", async () => {
|
|
91
|
+
const { formatCardSetupBlocks } = await import("../card-setup.js");
|
|
92
|
+
const blocks = formatCardSetupBlocks("http://api.test/card/handoff/tok");
|
|
93
|
+
expect(blocks).toHaveLength(1);
|
|
94
|
+
expect(blocks[0]).toContain("Open this setup page to connect your card:");
|
|
95
|
+
expect(blocks[0]).toContain("http://api.test/card/handoff/tok");
|
|
96
|
+
expect(blocks[0]).toContain("securely save the card");
|
|
97
|
+
expect(blocks[0]).not.toContain("QR code above");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { formatRunResult } from "../formatters.js";
|
|
3
|
+
describe("formatRunResult", () => {
|
|
4
|
+
it("does not show a paid line for credit-pack-backed runs with zero cost", () => {
|
|
5
|
+
const output = formatRunResult({
|
|
6
|
+
agent_name: "Credit Pack Agent",
|
|
7
|
+
status: "success",
|
|
8
|
+
cost: 0,
|
|
9
|
+
consumption_mode: "credit_pack",
|
|
10
|
+
credit_pack_id: "pack-123",
|
|
11
|
+
}, { paymentMethod: "card" });
|
|
12
|
+
expect(output).toContain("Covered by credit pack (pack-123)");
|
|
13
|
+
expect(output).not.toContain("Paid:");
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { formatCreditPack, formatCreditPackOffer, getActiveCreditPack, getCreditPackProgram, } from "../passes.js";
|
|
3
|
+
const baseAgent = {
|
|
4
|
+
id: "agent-1",
|
|
5
|
+
name: "Test Agent",
|
|
6
|
+
payment: {
|
|
7
|
+
credit_packs: {
|
|
8
|
+
unit_type: "run",
|
|
9
|
+
packs: [
|
|
10
|
+
{ key: "starter", name: "Starter Pack", included_units: 5, price_usd: "1.00" },
|
|
11
|
+
{ key: "growth", name: "Growth Pack", included_units: 20, price_usd: "3.00" },
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
describe("getCreditPackProgram", () => {
|
|
17
|
+
it("returns the configured credit-pack program from agent metadata", () => {
|
|
18
|
+
expect(getCreditPackProgram(baseAgent)).toEqual({
|
|
19
|
+
unit_type: "run",
|
|
20
|
+
packs: [
|
|
21
|
+
{ key: "starter", name: "Starter Pack", included_units: 5, price_usd: "1.00" },
|
|
22
|
+
{ key: "growth", name: "Growth Pack", included_units: 20, price_usd: "3.00" },
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe("formatCreditPackOffer", () => {
|
|
28
|
+
it("shows units and per-unit price", () => {
|
|
29
|
+
expect(formatCreditPackOffer({
|
|
30
|
+
pack_id: "starter",
|
|
31
|
+
label: "Starter Pack",
|
|
32
|
+
included_units: 5,
|
|
33
|
+
price_usd: "1.00",
|
|
34
|
+
effective_price_per_unit_usd: "0.200000",
|
|
35
|
+
})).toContain("Starter Pack");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe("formatCreditPack", () => {
|
|
39
|
+
it("includes the pack status when it is no longer active", () => {
|
|
40
|
+
expect(formatCreditPack({
|
|
41
|
+
id: "pack-1",
|
|
42
|
+
status: "depleted",
|
|
43
|
+
unit_type: "run",
|
|
44
|
+
included_units: 5,
|
|
45
|
+
remaining_units: 0,
|
|
46
|
+
price_usd: "1.00",
|
|
47
|
+
purchased_at: "2026-05-04T15:08:28.700Z",
|
|
48
|
+
pack: { key: "starter", name: "Starter Pack" },
|
|
49
|
+
})).toContain("depleted");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe("getActiveCreditPack", () => {
|
|
53
|
+
it("returns the oldest active balance with remaining units", () => {
|
|
54
|
+
const pack = getActiveCreditPack({
|
|
55
|
+
consumer_principal: "did:pkh:eip155:8453:0x1111111111111111111111111111111111111111",
|
|
56
|
+
offers: [],
|
|
57
|
+
balances: [
|
|
58
|
+
{
|
|
59
|
+
id: "newer",
|
|
60
|
+
status: "active",
|
|
61
|
+
unit_type: "run",
|
|
62
|
+
included_units: 20,
|
|
63
|
+
remaining_units: 20,
|
|
64
|
+
price_usd: "3.00",
|
|
65
|
+
purchased_at: "2026-04-10T00:00:00Z",
|
|
66
|
+
pack: { key: "growth", name: "Growth Pack" },
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "older",
|
|
70
|
+
status: "active",
|
|
71
|
+
unit_type: "run",
|
|
72
|
+
included_units: 5,
|
|
73
|
+
remaining_units: 2,
|
|
74
|
+
price_usd: "1.00",
|
|
75
|
+
purchased_at: "2026-04-01T00:00:00Z",
|
|
76
|
+
pack: { key: "starter", name: "Starter Pack" },
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
expect(pack?.id).toBe("older");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
let currentCard = {
|
|
3
|
+
consumerToken: "consumer_one",
|
|
4
|
+
paymentMethodId: "pm_one",
|
|
5
|
+
last4: "1111",
|
|
6
|
+
brand: "visa",
|
|
7
|
+
};
|
|
8
|
+
const createdFetches = [vi.fn(), vi.fn(), vi.fn()];
|
|
9
|
+
const mockMppxCreate = vi.fn();
|
|
10
|
+
const mockStripe = vi.fn((opts) => opts);
|
|
11
|
+
vi.mock("../config.js", () => ({
|
|
12
|
+
getApiUrl: () => "http://api.test",
|
|
13
|
+
getCardConfig: () => currentCard,
|
|
14
|
+
getConfig: () => ({ defaultPaymentMethod: "card" }),
|
|
15
|
+
getDefaultWallet: () => undefined,
|
|
16
|
+
getWallets: () => [],
|
|
17
|
+
resolveWalletAndChain: () => null,
|
|
18
|
+
}));
|
|
19
|
+
vi.mock("mppx/client", () => ({
|
|
20
|
+
Mppx: {
|
|
21
|
+
create: (config) => mockMppxCreate(config),
|
|
22
|
+
},
|
|
23
|
+
stripe: (config) => mockStripe(config),
|
|
24
|
+
}));
|
|
25
|
+
describe("card payment fetch cache", () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.clearAllMocks();
|
|
28
|
+
currentCard = {
|
|
29
|
+
consumerToken: "consumer_one",
|
|
30
|
+
paymentMethodId: "pm_one",
|
|
31
|
+
last4: "1111",
|
|
32
|
+
brand: "visa",
|
|
33
|
+
};
|
|
34
|
+
mockMppxCreate
|
|
35
|
+
.mockReturnValueOnce({ fetch: createdFetches[0] })
|
|
36
|
+
.mockReturnValueOnce({ fetch: createdFetches[1] })
|
|
37
|
+
.mockReturnValueOnce({ fetch: createdFetches[2] });
|
|
38
|
+
});
|
|
39
|
+
it("rebuilds the cached card fetch when the card config changes", async () => {
|
|
40
|
+
const { getPaymentFetch } = await import("../payments.js");
|
|
41
|
+
const firstFetch = await getPaymentFetch("card");
|
|
42
|
+
currentCard = {
|
|
43
|
+
consumerToken: "consumer_two",
|
|
44
|
+
paymentMethodId: "pm_two",
|
|
45
|
+
last4: "2222",
|
|
46
|
+
brand: "mastercard",
|
|
47
|
+
};
|
|
48
|
+
const secondFetch = await getPaymentFetch("card");
|
|
49
|
+
expect(firstFetch).not.toBe(secondFetch);
|
|
50
|
+
expect(mockMppxCreate).toHaveBeenCalledTimes(2);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const state = vi.hoisted(() => ({
|
|
3
|
+
wallets: [],
|
|
4
|
+
addresses: {},
|
|
5
|
+
addedWallets: [],
|
|
6
|
+
owsAvailable: false,
|
|
7
|
+
owsWallets: [],
|
|
8
|
+
}));
|
|
9
|
+
vi.mock("../config.js", () => ({
|
|
10
|
+
addWallet: (wallet) => {
|
|
11
|
+
state.addedWallets.push(wallet);
|
|
12
|
+
state.wallets.push(wallet);
|
|
13
|
+
},
|
|
14
|
+
getDefaultWallet: () => state.wallets[0],
|
|
15
|
+
getWallets: () => state.wallets,
|
|
16
|
+
}));
|
|
17
|
+
vi.mock("../payments.js", () => ({
|
|
18
|
+
getWalletAddress: async (walletId) => {
|
|
19
|
+
if (walletId in state.addresses) {
|
|
20
|
+
return state.addresses[walletId] ?? null;
|
|
21
|
+
}
|
|
22
|
+
const wallet = state.wallets.find((entry) => entry.id === walletId);
|
|
23
|
+
if (wallet?.key) {
|
|
24
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
25
|
+
return privateKeyToAccount(wallet.key).address;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
vi.mock("../ows-adapter.js", () => ({
|
|
31
|
+
createOwsWallet: async (name) => ({
|
|
32
|
+
walletId: `ows-${name}`,
|
|
33
|
+
address: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
34
|
+
}),
|
|
35
|
+
isOwsAvailable: async () => state.owsAvailable,
|
|
36
|
+
listOwsWallets: async () => state.owsWallets,
|
|
37
|
+
}));
|
|
38
|
+
describe("consumer principal helpers", () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.resetModules();
|
|
41
|
+
state.wallets = [];
|
|
42
|
+
state.addresses = {};
|
|
43
|
+
state.addedWallets = [];
|
|
44
|
+
state.owsAvailable = false;
|
|
45
|
+
state.owsWallets = [];
|
|
46
|
+
});
|
|
47
|
+
it("prefers an EVM-backed principal when both EVM and Solana wallets exist", async () => {
|
|
48
|
+
state.wallets = [
|
|
49
|
+
{ id: "sol-wallet", keyType: "ows", owsWalletId: "ows-sol", chains: ["solana"], defaultChain: "solana" },
|
|
50
|
+
{ id: "evm-wallet", keyType: "evm", key: "0x1234", chains: ["tempo", "base"], defaultChain: "base" },
|
|
51
|
+
];
|
|
52
|
+
state.addresses = {
|
|
53
|
+
"sol-wallet": "So1ana11111111111111111111111111111111111111",
|
|
54
|
+
"evm-wallet": "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
|
|
55
|
+
};
|
|
56
|
+
const { getConsumerPrincipal } = await import("../principal.js");
|
|
57
|
+
const principal = await getConsumerPrincipal();
|
|
58
|
+
expect(principal).toBe("did:pkh:eip155:8453:0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
|
|
59
|
+
});
|
|
60
|
+
it("creates a fallback EVM identity wallet when none exists", async () => {
|
|
61
|
+
const { ensureConsumerPrincipal } = await import("../principal.js");
|
|
62
|
+
const principal = await ensureConsumerPrincipal();
|
|
63
|
+
expect(principal).toMatch(/^did:pkh:eip155:8453:0x[a-f0-9]{40}$/);
|
|
64
|
+
expect(state.addedWallets).toHaveLength(1);
|
|
65
|
+
expect(state.addedWallets[0]?.chains).toEqual(["tempo", "base"]);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -3,12 +3,17 @@ export declare class ApiError extends Error {
|
|
|
3
3
|
readonly body?: unknown | undefined;
|
|
4
4
|
constructor(status: number, message: string, body?: unknown | undefined);
|
|
5
5
|
}
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
interface RequestOptions {
|
|
7
|
+
ensureConsumerPrincipal?: boolean;
|
|
8
|
+
extraHeaders?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export declare function apiGet<T>(path: string, options?: RequestOptions): Promise<T>;
|
|
11
|
+
export declare function apiPost<T>(path: string, body: unknown, options?: RequestOptions): Promise<T>;
|
|
8
12
|
/**
|
|
9
13
|
* POST with payment support. Uses the configured payment method to
|
|
10
14
|
* auto-handle 402 → sign → retry for paid endpoints.
|
|
11
15
|
* Pass `payWith` to specify a method, or omit for auto-detection.
|
|
12
16
|
*/
|
|
13
|
-
export declare function apiPostWithPayment<T>(path: string, body: unknown, payWith?: string): Promise<T>;
|
|
14
|
-
export declare function apiPut<T>(path: string, body: unknown): Promise<T>;
|
|
17
|
+
export declare function apiPostWithPayment<T>(path: string, body: unknown, payWith?: string, options?: RequestOptions): Promise<T>;
|
|
18
|
+
export declare function apiPut<T>(path: string, body: unknown, options?: RequestOptions): Promise<T>;
|
|
19
|
+
export {};
|
package/dist/core/api-client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getApiUrl, getApiKey } from "./config.js";
|
|
2
2
|
import { getPaymentFetch } from "./payments.js";
|
|
3
|
+
import { ensureConsumerPrincipal, getConsumerPrincipal } from "./principal.js";
|
|
3
4
|
// ── Error class ────────────────────────────────────────────────────
|
|
4
5
|
export class ApiError extends Error {
|
|
5
6
|
status;
|
|
@@ -11,8 +12,7 @@ export class ApiError extends Error {
|
|
|
11
12
|
this.name = "ApiError";
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
-
function buildHeaders() {
|
|
15
|
+
async function buildHeaders(options) {
|
|
16
16
|
const headers = {
|
|
17
17
|
"Content-Type": "application/json",
|
|
18
18
|
Accept: "application/json",
|
|
@@ -21,6 +21,15 @@ function buildHeaders() {
|
|
|
21
21
|
if (apiKey) {
|
|
22
22
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
23
23
|
}
|
|
24
|
+
const principal = options?.ensureConsumerPrincipal
|
|
25
|
+
? await ensureConsumerPrincipal()
|
|
26
|
+
: await getConsumerPrincipal();
|
|
27
|
+
if (principal) {
|
|
28
|
+
headers["X-AW-Consumer-Principal"] = principal;
|
|
29
|
+
}
|
|
30
|
+
if (options?.extraHeaders) {
|
|
31
|
+
Object.assign(headers, options.extraHeaders);
|
|
32
|
+
}
|
|
24
33
|
return headers;
|
|
25
34
|
}
|
|
26
35
|
async function handleResponse(response) {
|
|
@@ -45,54 +54,75 @@ async function handleResponse(response) {
|
|
|
45
54
|
}
|
|
46
55
|
return body;
|
|
47
56
|
}
|
|
57
|
+
function attachResponseMetadata(result, response) {
|
|
58
|
+
if (!result || typeof result !== "object") {
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
const record = result;
|
|
62
|
+
const headerToken = response.headers.get("x-feedback-token");
|
|
63
|
+
if (headerToken && !("feedback_token" in record)) {
|
|
64
|
+
record.feedback_token = headerToken;
|
|
65
|
+
}
|
|
66
|
+
const consumptionMode = response.headers.get("x-aw-consumption-mode");
|
|
67
|
+
if (consumptionMode) {
|
|
68
|
+
record.consumption_mode = consumptionMode;
|
|
69
|
+
}
|
|
70
|
+
const creditPackId = response.headers.get("x-aw-credit-pack-id");
|
|
71
|
+
if (creditPackId) {
|
|
72
|
+
record.credit_pack_id = creditPackId;
|
|
73
|
+
}
|
|
74
|
+
const principal = response.headers.get("x-aw-consumer-principal");
|
|
75
|
+
if (principal) {
|
|
76
|
+
record.consumer_principal = principal;
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
48
80
|
// ── Public API ─────────────────────────────────────────────────────
|
|
49
|
-
export async function apiGet(path) {
|
|
81
|
+
export async function apiGet(path, options) {
|
|
50
82
|
const url = `${getApiUrl()}${path}`;
|
|
51
83
|
const response = await fetch(url, {
|
|
52
84
|
method: "GET",
|
|
53
|
-
headers: buildHeaders(),
|
|
85
|
+
headers: await buildHeaders(options),
|
|
54
86
|
});
|
|
55
|
-
|
|
87
|
+
const result = await handleResponse(response);
|
|
88
|
+
return attachResponseMetadata(result, response);
|
|
56
89
|
}
|
|
57
|
-
export async function apiPost(path, body) {
|
|
90
|
+
export async function apiPost(path, body, options) {
|
|
58
91
|
const url = `${getApiUrl()}${path}`;
|
|
59
92
|
const response = await fetch(url, {
|
|
60
93
|
method: "POST",
|
|
61
|
-
headers: buildHeaders(),
|
|
94
|
+
headers: await buildHeaders(options),
|
|
62
95
|
body: JSON.stringify(body),
|
|
63
96
|
});
|
|
64
|
-
|
|
97
|
+
const result = await handleResponse(response);
|
|
98
|
+
return attachResponseMetadata(result, response);
|
|
65
99
|
}
|
|
66
100
|
/**
|
|
67
101
|
* POST with payment support. Uses the configured payment method to
|
|
68
102
|
* auto-handle 402 → sign → retry for paid endpoints.
|
|
69
103
|
* Pass `payWith` to specify a method, or omit for auto-detection.
|
|
70
104
|
*/
|
|
71
|
-
export async function apiPostWithPayment(path, body, payWith) {
|
|
105
|
+
export async function apiPostWithPayment(path, body, payWith, options) {
|
|
72
106
|
const url = `${getApiUrl()}${path}`;
|
|
73
107
|
const paymentFetch = await getPaymentFetch(payWith);
|
|
74
108
|
const response = await paymentFetch(url, {
|
|
75
109
|
method: "POST",
|
|
76
|
-
headers: buildHeaders(
|
|
110
|
+
headers: await buildHeaders({
|
|
111
|
+
ensureConsumerPrincipal: true,
|
|
112
|
+
...options,
|
|
113
|
+
}),
|
|
77
114
|
body: JSON.stringify(body),
|
|
78
115
|
});
|
|
79
116
|
const result = await handleResponse(response);
|
|
80
|
-
|
|
81
|
-
// The gateway also sends feedback_token as a header to survive this.
|
|
82
|
-
if (result && typeof result === "object" && !("feedback_token" in result)) {
|
|
83
|
-
const headerToken = response.headers.get("x-feedback-token");
|
|
84
|
-
if (headerToken) {
|
|
85
|
-
result.feedback_token = headerToken;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return result;
|
|
117
|
+
return attachResponseMetadata(result, response);
|
|
89
118
|
}
|
|
90
|
-
export async function apiPut(path, body) {
|
|
119
|
+
export async function apiPut(path, body, options) {
|
|
91
120
|
const url = `${getApiUrl()}${path}`;
|
|
92
121
|
const response = await fetch(url, {
|
|
93
122
|
method: "PUT",
|
|
94
|
-
headers: buildHeaders(),
|
|
123
|
+
headers: await buildHeaders(options),
|
|
95
124
|
body: JSON.stringify(body),
|
|
96
125
|
});
|
|
97
|
-
|
|
126
|
+
const result = await handleResponse(response);
|
|
127
|
+
return attachResponseMetadata(result, response);
|
|
98
128
|
}
|
|
@@ -1,23 +1,30 @@
|
|
|
1
|
+
export interface CardSetupResult {
|
|
2
|
+
last4: string;
|
|
3
|
+
brand: string;
|
|
4
|
+
consumerToken: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CardCapabilities {
|
|
7
|
+
spt_status: "enabled" | "unavailable" | "unknown";
|
|
8
|
+
message?: string;
|
|
9
|
+
}
|
|
1
10
|
/**
|
|
2
|
-
*
|
|
3
|
-
* QR code + short URL for the user to scan.
|
|
11
|
+
* Create a new card setup session or resume the pending one from config.
|
|
4
12
|
*/
|
|
5
|
-
export declare function
|
|
6
|
-
qr: string;
|
|
13
|
+
export declare function getOrCreatePendingCardSetup(): Promise<{
|
|
7
14
|
url: string;
|
|
8
15
|
token: string;
|
|
16
|
+
isNew: boolean;
|
|
9
17
|
}>;
|
|
10
18
|
/**
|
|
11
|
-
* Format the card setup prompt as
|
|
12
|
-
*
|
|
13
|
-
*
|
|
19
|
+
* Format the card setup prompt as a single text block.
|
|
20
|
+
*
|
|
21
|
+
* Some MCP clients only surface one text chunk or collapse multi-part tool
|
|
22
|
+
* output, which can hide the QR code entirely. Keeping everything in one
|
|
23
|
+
* message makes the browser handoff much more reliable.
|
|
14
24
|
*/
|
|
15
|
-
export declare function formatCardSetupBlocks(
|
|
25
|
+
export declare function formatCardSetupBlocks(url: string): string[];
|
|
16
26
|
/**
|
|
17
27
|
* Poll for card setup completion. Returns the card details or null on timeout.
|
|
18
28
|
*/
|
|
19
|
-
export declare function pollCardSetup(token: string, timeoutMs?: number): Promise<
|
|
20
|
-
|
|
21
|
-
brand: string;
|
|
22
|
-
consumerToken: string;
|
|
23
|
-
} | null>;
|
|
29
|
+
export declare function pollCardSetup(token: string, timeoutMs?: number): Promise<CardSetupResult | null>;
|
|
30
|
+
export declare function getCardCapabilities(): Promise<CardCapabilities>;
|