x402-proxy-openclaw 0.10.9

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/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # x402-proxy-openclaw
2
+
3
+ OpenClaw plugin for x402 and MPP payments, wallet tools, and paid inference proxying.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ openclaw plugins install x402-proxy-openclaw
9
+ ```
10
+
11
+ ## What It Adds
12
+
13
+ - `x_wallet` and `x_request` tools, with `x_balance` and `x_payment` aliases
14
+ - `/x_wallet` and `/x_send` commands
15
+ - an HTTP route proxy for upstream inference endpoints
16
+ - a built-in `surf` provider at `/x402-proxy/v1`
17
+
18
+ By default, the plugin proxies `https://surf.cascade.fyi/api/v1/inference` and prefers MPP.
19
+
20
+ ## Relationship To The CLI Package
21
+
22
+ The standalone CLI and library remain published as `x402-proxy`.
23
+
24
+ Use `x402-proxy` when you want:
25
+ - paid HTTP requests from the command line
26
+ - the MCP proxy
27
+ - local wallet setup and export flows
28
+
29
+ Use `x402-proxy-openclaw` when you want:
30
+ - OpenClaw gateway integration
31
+ - plugin-managed tools, commands, and routes
32
+
33
+ ## License
34
+
35
+ Apache-2.0
@@ -0,0 +1,149 @@
1
+ import { getHistoryPath } from "../lib/config.js";
2
+ import { resolveWallet } from "../lib/wallet-resolution.js";
3
+ import { calcSpend, formatAmount, formatTxLine, formatUsdcValue, readHistory } from "../history.js";
4
+ import { dim, info } from "../lib/output.js";
5
+ import { address, getAddressEncoder, getProgramDerivedAddress } from "@solana/kit";
6
+ import { buildCommand } from "@stricli/core";
7
+ import pc from "picocolors";
8
+ //#region packages/x402-proxy/src/commands/wallet.ts
9
+ const BASE_RPC = "https://mainnet.base.org";
10
+ const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
11
+ const TEMPO_RPC = "https://rpc.presto.tempo.xyz";
12
+ const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
13
+ const USDC_TEMPO = "0x20C000000000000000000000b9537d11c60E8b50";
14
+ const USDC_SOLANA_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
15
+ const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
16
+ const ATA_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
17
+ async function rpcCall(url, method, params) {
18
+ const res = await fetch(url, {
19
+ method: "POST",
20
+ headers: { "Content-Type": "application/json" },
21
+ body: JSON.stringify({
22
+ jsonrpc: "2.0",
23
+ method,
24
+ params,
25
+ id: 1
26
+ })
27
+ });
28
+ if (!res.ok) throw new Error(`RPC ${method} failed: ${res.status} ${res.statusText}`);
29
+ return res.json();
30
+ }
31
+ async function fetchEvmBalances(address) {
32
+ const usdcData = `0x70a08231${address.slice(2).padStart(64, "0")}`;
33
+ const [ethRes, usdcRes] = await Promise.all([rpcCall(BASE_RPC, "eth_getBalance", [address, "latest"]), rpcCall(BASE_RPC, "eth_call", [{
34
+ to: USDC_BASE,
35
+ data: usdcData
36
+ }, "latest"])]);
37
+ return {
38
+ eth: ethRes.result ? (Number(BigInt(ethRes.result)) / 0xde0b6b3a7640000).toFixed(6) : "?",
39
+ usdc: usdcRes.result ? formatUsdcValue(Number(BigInt(usdcRes.result)) / 1e6) : "?"
40
+ };
41
+ }
42
+ async function fetchTempoBalances(address) {
43
+ const res = await rpcCall(TEMPO_RPC, "eth_call", [{
44
+ to: USDC_TEMPO,
45
+ data: `0x70a08231${address.slice(2).padStart(64, "0")}`
46
+ }, "latest"]);
47
+ return { usdc: res.result ? formatUsdcValue(Number(BigInt(res.result)) / 1e6) : "?" };
48
+ }
49
+ async function getUsdcAta(owner) {
50
+ const encoder = getAddressEncoder();
51
+ const [ata] = await getProgramDerivedAddress({
52
+ programAddress: address(ATA_PROGRAM),
53
+ seeds: [
54
+ encoder.encode(address(owner)),
55
+ encoder.encode(address(TOKEN_PROGRAM)),
56
+ encoder.encode(address(USDC_SOLANA_MINT))
57
+ ]
58
+ });
59
+ return ata;
60
+ }
61
+ async function fetchSolanaBalances(ownerAddress) {
62
+ const ata = await getUsdcAta(ownerAddress);
63
+ const [solRes, usdcRes] = await Promise.all([rpcCall(SOLANA_RPC, "getBalance", [ownerAddress]), rpcCall(SOLANA_RPC, "getTokenAccountBalance", [ata])]);
64
+ const sol = solRes.result?.value != null ? (solRes.result.value / 1e9).toFixed(6) : "?";
65
+ const usdcVal = usdcRes.result?.value;
66
+ return {
67
+ sol,
68
+ usdc: usdcVal ? formatUsdcValue(Number(usdcVal.uiAmountString)) : "0"
69
+ };
70
+ }
71
+ function balanceLine(usdc, native, nativeSymbol) {
72
+ return pc.dim(` (${usdc} USDC, ${native} ${nativeSymbol})`);
73
+ }
74
+ async function fetchAllBalances(evmAddress, solanaAddress) {
75
+ const [evmResult, solResult, tempoResult] = await Promise.allSettled([
76
+ evmAddress ? fetchEvmBalances(evmAddress) : Promise.resolve(null),
77
+ solanaAddress ? fetchSolanaBalances(solanaAddress) : Promise.resolve(null),
78
+ evmAddress ? fetchTempoBalances(evmAddress) : Promise.resolve(null)
79
+ ]);
80
+ return {
81
+ evm: evmResult.status === "fulfilled" ? evmResult.value : null,
82
+ sol: solResult.status === "fulfilled" ? solResult.value : null,
83
+ tempo: tempoResult.status === "fulfilled" ? tempoResult.value : null
84
+ };
85
+ }
86
+ buildCommand({
87
+ docs: { brief: "Show wallet addresses and balances" },
88
+ parameters: {
89
+ flags: { verbose: {
90
+ kind: "boolean",
91
+ brief: "Show transaction IDs",
92
+ default: false
93
+ } },
94
+ positional: {
95
+ kind: "tuple",
96
+ parameters: []
97
+ }
98
+ },
99
+ async func(flags) {
100
+ const wallet = resolveWallet();
101
+ if (wallet.source === "none") {
102
+ console.log(pc.yellow("No wallet configured."));
103
+ console.log(pc.dim(`\nRun:\n ${pc.cyan("$ npx x402-proxy setup")}\n\nOr set ${pc.cyan("X402_PROXY_WALLET_MNEMONIC")} environment variable.`));
104
+ process.exit(1);
105
+ }
106
+ console.log();
107
+ info("Wallet");
108
+ console.log();
109
+ console.log(pc.dim(` Source: ${wallet.source}`));
110
+ const { evm, sol, tempo } = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
111
+ if (wallet.evmAddress) {
112
+ const bal = evm ? balanceLine(evm.usdc, evm.eth, "ETH") : pc.dim(" (network error)");
113
+ console.log(` Base: ${pc.green(wallet.evmAddress)}${bal}`);
114
+ }
115
+ if (wallet.evmAddress) {
116
+ const bal = tempo ? pc.dim(` (${tempo.usdc} USDC)`) : pc.dim(" (network error)");
117
+ console.log(` Tempo: ${pc.green(wallet.evmAddress)}${bal}`);
118
+ }
119
+ if (wallet.solanaAddress) {
120
+ const bal = sol ? balanceLine(sol.usdc, sol.sol, "SOL") : pc.dim(" (network error)");
121
+ console.log(` Solana: ${pc.green(wallet.solanaAddress)}${bal}`);
122
+ }
123
+ const evmEmpty = !evm || Number(evm.usdc) === 0;
124
+ const solEmpty = !sol || Number(sol.usdc) === 0;
125
+ const tempoEmpty = !tempo || Number(tempo.usdc) === 0;
126
+ if (evmEmpty && solEmpty && tempoEmpty) {
127
+ console.log();
128
+ dim(" Send USDC to any address above to start using paid APIs.");
129
+ }
130
+ console.log();
131
+ const records = readHistory(getHistoryPath());
132
+ if (records.length > 0) {
133
+ const spend = calcSpend(records);
134
+ const recent = records.slice(-10);
135
+ dim(" Recent transactions:");
136
+ for (const r of recent) {
137
+ const line = formatTxLine(r, { verbose: flags.verbose }).replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
138
+ console.log(line);
139
+ }
140
+ console.log();
141
+ console.log(pc.dim(` Today: ${formatAmount(spend.today, "USDC")} | Total: ${formatAmount(spend.total, "USDC")} | ${spend.count} tx`));
142
+ } else dim(" No transactions yet.");
143
+ console.log();
144
+ console.log(pc.dim(" See also: wallet history, wallet export-key"));
145
+ console.log();
146
+ }
147
+ });
148
+ //#endregion
149
+ export { fetchAllBalances };
@@ -0,0 +1,218 @@
1
+ import { appendHistory, formatTxLine, readHistory } from "./history.js";
2
+ import { checkAtaExists, getUsdcBalance, transferUsdc } from "./solana.js";
3
+ import { SOL_MAINNET, getWalletSnapshot } from "./tools.js";
4
+ //#region packages/x402-proxy/src/openclaw/commands.ts
5
+ const HISTORY_PAGE_SIZE = 5;
6
+ const STATUS_HISTORY_COUNT = 3;
7
+ const INLINE_HISTORY_TOKEN_THRESHOLD = 3;
8
+ const SEND_CONFIRM_TTL_MS = 300 * 1e3;
9
+ const TOKEN_SYMBOL_CACHE_MAX = 200;
10
+ const tokenSymbolCache = /* @__PURE__ */ new Map();
11
+ const pendingSends = /* @__PURE__ */ new Map();
12
+ async function resolveTokenSymbols(mints) {
13
+ const result = /* @__PURE__ */ new Map();
14
+ const toResolve = [];
15
+ for (const m of mints) {
16
+ const cached = tokenSymbolCache.get(m);
17
+ if (cached) result.set(m, cached);
18
+ else toResolve.push(m);
19
+ }
20
+ if (toResolve.length === 0) return result;
21
+ try {
22
+ const res = await globalThis.fetch(`https://api.dexscreener.com/tokens/v1/solana/${toResolve.join(",")}`, { signal: AbortSignal.timeout(5e3) });
23
+ if (!res.ok) return result;
24
+ const pairs = await res.json();
25
+ for (const pair of pairs) {
26
+ const addr = pair.baseToken?.address;
27
+ const sym = pair.baseToken?.symbol;
28
+ if (addr && sym && toResolve.includes(addr)) {
29
+ if (tokenSymbolCache.size >= TOKEN_SYMBOL_CACHE_MAX) {
30
+ const oldest = tokenSymbolCache.keys().next();
31
+ if (!oldest.done) tokenSymbolCache.delete(oldest.value);
32
+ }
33
+ tokenSymbolCache.set(addr, sym);
34
+ result.set(addr, sym);
35
+ }
36
+ }
37
+ } catch {}
38
+ return result;
39
+ }
40
+ function senderKey(cmdCtx) {
41
+ return [
42
+ cmdCtx.channel,
43
+ cmdCtx.accountId ?? "",
44
+ cmdCtx.senderId ?? cmdCtx.from ?? "",
45
+ String(cmdCtx.messageThreadId ?? "")
46
+ ].join(":");
47
+ }
48
+ async function executeSend(amountStr, destination, wallet, signer, rpc, histPath) {
49
+ if (!signer) return { text: "Wallet not loaded yet. Please wait for the gateway to finish starting." };
50
+ try {
51
+ if (!await checkAtaExists(rpc, destination)) return { text: "Recipient does not have a USDC token account.\nThey need to receive USDC at least once to create one." };
52
+ let amountRaw;
53
+ let amountUi;
54
+ if (amountStr.toLowerCase() === "all") {
55
+ const balance = await getUsdcBalance(rpc, wallet);
56
+ if (balance.raw === 0n) return { text: "Wallet has no USDC to send." };
57
+ amountRaw = balance.raw;
58
+ amountUi = balance.ui;
59
+ } else {
60
+ const amount = Number.parseFloat(amountStr);
61
+ if (Number.isNaN(amount) || amount <= 0) return { text: `Invalid amount: ${amountStr}` };
62
+ amountRaw = BigInt(Math.round(amount * 1e6));
63
+ amountUi = amount.toString();
64
+ }
65
+ const sig = await transferUsdc(signer, rpc, destination, amountRaw);
66
+ appendHistory(histPath, {
67
+ t: Date.now(),
68
+ ok: true,
69
+ kind: "transfer",
70
+ net: SOL_MAINNET,
71
+ from: wallet,
72
+ to: destination,
73
+ tx: sig,
74
+ amount: Number.parseFloat(amountUi),
75
+ token: "USDC",
76
+ label: `${destination.slice(0, 4)}...${destination.slice(-4)}`
77
+ });
78
+ return { text: `Sent ${amountUi} USDC to \`${destination}\`\n[View transaction](https://solscan.io/tx/${sig})` };
79
+ } catch (err) {
80
+ appendHistory(histPath, {
81
+ t: Date.now(),
82
+ ok: false,
83
+ kind: "transfer",
84
+ net: SOL_MAINNET,
85
+ from: wallet,
86
+ to: destination,
87
+ token: "USDC",
88
+ error: String(err).substring(0, 200)
89
+ });
90
+ const msg = String(err);
91
+ const detail = err.cause?.message || msg;
92
+ if (detail.includes("insufficient") || detail.includes("lamports")) return { text: "Send failed - insufficient SOL for fees\nBalance too low for transaction fees. Fund wallet with SOL." };
93
+ return { text: `Send failed: ${detail}` };
94
+ }
95
+ }
96
+ function handleHistory(histPath, page) {
97
+ const records = readHistory(histPath);
98
+ const totalTxs = records.length;
99
+ const start = (page - 1) * HISTORY_PAGE_SIZE;
100
+ const fromEnd = totalTxs - start;
101
+ const pageRecords = records.slice(Math.max(0, fromEnd - HISTORY_PAGE_SIZE), fromEnd).reverse();
102
+ if (pageRecords.length === 0) return { text: page === 1 ? "No transactions yet." : "No more transactions." };
103
+ const lines = [`**History** (${start + 1}-${start + pageRecords.length})`, ""];
104
+ for (const r of pageRecords) lines.push(formatTxLine(r));
105
+ const nav = [];
106
+ if (page > 1) nav.push(`Newer: \`/x_wallet history${page === 2 ? "" : ` ${page - 1}`}\``);
107
+ if (start + HISTORY_PAGE_SIZE < totalTxs) nav.push(`Older: \`/x_wallet history ${page + 1}\``);
108
+ if (nav.length > 0) lines.push("", nav.join(" · "));
109
+ return { text: lines.join("\n") };
110
+ }
111
+ function createWalletCommand(ctx) {
112
+ return {
113
+ name: "x_wallet",
114
+ description: "Wallet status, balances, payment readiness, and transaction history",
115
+ acceptsArgs: true,
116
+ requireAuth: true,
117
+ handler: async (cmdCtx) => {
118
+ await ctx.ensureReady();
119
+ const solanaWallet = ctx.getSolanaWalletAddress();
120
+ const evmWallet = ctx.getEvmWalletAddress();
121
+ if (!solanaWallet && !evmWallet) return { text: "Wallet not configured yet.\nRun `x402-proxy setup` or set `X402_PROXY_WALLET_MNEMONIC` on the gateway host." };
122
+ const parts = (cmdCtx.args?.trim() ?? "").split(/\s+/).filter(Boolean);
123
+ if (parts[0]?.toLowerCase() === "history") {
124
+ const pageArg = parts[1];
125
+ const page = pageArg ? Math.max(1, Number.parseInt(pageArg, 10) || 1) : 1;
126
+ return handleHistory(ctx.historyPath, page);
127
+ }
128
+ if (parts[0]?.toLowerCase() === "send") return { text: "Use `/x_send <amount|all> <address>` for transfers." };
129
+ try {
130
+ const snap = await getWalletSnapshot(ctx.rpcUrl, solanaWallet, evmWallet, ctx.historyPath);
131
+ const lines = [`x402-proxy v0.10.9`];
132
+ const defaultModel = ctx.allModels[0];
133
+ if (defaultModel) lines.push("", `**Model** - ${defaultModel.name} (${defaultModel.provider})`);
134
+ lines.push("", `**Protocol** - ${ctx.getDefaultRequestProtocol()}`);
135
+ lines.push(`MPP session budget: ${ctx.getDefaultMppSessionBudget()} USDC`);
136
+ lines.push(`MPP ready: ${evmWallet ? "yes" : "no"}`);
137
+ lines.push(`x402 ready: ${solanaWallet ? "yes" : "no"}`);
138
+ if (evmWallet) {
139
+ lines.push("", "**EVM / Tempo**");
140
+ lines.push(`\`${evmWallet}\``);
141
+ lines.push(` Base: ${snap.balances.evm?.usdc ?? "?"} USDC`);
142
+ lines.push(` Tempo: ${snap.balances.tempo?.usdc ?? "?"} USDC`);
143
+ }
144
+ if (solanaWallet) {
145
+ const solscanUrl = `https://solscan.io/account/${solanaWallet}`;
146
+ lines.push("", `**[Solana Wallet](${solscanUrl})**`, `\`${solanaWallet}\``);
147
+ lines.push(` ${snap.sol} SOL`, ` ${snap.ui} USDC`);
148
+ if (snap.spend.today > 0) lines.push(` -${snap.spend.today.toFixed(2)} USDC today`);
149
+ }
150
+ if (snap.tokens.length > 0) {
151
+ const displayTokens = snap.tokens.slice(0, 10);
152
+ const symbols = await resolveTokenSymbols(displayTokens.map((t) => t.mint));
153
+ lines.push("", "**Solana Tokens**");
154
+ for (const t of displayTokens) {
155
+ const label = symbols.get(t.mint) ?? `${t.mint.slice(0, 4)}...${t.mint.slice(-4)}`;
156
+ const amt = Number.parseFloat(t.amount).toLocaleString("en-US", { maximumFractionDigits: 0 });
157
+ lines.push(` ${amt} ${label}`);
158
+ }
159
+ if (snap.tokens.length > 10) lines.push(` ...and ${snap.tokens.length - 10} more`);
160
+ }
161
+ if (snap.tokens.length <= INLINE_HISTORY_TOKEN_THRESHOLD) {
162
+ const recentRecords = snap.records.slice(-STATUS_HISTORY_COUNT).reverse();
163
+ if (recentRecords.length > 0) {
164
+ lines.push("", "**Recent**");
165
+ for (const r of recentRecords) lines.push(formatTxLine(r));
166
+ }
167
+ }
168
+ if (!evmWallet) lines.push("", "MPP setup: add an EVM wallet via `x402-proxy setup` or `X402_PROXY_WALLET_EVM_KEY`.");
169
+ if (!solanaWallet) lines.push("", "x402 setup: add a Solana wallet or mnemonic if you need Solana x402.");
170
+ lines.push("", "History: `/x_wallet history`", "Send: `/x_send <amount|all> <address>`");
171
+ if (ctx.dashboardUrl) lines.push(`[Dashboard](${ctx.dashboardUrl})`);
172
+ return { text: lines.join("\n") };
173
+ } catch (err) {
174
+ return { text: `Failed to check wallet status: ${String(err)}` };
175
+ }
176
+ }
177
+ };
178
+ }
179
+ function createSendCommand(ctx) {
180
+ return {
181
+ name: "x_send",
182
+ description: "Send USDC from the plugin wallet with an explicit confirmation step",
183
+ acceptsArgs: true,
184
+ requireAuth: true,
185
+ handler: async (cmdCtx) => {
186
+ await ctx.ensureReady();
187
+ const wallet = ctx.getSolanaWalletAddress();
188
+ if (!wallet) return { text: "No Solana wallet configured. `/x_send` only works with the Solana USDC wallet." };
189
+ const key = senderKey(cmdCtx);
190
+ const now = Date.now();
191
+ const pending = pendingSends.get(key);
192
+ if (pending && now - pending.createdAt > SEND_CONFIRM_TTL_MS) pendingSends.delete(key);
193
+ const parts = (cmdCtx.args?.trim() ?? "").split(/\s+/).filter(Boolean);
194
+ if (parts.length === 0) return { text: "Usage: `/x_send <amount|all> <address>`\nConfirm with `/x_send confirm`\nCancel with `/x_send cancel`" };
195
+ if (parts[0]?.toLowerCase() === "confirm") {
196
+ const next = pendingSends.get(key);
197
+ if (!next) return { text: "No pending transfer to confirm." };
198
+ pendingSends.delete(key);
199
+ return executeSend(next.amount, next.destination, wallet, ctx.getSigner(), ctx.rpcUrl, ctx.historyPath);
200
+ }
201
+ if (parts[0]?.toLowerCase() === "cancel") {
202
+ pendingSends.delete(key);
203
+ return { text: "Pending transfer cleared." };
204
+ }
205
+ if (parts.length !== 2) return { text: "Usage: `/x_send <amount|all> <address>`\nExample: `/x_send 0.5 7xKXtg...`" };
206
+ const [amount, destination] = parts;
207
+ if (destination.length < 32 || destination.length > 44) return { text: `Invalid Solana address: ${destination}` };
208
+ pendingSends.set(key, {
209
+ amount,
210
+ destination,
211
+ createdAt: now
212
+ });
213
+ return { text: `Pending transfer: send ${amount} USDC to \`${destination}\`\nConfirm within 5 minutes with \`/x_send confirm\`\nCancel with \`/x_send cancel\`` };
214
+ }
215
+ };
216
+ }
217
+ //#endregion
218
+ export { createSendCommand, createWalletCommand };
@@ -0,0 +1,129 @@
1
+ //#region packages/x402-proxy/src/openclaw/defaults.ts
2
+ const DEFAULT_SURF_PROVIDER_ID = "surf";
3
+ const DEFAULT_SURF_BASE_URL = "/x402-proxy/v1";
4
+ const DEFAULT_SURF_UPSTREAM_URL = "https://surf.cascade.fyi/api/v1/inference";
5
+ const DEFAULT_SURF_MODELS = [
6
+ {
7
+ id: "anthropic/claude-opus-4.6",
8
+ name: "Claude Opus 4.6",
9
+ maxTokens: 2e5,
10
+ reasoning: true,
11
+ input: ["text", "image"],
12
+ cost: {
13
+ input: .015,
14
+ output: .075,
15
+ cacheRead: .0015,
16
+ cacheWrite: .01875
17
+ },
18
+ contextWindow: 2e5
19
+ },
20
+ {
21
+ id: "anthropic/claude-sonnet-4.6",
22
+ name: "Claude Sonnet 4.6",
23
+ maxTokens: 2e5,
24
+ reasoning: true,
25
+ input: ["text", "image"],
26
+ cost: {
27
+ input: .003,
28
+ output: .015,
29
+ cacheRead: 3e-4,
30
+ cacheWrite: .00375
31
+ },
32
+ contextWindow: 2e5
33
+ },
34
+ {
35
+ id: "x-ai/grok-4.20-beta",
36
+ name: "Grok 4.20 Beta",
37
+ maxTokens: 131072,
38
+ reasoning: true,
39
+ input: ["text"],
40
+ cost: {
41
+ input: .003,
42
+ output: .015,
43
+ cacheRead: 0,
44
+ cacheWrite: 0
45
+ },
46
+ contextWindow: 131072
47
+ },
48
+ {
49
+ id: "minimax/minimax-m2.5",
50
+ name: "MiniMax M2.5",
51
+ maxTokens: 1e6,
52
+ reasoning: false,
53
+ input: ["text"],
54
+ cost: {
55
+ input: .001,
56
+ output: .005,
57
+ cacheRead: 0,
58
+ cacheWrite: 0
59
+ },
60
+ contextWindow: 1e6
61
+ },
62
+ {
63
+ id: "moonshotai/kimi-k2.5",
64
+ name: "Kimi K2.5",
65
+ maxTokens: 131072,
66
+ reasoning: true,
67
+ input: ["text"],
68
+ cost: {
69
+ input: .002,
70
+ output: .008,
71
+ cacheRead: 0,
72
+ cacheWrite: 0
73
+ },
74
+ contextWindow: 131072
75
+ },
76
+ {
77
+ id: "z-ai/glm-5",
78
+ name: "GLM-5",
79
+ maxTokens: 128e3,
80
+ reasoning: false,
81
+ input: ["text"],
82
+ cost: {
83
+ input: .001,
84
+ output: .005,
85
+ cacheRead: 0,
86
+ cacheWrite: 0
87
+ },
88
+ contextWindow: 128e3
89
+ }
90
+ ];
91
+ function resolveProviders(config) {
92
+ const defaultProtocol = resolveProtocol(config.protocol);
93
+ const defaultMppSessionBudget = resolveMppSessionBudget(config.mppSessionBudget);
94
+ const raw = config.providers ?? {};
95
+ const entries = Object.entries(raw).length > 0 ? Object.entries(raw).map(([id, provider]) => ({
96
+ id,
97
+ baseUrl: provider.baseUrl || "/x402-proxy/v1",
98
+ upstreamUrl: provider.upstreamUrl || "https://surf.cascade.fyi/api/v1/inference",
99
+ protocol: resolveProtocol(provider.protocol, defaultProtocol),
100
+ mppSessionBudget: resolveMppSessionBudget(provider.mppSessionBudget, defaultMppSessionBudget),
101
+ models: provider.models && provider.models.length > 0 ? provider.models : DEFAULT_SURF_MODELS
102
+ })) : [{
103
+ id: DEFAULT_SURF_PROVIDER_ID,
104
+ baseUrl: DEFAULT_SURF_BASE_URL,
105
+ upstreamUrl: DEFAULT_SURF_UPSTREAM_URL,
106
+ protocol: defaultProtocol,
107
+ mppSessionBudget: defaultMppSessionBudget,
108
+ models: DEFAULT_SURF_MODELS
109
+ }];
110
+ return {
111
+ providers: entries,
112
+ models: entries.flatMap((provider) => provider.models.map((model) => ({
113
+ ...model,
114
+ provider: provider.id
115
+ })))
116
+ };
117
+ }
118
+ function routePrefixForBaseUrl(baseUrl) {
119
+ const segments = baseUrl.split("/").filter(Boolean);
120
+ return segments.length > 0 ? `/${segments[0]}` : "/";
121
+ }
122
+ function resolveProtocol(value, fallback = "mpp") {
123
+ return value === "x402" || value === "mpp" || value === "auto" ? value : fallback;
124
+ }
125
+ function resolveMppSessionBudget(value, fallback = "0.5") {
126
+ return typeof value === "string" && value.trim().length > 0 ? value : fallback;
127
+ }
128
+ //#endregion
129
+ export { resolveProviders, routePrefixForBaseUrl };