@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
package/src/core/card-setup.ts
CHANGED
|
@@ -1,51 +1,78 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { apiPost, apiGet, ApiError } from "./api-client.js";
|
|
2
|
+
import {
|
|
3
|
+
getApiUrl,
|
|
4
|
+
setCardConfig,
|
|
5
|
+
getPendingCardSetupToken,
|
|
6
|
+
setPendingCardSetupToken,
|
|
7
|
+
} from "./config.js";
|
|
8
|
+
|
|
9
|
+
export interface CardSetupResult {
|
|
10
|
+
last4: string;
|
|
11
|
+
brand: string;
|
|
12
|
+
consumerToken: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CardCapabilities {
|
|
16
|
+
spt_status: "enabled" | "unavailable" | "unknown";
|
|
17
|
+
message?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let cachedCapabilities:
|
|
21
|
+
| { value: CardCapabilities; expiresAt: number; apiUrl: string }
|
|
22
|
+
| null = null;
|
|
23
|
+
|
|
24
|
+
function buildCardSetupUrl(token: string): string {
|
|
25
|
+
return `${getApiUrl()}/card/handoff/${token}`;
|
|
26
|
+
}
|
|
4
27
|
|
|
5
28
|
/**
|
|
6
|
-
*
|
|
7
|
-
* QR code + short URL for the user to scan.
|
|
29
|
+
* Create a new card setup session or resume the pending one from config.
|
|
8
30
|
*/
|
|
9
|
-
export async function
|
|
10
|
-
qr: string;
|
|
31
|
+
export async function getOrCreatePendingCardSetup(): Promise<{
|
|
11
32
|
url: string;
|
|
12
33
|
token: string;
|
|
34
|
+
isNew: boolean;
|
|
13
35
|
}> {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
margin: 2,
|
|
24
|
-
});
|
|
36
|
+
const pendingToken = getPendingCardSetupToken();
|
|
37
|
+
if (pendingToken) {
|
|
38
|
+
const url = buildCardSetupUrl(pendingToken);
|
|
39
|
+
return {
|
|
40
|
+
url,
|
|
41
|
+
token: pendingToken,
|
|
42
|
+
isNew: false,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
25
45
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
46
|
+
const { token } = await apiPost<{ url: string; token: string }>("/card/setup", {});
|
|
47
|
+
setPendingCardSetupToken(token);
|
|
48
|
+
const url = buildCardSetupUrl(token);
|
|
29
49
|
|
|
30
|
-
return {
|
|
50
|
+
return {
|
|
51
|
+
url,
|
|
52
|
+
token,
|
|
53
|
+
isNew: true,
|
|
54
|
+
};
|
|
31
55
|
}
|
|
32
56
|
|
|
33
57
|
/**
|
|
34
|
-
* Format the card setup prompt as
|
|
35
|
-
*
|
|
36
|
-
*
|
|
58
|
+
* Format the card setup prompt as a single text block.
|
|
59
|
+
*
|
|
60
|
+
* Some MCP clients only surface one text chunk or collapse multi-part tool
|
|
61
|
+
* output, which can hide the QR code entirely. Keeping everything in one
|
|
62
|
+
* message makes the browser handoff much more reliable.
|
|
37
63
|
*/
|
|
38
|
-
export function formatCardSetupBlocks(
|
|
64
|
+
export function formatCardSetupBlocks(url: string): string[] {
|
|
39
65
|
return [
|
|
40
|
-
"\n" + qr.trim(),
|
|
41
66
|
[
|
|
42
|
-
|
|
67
|
+
"Open this setup page to connect your card:",
|
|
43
68
|
"",
|
|
44
69
|
url,
|
|
45
70
|
"",
|
|
46
|
-
|
|
71
|
+
"The setup page will take you to Stripe to securely save the card.",
|
|
72
|
+
"",
|
|
73
|
+
`Tell the user: "Open this setup page to connect your card. Let me know when you're done."`,
|
|
47
74
|
`After they confirm, call wallet_setup({ action: "add-card" }) to complete setup.`,
|
|
48
|
-
`Crypto wallets (
|
|
75
|
+
`Crypto wallets (Tempo, Base, or Solana USDC) are also available via wallet_setup({ action: "create" }).`,
|
|
49
76
|
].join("\n"),
|
|
50
77
|
];
|
|
51
78
|
}
|
|
@@ -56,11 +83,10 @@ export function formatCardSetupBlocks(qr: string, url: string): string[] {
|
|
|
56
83
|
export async function pollCardSetup(
|
|
57
84
|
token: string,
|
|
58
85
|
timeoutMs = 120_000,
|
|
59
|
-
): Promise<
|
|
86
|
+
): Promise<CardSetupResult | null> {
|
|
60
87
|
const deadline = Date.now() + timeoutMs;
|
|
61
88
|
|
|
62
89
|
while (Date.now() < deadline) {
|
|
63
|
-
await new Promise((r) => setTimeout(r, 3000));
|
|
64
90
|
try {
|
|
65
91
|
const status = await apiGet<{
|
|
66
92
|
status: string;
|
|
@@ -71,7 +97,7 @@ export async function pollCardSetup(
|
|
|
71
97
|
}>(`/card/status?token=${token}`);
|
|
72
98
|
|
|
73
99
|
if (status.status === "complete" && status.consumer_token) {
|
|
74
|
-
const card = {
|
|
100
|
+
const card: CardSetupResult = {
|
|
75
101
|
last4: status.card_last4 ?? "????",
|
|
76
102
|
brand: status.card_brand ?? "card",
|
|
77
103
|
consumerToken: status.consumer_token,
|
|
@@ -84,13 +110,62 @@ export async function pollCardSetup(
|
|
|
84
110
|
last4: card.last4,
|
|
85
111
|
brand: card.brand,
|
|
86
112
|
});
|
|
113
|
+
setPendingCardSetupToken(null);
|
|
87
114
|
|
|
88
115
|
return card;
|
|
89
116
|
}
|
|
90
|
-
} catch {
|
|
117
|
+
} catch (err) {
|
|
118
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
119
|
+
setPendingCardSetupToken(null);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
91
122
|
// Keep polling
|
|
92
123
|
}
|
|
124
|
+
|
|
125
|
+
const remainingMs = deadline - Date.now();
|
|
126
|
+
if (remainingMs <= 0) break;
|
|
127
|
+
await new Promise((r) => setTimeout(r, Math.min(3000, remainingMs)));
|
|
93
128
|
}
|
|
94
129
|
|
|
95
130
|
return null;
|
|
96
131
|
}
|
|
132
|
+
|
|
133
|
+
export async function getCardCapabilities(): Promise<CardCapabilities> {
|
|
134
|
+
const apiUrl = getApiUrl();
|
|
135
|
+
if (cachedCapabilities && cachedCapabilities.apiUrl === apiUrl && cachedCapabilities.expiresAt > Date.now()) {
|
|
136
|
+
return cachedCapabilities.value;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const capabilities = await apiGet<CardCapabilities>("/card/capabilities");
|
|
141
|
+
cachedCapabilities = {
|
|
142
|
+
value: capabilities,
|
|
143
|
+
apiUrl,
|
|
144
|
+
expiresAt: Date.now() + 30_000,
|
|
145
|
+
};
|
|
146
|
+
return capabilities;
|
|
147
|
+
} catch (err) {
|
|
148
|
+
if (err instanceof ApiError) {
|
|
149
|
+
const fallback = {
|
|
150
|
+
spt_status: "unknown",
|
|
151
|
+
message: err.message,
|
|
152
|
+
} satisfies CardCapabilities;
|
|
153
|
+
cachedCapabilities = {
|
|
154
|
+
value: fallback,
|
|
155
|
+
apiUrl,
|
|
156
|
+
expiresAt: Date.now() + 10_000,
|
|
157
|
+
};
|
|
158
|
+
return fallback;
|
|
159
|
+
}
|
|
160
|
+
const fallback = {
|
|
161
|
+
spt_status: "unknown",
|
|
162
|
+
message: "Could not determine card payment availability.",
|
|
163
|
+
} satisfies CardCapabilities;
|
|
164
|
+
cachedCapabilities = {
|
|
165
|
+
value: fallback,
|
|
166
|
+
apiUrl,
|
|
167
|
+
expiresAt: Date.now() + 10_000,
|
|
168
|
+
};
|
|
169
|
+
return fallback;
|
|
170
|
+
}
|
|
171
|
+
}
|
package/src/core/config.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface WalletEntry {
|
|
|
9
9
|
keyType: "evm" | "ows"; // key format: raw EVM key or OWS-managed wallet
|
|
10
10
|
key?: string; // private key (legacy / raw EVM only)
|
|
11
11
|
owsWalletId?: string; // OWS wallet reference (when keyType === "ows")
|
|
12
|
-
chains: string[]; // enabled chains: "tempo", "base"
|
|
12
|
+
chains: string[]; // enabled chains: "tempo", "base", "solana"
|
|
13
13
|
defaultChain?: string; // which chain to use by default for this wallet
|
|
14
14
|
label?: string; // user-friendly name
|
|
15
15
|
}
|
|
@@ -21,6 +21,19 @@ export interface CardConfig {
|
|
|
21
21
|
brand: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export interface SpendPolicy {
|
|
25
|
+
maxPerTxUsd?: number;
|
|
26
|
+
maxPerDayUsd?: number;
|
|
27
|
+
requireConfirmationAboveUsd?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SpendLedgerEntry {
|
|
31
|
+
method: string;
|
|
32
|
+
amountUsd: number;
|
|
33
|
+
day: string;
|
|
34
|
+
timestamp: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
export interface Config {
|
|
25
38
|
apiUrl: string;
|
|
26
39
|
apiKey: string | null;
|
|
@@ -29,11 +42,16 @@ export interface Config {
|
|
|
29
42
|
defaultWallet: string | null;
|
|
30
43
|
defaultPaymentMethod?: string;
|
|
31
44
|
card: CardConfig | null;
|
|
45
|
+
pendingCardSetupToken?: string | null;
|
|
32
46
|
favorites: string[];
|
|
33
47
|
/** Require user confirmation before spending. Default: true. Set false for headless/automated use. */
|
|
34
48
|
confirmBeforeSpend: boolean;
|
|
35
49
|
/** Auto-tip amount in USD for successful runs. Default: 0 (no auto-tip). */
|
|
36
50
|
defaultTipAmount: number;
|
|
51
|
+
/** Optional per-method spend policies enforced client-side by MCP tools. */
|
|
52
|
+
spendPolicies?: Record<string, SpendPolicy>;
|
|
53
|
+
/** Daily spend ledger used to enforce max-per-day limits. */
|
|
54
|
+
spendLedger?: SpendLedgerEntry[];
|
|
37
55
|
}
|
|
38
56
|
|
|
39
57
|
/** All supported chain identifiers. */
|
|
@@ -80,6 +98,9 @@ interface LegacyConfig {
|
|
|
80
98
|
wallets?: WalletEntry[];
|
|
81
99
|
defaultWallet?: string | null;
|
|
82
100
|
card?: CardConfig | null;
|
|
101
|
+
pendingCardSetupToken?: string | null;
|
|
102
|
+
spendPolicies?: Record<string, SpendPolicy>;
|
|
103
|
+
spendLedger?: SpendLedgerEntry[];
|
|
83
104
|
}
|
|
84
105
|
|
|
85
106
|
/**
|
|
@@ -96,10 +117,14 @@ function migrateIfNeeded(raw: LegacyConfig): Config {
|
|
|
96
117
|
userId: raw.userId ?? null,
|
|
97
118
|
wallets: raw.wallets,
|
|
98
119
|
defaultWallet: raw.defaultWallet ?? null,
|
|
120
|
+
defaultPaymentMethod: raw.defaultPaymentMethod ?? undefined,
|
|
99
121
|
card: raw.card ?? null,
|
|
122
|
+
pendingCardSetupToken: (r.pendingCardSetupToken as string | null | undefined) ?? null,
|
|
100
123
|
favorites: r.favorites as string[] ?? [],
|
|
101
124
|
confirmBeforeSpend: r.confirmBeforeSpend !== false,
|
|
102
125
|
defaultTipAmount: typeof r.defaultTipAmount === "number" ? r.defaultTipAmount : 0,
|
|
126
|
+
spendPolicies: r.spendPolicies as Record<string, SpendPolicy> | undefined,
|
|
127
|
+
spendLedger: r.spendLedger as SpendLedgerEntry[] | undefined,
|
|
103
128
|
};
|
|
104
129
|
}
|
|
105
130
|
|
|
@@ -168,10 +193,14 @@ function migrateIfNeeded(raw: LegacyConfig): Config {
|
|
|
168
193
|
userId: raw.userId ?? null,
|
|
169
194
|
wallets,
|
|
170
195
|
defaultWallet,
|
|
196
|
+
defaultPaymentMethod: raw.defaultPaymentMethod ?? undefined,
|
|
171
197
|
card,
|
|
198
|
+
pendingCardSetupToken: null,
|
|
172
199
|
favorites: [],
|
|
173
200
|
confirmBeforeSpend: true,
|
|
174
201
|
defaultTipAmount: 0,
|
|
202
|
+
spendPolicies: {},
|
|
203
|
+
spendLedger: [],
|
|
175
204
|
};
|
|
176
205
|
|
|
177
206
|
// Write migrated config (only if there was something to migrate)
|
|
@@ -193,9 +222,12 @@ export function getConfig(): Config {
|
|
|
193
222
|
wallets: [],
|
|
194
223
|
defaultWallet: null,
|
|
195
224
|
card: null,
|
|
225
|
+
pendingCardSetupToken: null,
|
|
196
226
|
favorites: [],
|
|
197
227
|
confirmBeforeSpend: true,
|
|
198
228
|
defaultTipAmount: 0,
|
|
229
|
+
spendPolicies: {},
|
|
230
|
+
spendLedger: [],
|
|
199
231
|
};
|
|
200
232
|
|
|
201
233
|
if (!existsSync(CONFIG_FILE)) {
|
|
@@ -242,6 +274,26 @@ export function getDefaultTipAmount(): number {
|
|
|
242
274
|
return getConfig().defaultTipAmount;
|
|
243
275
|
}
|
|
244
276
|
|
|
277
|
+
export function getSpendPolicy(method: string): SpendPolicy | null {
|
|
278
|
+
const policies = getConfig().spendPolicies ?? {};
|
|
279
|
+
return policies[method] ?? null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function setSpendPolicy(method: string, policy: SpendPolicy): void {
|
|
283
|
+
const config = getConfig();
|
|
284
|
+
const policies = config.spendPolicies ?? {};
|
|
285
|
+
policies[method] = policy;
|
|
286
|
+
saveConfig({ spendPolicies: policies });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function getSpendLedger(): SpendLedgerEntry[] {
|
|
290
|
+
return getConfig().spendLedger ?? [];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function saveSpendLedger(entries: SpendLedgerEntry[]): void {
|
|
294
|
+
saveConfig({ spendLedger: entries });
|
|
295
|
+
}
|
|
296
|
+
|
|
245
297
|
// ── Wallet helpers ─────────────────────────────────────────────────
|
|
246
298
|
|
|
247
299
|
/**
|
|
@@ -351,13 +403,32 @@ export function getCardConfig(): CardConfig | null {
|
|
|
351
403
|
* Save card configuration after setup.
|
|
352
404
|
*/
|
|
353
405
|
export function setCardConfig(card: CardConfig | null): void {
|
|
406
|
+
const current = getConfig();
|
|
354
407
|
if (card) {
|
|
355
|
-
saveConfig({
|
|
408
|
+
saveConfig({
|
|
409
|
+
card,
|
|
410
|
+
defaultPaymentMethod: "card",
|
|
411
|
+
pendingCardSetupToken: null,
|
|
412
|
+
});
|
|
356
413
|
} else {
|
|
357
|
-
saveConfig({
|
|
414
|
+
saveConfig({
|
|
415
|
+
card,
|
|
416
|
+
pendingCardSetupToken: null,
|
|
417
|
+
defaultPaymentMethod: current.defaultPaymentMethod === "card"
|
|
418
|
+
? undefined
|
|
419
|
+
: current.defaultPaymentMethod,
|
|
420
|
+
});
|
|
358
421
|
}
|
|
359
422
|
}
|
|
360
423
|
|
|
424
|
+
export function getPendingCardSetupToken(): string | null {
|
|
425
|
+
return getConfig().pendingCardSetupToken ?? null;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function setPendingCardSetupToken(token: string | null): void {
|
|
429
|
+
saveConfig({ pendingCardSetupToken: token });
|
|
430
|
+
}
|
|
431
|
+
|
|
361
432
|
/**
|
|
362
433
|
* Resolve a payment method string to a wallet + chain.
|
|
363
434
|
* Accepts: wallet ID, chain name, or "card".
|
package/src/core/formatters.ts
CHANGED
|
@@ -36,11 +36,10 @@ export function isFileOutput(output: unknown): output is { type: "file"; url: st
|
|
|
36
36
|
|
|
37
37
|
// ── Price formatting ─────────────────────────────────────────────
|
|
38
38
|
|
|
39
|
-
export function formatPrice(
|
|
40
|
-
if (!
|
|
41
|
-
const p = parseFloat(
|
|
42
|
-
|
|
43
|
-
return `$${p.toFixed(3)}/1k tokens`;
|
|
39
|
+
export function formatPrice(pricePerRunUsd?: string | null): string {
|
|
40
|
+
if (!pricePerRunUsd) return "free";
|
|
41
|
+
const p = parseFloat(pricePerRunUsd);
|
|
42
|
+
return `$${p.toFixed(2)}/req`;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
// ── Agent line (compact, one line per agent) ─────────────────────
|
|
@@ -52,8 +51,7 @@ interface AgentLike {
|
|
|
52
51
|
avgRating?: number | null;
|
|
53
52
|
ratingCount?: number;
|
|
54
53
|
totalExecutions?: number;
|
|
55
|
-
|
|
56
|
-
pricingModel?: string;
|
|
54
|
+
pricePerRunUsd?: string;
|
|
57
55
|
stats?: { completedJobs?: number; avgRating?: number | null };
|
|
58
56
|
[key: string]: unknown;
|
|
59
57
|
}
|
|
@@ -63,7 +61,7 @@ export function agentLine(agent: AgentLike): string {
|
|
|
63
61
|
const slug = agent.slug ? ` (${agent.slug})` : "";
|
|
64
62
|
const rating = agent.avgRating ?? agent.stats?.avgRating ?? null;
|
|
65
63
|
const jobs = agent.stats?.completedJobs ?? agent.totalExecutions ?? 0;
|
|
66
|
-
const price = formatPrice(agent.
|
|
64
|
+
const price = formatPrice(agent.pricePerRunUsd);
|
|
67
65
|
const reliability = agent.successRate != null && Number(agent.successRate) < 1
|
|
68
66
|
? ` • ${(Number(agent.successRate) * 100).toFixed(0)}% reliable`
|
|
69
67
|
: "";
|
|
@@ -118,6 +116,8 @@ interface RunResultLike {
|
|
|
118
116
|
estimated_cost?: number;
|
|
119
117
|
input_tokens?: number;
|
|
120
118
|
tags?: string[];
|
|
119
|
+
consumption_mode?: string;
|
|
120
|
+
credit_pack_id?: string;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
export function formatRunResult(result: RunResultLike, opts?: { paymentMethod?: string }): string {
|
|
@@ -147,6 +147,7 @@ export function formatRunResult(result: RunResultLike, opts?: { paymentMethod?:
|
|
|
147
147
|
|
|
148
148
|
// Summary line
|
|
149
149
|
const cost = result.cost ?? result.estimated_cost;
|
|
150
|
+
const usedCreditPack = result.consumption_mode === "credit_pack";
|
|
150
151
|
const status = result.status === "success" || result.status === "completed" ? "✓" : "✗";
|
|
151
152
|
const agent = result.agent_name ?? result.agent_id ?? "";
|
|
152
153
|
const costStr = cost != null ? `$${cost.toFixed(cost < 0.01 ? 6 : 2)}` : "";
|
|
@@ -157,7 +158,10 @@ export function formatRunResult(result: RunResultLike, opts?: { paymentMethod?:
|
|
|
157
158
|
if (result.error_code) {
|
|
158
159
|
lines.push(`Error: ${result.error_code}`);
|
|
159
160
|
}
|
|
160
|
-
if (
|
|
161
|
+
if (usedCreditPack) {
|
|
162
|
+
lines.push(`Covered by credit pack${result.credit_pack_id ? ` (${result.credit_pack_id})` : ""}`);
|
|
163
|
+
}
|
|
164
|
+
if (costStr && !usedCreditPack) {
|
|
161
165
|
lines.push(`Paid: ${costStr}${method ? ` via ${method}` : ""}`);
|
|
162
166
|
}
|
|
163
167
|
if (result.job_id) {
|
package/src/core/index.ts
CHANGED
package/src/core/ows-adapter.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { LocalAccount } from "viem/accounts";
|
|
12
12
|
import type { Hex } from "viem";
|
|
13
|
+
import type { Keypair } from "@solana/web3.js";
|
|
13
14
|
|
|
14
15
|
// Note: OWS provides encrypted key storage at rest (~/.ows/, AES-256-GCM).
|
|
15
16
|
// For EVM signing, we export the secp256k1 key and use viem's native
|
|
@@ -127,6 +128,33 @@ function findEvmAccount(
|
|
|
127
128
|
return evm;
|
|
128
129
|
}
|
|
129
130
|
|
|
131
|
+
function findSolanaAccount(
|
|
132
|
+
wallet: OwsWalletInfo,
|
|
133
|
+
): OwsAccountInfo {
|
|
134
|
+
const solana = wallet.accounts.find((a) => a.chainId.startsWith("solana"));
|
|
135
|
+
if (!solana) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Wallet "${wallet.name}" (${wallet.id}) has no Solana account.`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return solana;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function ed25519HexToBytes(privateKeyHex: string): Uint8Array {
|
|
144
|
+
return Uint8Array.from(Buffer.from(privateKeyHex, "hex"));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function keypairFromEd25519Hex(privateKeyHex: string, KeypairCtor: typeof import("@solana/web3.js").Keypair): Keypair {
|
|
148
|
+
const bytes = ed25519HexToBytes(privateKeyHex);
|
|
149
|
+
if (bytes.length === 32) {
|
|
150
|
+
return KeypairCtor.fromSeed(bytes);
|
|
151
|
+
}
|
|
152
|
+
if (bytes.length === 64) {
|
|
153
|
+
return KeypairCtor.fromSecretKey(bytes);
|
|
154
|
+
}
|
|
155
|
+
throw new Error(`Unsupported ed25519 key length: ${bytes.length} bytes.`);
|
|
156
|
+
}
|
|
157
|
+
|
|
130
158
|
// ── Public API ───────────────────────────────────────────────────
|
|
131
159
|
|
|
132
160
|
/**
|
|
@@ -169,16 +197,45 @@ export async function owsAccountFromWalletId(
|
|
|
169
197
|
return privateKeyToAccount(`0x${keys.secp256k1}` as Hex);
|
|
170
198
|
}
|
|
171
199
|
|
|
200
|
+
export async function owsSolanaKeypairFromWalletId(
|
|
201
|
+
walletId: string,
|
|
202
|
+
): Promise<Keypair> {
|
|
203
|
+
const ows = await loadOws();
|
|
204
|
+
const wallet = ows.getWallet(walletId);
|
|
205
|
+
findSolanaAccount(wallet);
|
|
206
|
+
|
|
207
|
+
const exported = ows.exportWallet(walletId);
|
|
208
|
+
const keys = JSON.parse(exported) as { secp256k1?: string; ed25519?: string };
|
|
209
|
+
if (!keys.ed25519) {
|
|
210
|
+
throw new Error(`Wallet "${wallet.name}" has no ed25519 key for Solana signing.`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const { Keypair } = await import("@solana/web3.js");
|
|
214
|
+
return keypairFromEd25519Hex(keys.ed25519, Keypair);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export async function getOwsWalletAddress(
|
|
218
|
+
walletId: string,
|
|
219
|
+
chain: "evm" | "solana" = "evm",
|
|
220
|
+
): Promise<string> {
|
|
221
|
+
const ows = await loadOws();
|
|
222
|
+
const wallet = ows.getWallet(walletId);
|
|
223
|
+
return chain === "solana"
|
|
224
|
+
? findSolanaAccount(wallet).address
|
|
225
|
+
: findEvmAccount(wallet).address;
|
|
226
|
+
}
|
|
227
|
+
|
|
172
228
|
/**
|
|
173
229
|
* Create a new OWS wallet and return its ID + EVM address.
|
|
174
230
|
*/
|
|
175
231
|
export async function createOwsWallet(
|
|
176
232
|
name: string,
|
|
233
|
+
chain: "evm" | "solana" = "evm",
|
|
177
234
|
): Promise<{ walletId: string; address: string }> {
|
|
178
235
|
const ows = await loadOws();
|
|
179
236
|
const wallet = ows.createWallet(name);
|
|
180
|
-
const
|
|
181
|
-
return { walletId: wallet.id, address:
|
|
237
|
+
const account = chain === "solana" ? findSolanaAccount(wallet) : findEvmAccount(wallet);
|
|
238
|
+
return { walletId: wallet.id, address: account.address };
|
|
182
239
|
}
|
|
183
240
|
|
|
184
241
|
/**
|
|
@@ -187,6 +244,7 @@ export async function createOwsWallet(
|
|
|
187
244
|
export async function importKeyToOws(
|
|
188
245
|
privateKey: string,
|
|
189
246
|
name: string,
|
|
247
|
+
chain: "evm" | "solana" = "evm",
|
|
190
248
|
): Promise<{ walletId: string; address: string }> {
|
|
191
249
|
const ows = await loadOws();
|
|
192
250
|
const normalizedKey = privateKey.startsWith("0x")
|
|
@@ -197,10 +255,10 @@ export async function importKeyToOws(
|
|
|
197
255
|
normalizedKey,
|
|
198
256
|
null,
|
|
199
257
|
null,
|
|
200
|
-
|
|
258
|
+
chain,
|
|
201
259
|
);
|
|
202
|
-
const
|
|
203
|
-
return { walletId: wallet.id, address:
|
|
260
|
+
const account = chain === "solana" ? findSolanaAccount(wallet) : findEvmAccount(wallet);
|
|
261
|
+
return { walletId: wallet.id, address: account.address };
|
|
204
262
|
}
|
|
205
263
|
|
|
206
264
|
/**
|
|
@@ -209,14 +267,22 @@ export async function importKeyToOws(
|
|
|
209
267
|
export async function listOwsWallets(): Promise<
|
|
210
268
|
Array<{ id: string; name: string; address: string }>
|
|
211
269
|
> {
|
|
270
|
+
return listOwsWalletsByChain("evm");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export async function listOwsWalletsByChain(
|
|
274
|
+
chain: "evm" | "solana" = "evm",
|
|
275
|
+
): Promise<Array<{ id: string; name: string; address: string }>> {
|
|
212
276
|
const ows = await loadOws();
|
|
213
277
|
const wallets = ows.listWallets();
|
|
214
278
|
const result: Array<{ id: string; name: string; address: string }> = [];
|
|
215
279
|
|
|
216
280
|
for (const w of wallets) {
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
281
|
+
const account = chain === "solana"
|
|
282
|
+
? w.accounts.find((a) => a.chainId.startsWith("solana"))
|
|
283
|
+
: w.accounts.find((a) => a.chainId.startsWith("eip155") || a.chainId.startsWith("evm"));
|
|
284
|
+
if (account) {
|
|
285
|
+
result.push({ id: w.id, name: w.name, address: account.address });
|
|
220
286
|
}
|
|
221
287
|
}
|
|
222
288
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { apiGet } from "./api-client.js";
|
|
2
|
+
import type { AgentRecord } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export interface CreditPackOffer {
|
|
5
|
+
pack_id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
included_units: number;
|
|
8
|
+
price_usd: string;
|
|
9
|
+
effective_price_per_unit_usd?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CreditPackRecord {
|
|
13
|
+
id: string;
|
|
14
|
+
status: string;
|
|
15
|
+
unit_type: string;
|
|
16
|
+
included_units: number;
|
|
17
|
+
remaining_units: number;
|
|
18
|
+
price_usd: string;
|
|
19
|
+
purchased_at: string;
|
|
20
|
+
pack?: {
|
|
21
|
+
name?: string | null;
|
|
22
|
+
key?: string | null;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CreditPackProgramInfo {
|
|
27
|
+
unit_type?: string;
|
|
28
|
+
packs?: Array<{
|
|
29
|
+
key?: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
included_units?: number;
|
|
32
|
+
price_usd?: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CreditPackInventory {
|
|
37
|
+
consumer_principal: string | null;
|
|
38
|
+
offers: CreditPackOffer[];
|
|
39
|
+
balances: CreditPackRecord[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getCreditPackProgram(agent: AgentRecord): CreditPackProgramInfo | null {
|
|
43
|
+
const payment = (agent.payment ?? {}) as Record<string, unknown>;
|
|
44
|
+
const creditPacks = payment.credit_packs as CreditPackProgramInfo | null | undefined;
|
|
45
|
+
return creditPacks ?? null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function getCreditPackInventory(agentId: string): Promise<CreditPackInventory | null> {
|
|
49
|
+
try {
|
|
50
|
+
return await apiGet<CreditPackInventory>(`/agents/${agentId}/credit-packs`, { ensureConsumerPrincipal: true });
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getActiveCreditPack(inventory: CreditPackInventory | null): CreditPackRecord | null {
|
|
57
|
+
if (!inventory) return null;
|
|
58
|
+
return inventory.balances
|
|
59
|
+
.filter((pack) => pack.status === "active" && pack.remaining_units > 0)
|
|
60
|
+
.sort((a, b) => new Date(a.purchased_at).getTime() - new Date(b.purchased_at).getTime())[0] ?? null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function formatCreditPackOffer(offer: CreditPackOffer): string {
|
|
64
|
+
const unitPrice = offer.effective_price_per_unit_usd
|
|
65
|
+
? ` ($${Number(offer.effective_price_per_unit_usd).toFixed(2)}/unit)`
|
|
66
|
+
: "";
|
|
67
|
+
return `${offer.label} — ${offer.included_units} units for $${offer.price_usd}${unitPrice}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function formatCreditPack(pack: CreditPackRecord): string {
|
|
71
|
+
const label = pack.pack?.name ?? pack.pack?.key ?? "Credit Pack";
|
|
72
|
+
const statusPrefix = pack.status === "active" ? "" : `${pack.status} • `;
|
|
73
|
+
return `${label}: ${statusPrefix}${pack.remaining_units}/${pack.included_units} ${pack.unit_type}s remaining`;
|
|
74
|
+
}
|