@ckcloudai.com/clawrouter 0.0.5 → 0.0.6
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/index.js +550 -578
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12,6 +12,254 @@ var PROXY_PORT = (() => {
|
|
|
12
12
|
})();
|
|
13
13
|
var PLUGIN_NAME = "ckcloud";
|
|
14
14
|
|
|
15
|
+
// src/balance.ts
|
|
16
|
+
import { createPublicClient, http, erc20Abi } from "viem";
|
|
17
|
+
import { base } from "viem/chains";
|
|
18
|
+
|
|
19
|
+
// src/errors.ts
|
|
20
|
+
var RpcError = class extends Error {
|
|
21
|
+
code = "RPC_ERROR";
|
|
22
|
+
originalError;
|
|
23
|
+
constructor(message, originalError) {
|
|
24
|
+
super(`RPC error: ${message}. Check network connectivity.`);
|
|
25
|
+
this.name = "RpcError";
|
|
26
|
+
this.originalError = originalError;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// src/balance.ts
|
|
31
|
+
var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
32
|
+
var CACHE_TTL_MS = 3e4;
|
|
33
|
+
var BALANCE_THRESHOLDS = {
|
|
34
|
+
/** Low balance warning threshold: $1.00 */
|
|
35
|
+
LOW_BALANCE_MICROS: 1000000n,
|
|
36
|
+
/** Effectively zero threshold: $0.0001 (covers dust/rounding) */
|
|
37
|
+
ZERO_THRESHOLD: 100n
|
|
38
|
+
};
|
|
39
|
+
var BalanceMonitor = class {
|
|
40
|
+
client;
|
|
41
|
+
walletAddress;
|
|
42
|
+
/** Cached balance (null = not yet fetched) */
|
|
43
|
+
cachedBalance = null;
|
|
44
|
+
/** Timestamp when cache was last updated */
|
|
45
|
+
cachedAt = 0;
|
|
46
|
+
constructor(walletAddress) {
|
|
47
|
+
this.walletAddress = walletAddress;
|
|
48
|
+
this.client = createPublicClient({
|
|
49
|
+
chain: base,
|
|
50
|
+
transport: http(void 0, {
|
|
51
|
+
timeout: 1e4
|
|
52
|
+
// 10 second timeout to prevent hanging on slow RPC
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check current USDC balance.
|
|
58
|
+
* Uses cache if valid, otherwise fetches from RPC.
|
|
59
|
+
*/
|
|
60
|
+
async checkBalance() {
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
if (this.cachedBalance !== null && this.cachedBalance > 0n && now - this.cachedAt < CACHE_TTL_MS) {
|
|
63
|
+
return this.buildInfo(this.cachedBalance);
|
|
64
|
+
}
|
|
65
|
+
const balance = await this.fetchBalance();
|
|
66
|
+
if (balance > 0n) {
|
|
67
|
+
this.cachedBalance = balance;
|
|
68
|
+
this.cachedAt = now;
|
|
69
|
+
}
|
|
70
|
+
return this.buildInfo(balance);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Check if balance is sufficient for an estimated cost.
|
|
74
|
+
*
|
|
75
|
+
* @param estimatedCostMicros - Estimated cost in USDC smallest unit (6 decimals)
|
|
76
|
+
*/
|
|
77
|
+
async checkSufficient(estimatedCostMicros) {
|
|
78
|
+
const info = await this.checkBalance();
|
|
79
|
+
if (info.balance >= estimatedCostMicros) {
|
|
80
|
+
return { sufficient: true, info };
|
|
81
|
+
}
|
|
82
|
+
const shortfall = estimatedCostMicros - info.balance;
|
|
83
|
+
return {
|
|
84
|
+
sufficient: false,
|
|
85
|
+
info,
|
|
86
|
+
shortfall: this.formatUSDC(shortfall)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Optimistically deduct estimated cost from cached balance.
|
|
91
|
+
* Call this after a successful payment to keep cache accurate.
|
|
92
|
+
*
|
|
93
|
+
* @param amountMicros - Amount to deduct in USDC smallest unit
|
|
94
|
+
*/
|
|
95
|
+
deductEstimated(amountMicros) {
|
|
96
|
+
if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {
|
|
97
|
+
this.cachedBalance -= amountMicros;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Invalidate cache, forcing next checkBalance() to fetch from RPC.
|
|
102
|
+
* Call this after a payment failure to get accurate balance.
|
|
103
|
+
*/
|
|
104
|
+
invalidate() {
|
|
105
|
+
this.cachedBalance = null;
|
|
106
|
+
this.cachedAt = 0;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Force refresh balance from RPC (ignores cache).
|
|
110
|
+
*/
|
|
111
|
+
async refresh() {
|
|
112
|
+
this.invalidate();
|
|
113
|
+
return this.checkBalance();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Format USDC amount (in micros) as "$X.XX".
|
|
117
|
+
*/
|
|
118
|
+
formatUSDC(amountMicros) {
|
|
119
|
+
const dollars = Number(amountMicros) / 1e6;
|
|
120
|
+
return `$${dollars.toFixed(2)}`;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get the wallet address being monitored.
|
|
124
|
+
*/
|
|
125
|
+
getWalletAddress() {
|
|
126
|
+
return this.walletAddress;
|
|
127
|
+
}
|
|
128
|
+
/** Fetch balance from RPC */
|
|
129
|
+
async fetchBalance() {
|
|
130
|
+
try {
|
|
131
|
+
const balance = await this.client.readContract({
|
|
132
|
+
address: USDC_BASE,
|
|
133
|
+
abi: erc20Abi,
|
|
134
|
+
functionName: "balanceOf",
|
|
135
|
+
args: [this.walletAddress]
|
|
136
|
+
});
|
|
137
|
+
return balance;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
throw new RpcError(error instanceof Error ? error.message : "Unknown error", error);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Build BalanceInfo from raw balance */
|
|
143
|
+
buildInfo(balance) {
|
|
144
|
+
return {
|
|
145
|
+
balance,
|
|
146
|
+
balanceUSD: this.formatUSDC(balance),
|
|
147
|
+
isLow: balance < BALANCE_THRESHOLDS.LOW_BALANCE_MICROS,
|
|
148
|
+
isEmpty: balance < BALANCE_THRESHOLDS.ZERO_THRESHOLD,
|
|
149
|
+
walletAddress: this.walletAddress
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// src/solana-balance.ts
|
|
155
|
+
import { address as solAddress, createSolanaRpc } from "@solana/kit";
|
|
156
|
+
var SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
157
|
+
var SOLANA_DEFAULT_RPC = "https://api.mainnet-beta.solana.com";
|
|
158
|
+
var BALANCE_TIMEOUT_MS = 1e4;
|
|
159
|
+
var CACHE_TTL_MS2 = 3e4;
|
|
160
|
+
var SolanaBalanceMonitor = class {
|
|
161
|
+
rpc;
|
|
162
|
+
walletAddress;
|
|
163
|
+
cachedBalance = null;
|
|
164
|
+
cachedAt = 0;
|
|
165
|
+
constructor(walletAddress, rpcUrl) {
|
|
166
|
+
this.walletAddress = walletAddress;
|
|
167
|
+
const url = rpcUrl || process["env"].CLAWROUTER_SOLANA_RPC_URL || SOLANA_DEFAULT_RPC;
|
|
168
|
+
this.rpc = createSolanaRpc(url);
|
|
169
|
+
}
|
|
170
|
+
async checkBalance() {
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
if (this.cachedBalance !== null && this.cachedBalance > 0n && now - this.cachedAt < CACHE_TTL_MS2) {
|
|
173
|
+
return this.buildInfo(this.cachedBalance);
|
|
174
|
+
}
|
|
175
|
+
const balance = await this.fetchBalance();
|
|
176
|
+
if (balance > 0n) {
|
|
177
|
+
this.cachedBalance = balance;
|
|
178
|
+
this.cachedAt = now;
|
|
179
|
+
}
|
|
180
|
+
return this.buildInfo(balance);
|
|
181
|
+
}
|
|
182
|
+
deductEstimated(amountMicros) {
|
|
183
|
+
if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {
|
|
184
|
+
this.cachedBalance -= amountMicros;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
invalidate() {
|
|
188
|
+
this.cachedBalance = null;
|
|
189
|
+
this.cachedAt = 0;
|
|
190
|
+
}
|
|
191
|
+
async refresh() {
|
|
192
|
+
this.invalidate();
|
|
193
|
+
return this.checkBalance();
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Check if balance is sufficient for an estimated cost.
|
|
197
|
+
*/
|
|
198
|
+
async checkSufficient(estimatedCostMicros) {
|
|
199
|
+
const info = await this.checkBalance();
|
|
200
|
+
if (info.balance >= estimatedCostMicros) {
|
|
201
|
+
return { sufficient: true, info };
|
|
202
|
+
}
|
|
203
|
+
const shortfall = estimatedCostMicros - info.balance;
|
|
204
|
+
return {
|
|
205
|
+
sufficient: false,
|
|
206
|
+
info,
|
|
207
|
+
shortfall: this.formatUSDC(shortfall)
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Format USDC amount (in micros) as "$X.XX".
|
|
212
|
+
*/
|
|
213
|
+
formatUSDC(amountMicros) {
|
|
214
|
+
const dollars = Number(amountMicros) / 1e6;
|
|
215
|
+
return `$${dollars.toFixed(2)}`;
|
|
216
|
+
}
|
|
217
|
+
getWalletAddress() {
|
|
218
|
+
return this.walletAddress;
|
|
219
|
+
}
|
|
220
|
+
async fetchBalance() {
|
|
221
|
+
const owner = solAddress(this.walletAddress);
|
|
222
|
+
const mint = solAddress(SOLANA_USDC_MINT);
|
|
223
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
224
|
+
const result = await this.fetchBalanceOnce(owner, mint);
|
|
225
|
+
if (result > 0n || attempt === 1) return result;
|
|
226
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
227
|
+
}
|
|
228
|
+
return 0n;
|
|
229
|
+
}
|
|
230
|
+
async fetchBalanceOnce(owner, mint) {
|
|
231
|
+
const controller = new AbortController();
|
|
232
|
+
const timer = setTimeout(() => controller.abort(), BALANCE_TIMEOUT_MS);
|
|
233
|
+
try {
|
|
234
|
+
const response = await this.rpc.getTokenAccountsByOwner(owner, { mint }, { encoding: "jsonParsed" }).send({ abortSignal: controller.signal });
|
|
235
|
+
if (response.value.length === 0) return 0n;
|
|
236
|
+
let total = 0n;
|
|
237
|
+
for (const account of response.value) {
|
|
238
|
+
const parsed = account.account.data;
|
|
239
|
+
total += BigInt(parsed.parsed.info.tokenAmount.amount);
|
|
240
|
+
}
|
|
241
|
+
return total;
|
|
242
|
+
} catch (err) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
`Failed to fetch Solana USDC balance: ${err instanceof Error ? err.message : String(err)}`,
|
|
245
|
+
{ cause: err }
|
|
246
|
+
);
|
|
247
|
+
} finally {
|
|
248
|
+
clearTimeout(timer);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
buildInfo(balance) {
|
|
252
|
+
const dollars = Number(balance) / 1e6;
|
|
253
|
+
return {
|
|
254
|
+
balance,
|
|
255
|
+
balanceUSD: `$${dollars.toFixed(2)}`,
|
|
256
|
+
isLow: balance < 1000000n,
|
|
257
|
+
isEmpty: balance < 100n,
|
|
258
|
+
walletAddress: this.walletAddress
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
15
263
|
// src/index.ts
|
|
16
264
|
import { join as join6 } from "path";
|
|
17
265
|
import { homedir as homedir5 } from "os";
|
|
@@ -128,7 +376,7 @@ async function loadBaseModels() {
|
|
|
128
376
|
}
|
|
129
377
|
var BASE_MODELS = [...DEFAULT_BASE_MODELS];
|
|
130
378
|
function resolveModelAlias(model) {
|
|
131
|
-
const normalized = model.trim()
|
|
379
|
+
const normalized = model.trim();
|
|
132
380
|
const resolved = MODEL_ALIASES[normalized];
|
|
133
381
|
if (resolved) return resolved;
|
|
134
382
|
if (normalized.startsWith("ckcloud/")) {
|
|
@@ -163,68 +411,7 @@ function toOpenClawModel(m) {
|
|
|
163
411
|
maxTokens: m.maxOutput
|
|
164
412
|
};
|
|
165
413
|
}
|
|
166
|
-
var MODEL_ALIASES = {
|
|
167
|
-
// Claude - use newest versions (4.6)
|
|
168
|
-
claude: "anthropic/claude-sonnet-4.6",
|
|
169
|
-
sonnet: "anthropic/claude-sonnet-4.6",
|
|
170
|
-
"sonnet-4": "anthropic/claude-sonnet-4.6",
|
|
171
|
-
"sonnet-4.6": "anthropic/claude-sonnet-4.6",
|
|
172
|
-
"sonnet-4-6": "anthropic/claude-sonnet-4.6",
|
|
173
|
-
opus: "anthropic/claude-opus-4.6",
|
|
174
|
-
"opus-4": "anthropic/claude-opus-4.6",
|
|
175
|
-
"opus-4.6": "anthropic/claude-opus-4.6",
|
|
176
|
-
"opus-4-6": "anthropic/claude-opus-4.6",
|
|
177
|
-
haiku: "anthropic/claude-haiku-4.5",
|
|
178
|
-
// Claude - provider/shortname patterns (common in agent frameworks)
|
|
179
|
-
"anthropic/sonnet": "anthropic/claude-sonnet-4.6",
|
|
180
|
-
"anthropic/opus": "anthropic/claude-opus-4.6",
|
|
181
|
-
"anthropic/haiku": "anthropic/claude-haiku-4.5",
|
|
182
|
-
"anthropic/claude": "anthropic/claude-sonnet-4.6",
|
|
183
|
-
// Backward compatibility - map all variants to 4.6
|
|
184
|
-
"anthropic/claude-sonnet-4": "anthropic/claude-sonnet-4.6",
|
|
185
|
-
"anthropic/claude-sonnet-4-6": "anthropic/claude-sonnet-4.6",
|
|
186
|
-
"anthropic/claude-opus-4": "anthropic/claude-opus-4.6",
|
|
187
|
-
"anthropic/claude-opus-4-6": "anthropic/claude-opus-4.6",
|
|
188
|
-
"anthropic/claude-opus-4.5": "anthropic/claude-opus-4.6",
|
|
189
|
-
"anthropic/claude-haiku-4": "anthropic/claude-haiku-4.5",
|
|
190
|
-
"anthropic/claude-haiku-4-5": "anthropic/claude-haiku-4.5",
|
|
191
|
-
// OpenAI
|
|
192
|
-
gpt: "openai/gpt-4o",
|
|
193
|
-
gpt4: "openai/gpt-4o",
|
|
194
|
-
gpt5: "openai/gpt-5.4",
|
|
195
|
-
"gpt-5.4": "openai/gpt-5.4",
|
|
196
|
-
"gpt-5.4-pro": "openai/gpt-5.4-pro",
|
|
197
|
-
codex: "openai/gpt-5.2-codex",
|
|
198
|
-
mini: "openai/gpt-4o-mini",
|
|
199
|
-
o1: "openai/o1",
|
|
200
|
-
o3: "openai/o3",
|
|
201
|
-
// DeepSeek
|
|
202
|
-
deepseek: "deepseek/deepseek-chat",
|
|
203
|
-
reasoner: "deepseek/deepseek-reasoner",
|
|
204
|
-
// Kimi / Moonshot
|
|
205
|
-
kimi: "moonshot/kimi-k2.5",
|
|
206
|
-
moonshot: "moonshot/kimi-k2.5",
|
|
207
|
-
"kimi-k2.5": "moonshot/kimi-k2.5",
|
|
208
|
-
// Google
|
|
209
|
-
gemini: "google/gemini-2.5-pro",
|
|
210
|
-
flash: "google/gemini-2.5-flash",
|
|
211
|
-
"gemini-3.1-pro-preview": "google/gemini-3.1-pro",
|
|
212
|
-
"google/gemini-3.1-pro-preview": "google/gemini-3.1-pro",
|
|
213
|
-
// xAI
|
|
214
|
-
grok: "xai/grok-3",
|
|
215
|
-
"grok-fast": "xai/grok-4-fast-reasoning",
|
|
216
|
-
"grok-code": "xai/grok-code-fast-1",
|
|
217
|
-
// NVIDIA
|
|
218
|
-
nvidia: "nvidia/gpt-oss-120b",
|
|
219
|
-
"gpt-120b": "nvidia/gpt-oss-120b",
|
|
220
|
-
// MiniMax
|
|
221
|
-
minimax: "minimax/minimax-m2.5",
|
|
222
|
-
// Routing profile aliases (common variations)
|
|
223
|
-
"auto-router": "auto",
|
|
224
|
-
router: "auto"
|
|
225
|
-
// Note: auto, free, eco, premium are virtual routing profiles registered in BASE_MODELS
|
|
226
|
-
// They don't need aliases since they're already top-level model IDs
|
|
227
|
-
};
|
|
414
|
+
var MODEL_ALIASES = {};
|
|
228
415
|
function buildAliasModels(baseModels) {
|
|
229
416
|
return Object.entries(MODEL_ALIASES).map(([alias, targetId]) => {
|
|
230
417
|
const target = baseModels.find((m) => m.id === targetId);
|
|
@@ -295,348 +482,100 @@ var ckcloudProvider = {
|
|
|
295
482
|
docsPath: "",
|
|
296
483
|
//TODO
|
|
297
484
|
aliases: ["ckey"],
|
|
298
|
-
envVars: ["CKCLOUD_API_KEY"],
|
|
299
|
-
// Model definitions — dynamically set to proxy URL
|
|
300
|
-
get models() {
|
|
301
|
-
if (!activeProxy) {
|
|
302
|
-
return buildProviderModels(BASE_API_URL);
|
|
303
|
-
}
|
|
304
|
-
return buildProviderModels(activeProxy?.baseUrl);
|
|
305
|
-
},
|
|
306
|
-
// No auth required — the x402 proxy handles wallet-based payments internally.
|
|
307
|
-
// The proxy auto-generates a wallet on first run and stores it at
|
|
308
|
-
// ~/.openclaw/ckcloud/wallet.key. Users just fund that wallet with USDC.
|
|
309
|
-
auth: []
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
// src/proxy.ts
|
|
313
|
-
import { createServer } from "http";
|
|
314
|
-
import { finished } from "stream";
|
|
315
|
-
import { homedir as homedir4 } from "os";
|
|
316
|
-
import { join as join5 } from "path";
|
|
317
|
-
import { mkdir as mkdir3, writeFile as writeFile2, readFile, stat as fsStat } from "fs/promises";
|
|
318
|
-
import { createPublicClient as createPublicClient2, http as http2 } from "viem";
|
|
319
|
-
import { base as base2 } from "viem/chains";
|
|
320
|
-
import { toClientEvmSigner } from "@x402/evm";
|
|
321
|
-
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
322
|
-
import { x402Client } from "@x402/fetch";
|
|
323
|
-
|
|
324
|
-
// src/payment-preauth.ts
|
|
325
|
-
import { x402HTTPClient } from "@x402/fetch";
|
|
326
|
-
var DEFAULT_TTL_MS = 36e5;
|
|
327
|
-
if (typeof process !== "undefined") {
|
|
328
|
-
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
329
|
-
}
|
|
330
|
-
function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, options) {
|
|
331
|
-
const httpClient = new x402HTTPClient(client);
|
|
332
|
-
const cache = /* @__PURE__ */ new Map();
|
|
333
|
-
return async (input, init) => {
|
|
334
|
-
const request = new Request(input, init);
|
|
335
|
-
const urlPath = new URL(request.url).pathname;
|
|
336
|
-
const cached = !options?.skipPreAuth ? cache.get(urlPath) : void 0;
|
|
337
|
-
if (cached && Date.now() - cached.cachedAt < ttlMs) {
|
|
338
|
-
try {
|
|
339
|
-
const payload2 = await client.createPaymentPayload(cached.paymentRequired);
|
|
340
|
-
const headers = httpClient.encodePaymentSignatureHeader(payload2);
|
|
341
|
-
const preAuthRequest = request.clone();
|
|
342
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
343
|
-
preAuthRequest.headers.set(key, value);
|
|
344
|
-
}
|
|
345
|
-
const response2 = await baseFetch(preAuthRequest);
|
|
346
|
-
if (response2.status !== 402) {
|
|
347
|
-
return response2;
|
|
348
|
-
}
|
|
349
|
-
cache.delete(urlPath);
|
|
350
|
-
} catch {
|
|
351
|
-
cache.delete(urlPath);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
const clonedRequest = request.clone();
|
|
355
|
-
const response = await baseFetch(request);
|
|
356
|
-
if (response.status !== 402) {
|
|
357
|
-
return response;
|
|
358
|
-
}
|
|
359
|
-
let paymentRequired;
|
|
360
|
-
try {
|
|
361
|
-
const getHeader = (name) => response.headers.get(name);
|
|
362
|
-
let body;
|
|
363
|
-
try {
|
|
364
|
-
const responseText = await Promise.race([
|
|
365
|
-
response.text(),
|
|
366
|
-
new Promise(
|
|
367
|
-
(_, reject) => setTimeout(() => reject(new Error("Body read timeout")), 3e4)
|
|
368
|
-
)
|
|
369
|
-
]);
|
|
370
|
-
if (responseText) body = JSON.parse(responseText);
|
|
371
|
-
} catch {
|
|
372
|
-
}
|
|
373
|
-
paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body);
|
|
374
|
-
cache.set(urlPath, { paymentRequired, cachedAt: Date.now() });
|
|
375
|
-
} catch (error) {
|
|
376
|
-
throw new Error(
|
|
377
|
-
`Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
378
|
-
{ cause: error }
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
const payload = await client.createPaymentPayload(paymentRequired);
|
|
382
|
-
const paymentHeaders = httpClient.encodePaymentSignatureHeader(payload);
|
|
383
|
-
for (const [key, value] of Object.entries(paymentHeaders)) {
|
|
384
|
-
clonedRequest.headers.set(key, value);
|
|
385
|
-
}
|
|
386
|
-
return baseFetch(clonedRequest);
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// src/proxy.ts
|
|
391
|
-
import { registerExactEvmScheme } from "@x402/evm/exact/client";
|
|
392
|
-
|
|
393
|
-
// src/balance.ts
|
|
394
|
-
import { createPublicClient, http, erc20Abi } from "viem";
|
|
395
|
-
import { base } from "viem/chains";
|
|
396
|
-
|
|
397
|
-
// src/errors.ts
|
|
398
|
-
var RpcError = class extends Error {
|
|
399
|
-
code = "RPC_ERROR";
|
|
400
|
-
originalError;
|
|
401
|
-
constructor(message, originalError) {
|
|
402
|
-
super(`RPC error: ${message}. Check network connectivity.`);
|
|
403
|
-
this.name = "RpcError";
|
|
404
|
-
this.originalError = originalError;
|
|
405
|
-
}
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
// src/balance.ts
|
|
409
|
-
var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
410
|
-
var CACHE_TTL_MS = 3e4;
|
|
411
|
-
var BALANCE_THRESHOLDS = {
|
|
412
|
-
/** Low balance warning threshold: $1.00 */
|
|
413
|
-
LOW_BALANCE_MICROS: 1000000n,
|
|
414
|
-
/** Effectively zero threshold: $0.0001 (covers dust/rounding) */
|
|
415
|
-
ZERO_THRESHOLD: 100n
|
|
416
|
-
};
|
|
417
|
-
var BalanceMonitor = class {
|
|
418
|
-
client;
|
|
419
|
-
walletAddress;
|
|
420
|
-
/** Cached balance (null = not yet fetched) */
|
|
421
|
-
cachedBalance = null;
|
|
422
|
-
/** Timestamp when cache was last updated */
|
|
423
|
-
cachedAt = 0;
|
|
424
|
-
constructor(walletAddress) {
|
|
425
|
-
this.walletAddress = walletAddress;
|
|
426
|
-
this.client = createPublicClient({
|
|
427
|
-
chain: base,
|
|
428
|
-
transport: http(void 0, {
|
|
429
|
-
timeout: 1e4
|
|
430
|
-
// 10 second timeout to prevent hanging on slow RPC
|
|
431
|
-
})
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
/**
|
|
435
|
-
* Check current USDC balance.
|
|
436
|
-
* Uses cache if valid, otherwise fetches from RPC.
|
|
437
|
-
*/
|
|
438
|
-
async checkBalance() {
|
|
439
|
-
const now = Date.now();
|
|
440
|
-
if (this.cachedBalance !== null && this.cachedBalance > 0n && now - this.cachedAt < CACHE_TTL_MS) {
|
|
441
|
-
return this.buildInfo(this.cachedBalance);
|
|
442
|
-
}
|
|
443
|
-
const balance = await this.fetchBalance();
|
|
444
|
-
if (balance > 0n) {
|
|
445
|
-
this.cachedBalance = balance;
|
|
446
|
-
this.cachedAt = now;
|
|
447
|
-
}
|
|
448
|
-
return this.buildInfo(balance);
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* Check if balance is sufficient for an estimated cost.
|
|
452
|
-
*
|
|
453
|
-
* @param estimatedCostMicros - Estimated cost in USDC smallest unit (6 decimals)
|
|
454
|
-
*/
|
|
455
|
-
async checkSufficient(estimatedCostMicros) {
|
|
456
|
-
const info = await this.checkBalance();
|
|
457
|
-
if (info.balance >= estimatedCostMicros) {
|
|
458
|
-
return { sufficient: true, info };
|
|
459
|
-
}
|
|
460
|
-
const shortfall = estimatedCostMicros - info.balance;
|
|
461
|
-
return {
|
|
462
|
-
sufficient: false,
|
|
463
|
-
info,
|
|
464
|
-
shortfall: this.formatUSDC(shortfall)
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* Optimistically deduct estimated cost from cached balance.
|
|
469
|
-
* Call this after a successful payment to keep cache accurate.
|
|
470
|
-
*
|
|
471
|
-
* @param amountMicros - Amount to deduct in USDC smallest unit
|
|
472
|
-
*/
|
|
473
|
-
deductEstimated(amountMicros) {
|
|
474
|
-
if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {
|
|
475
|
-
this.cachedBalance -= amountMicros;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
/**
|
|
479
|
-
* Invalidate cache, forcing next checkBalance() to fetch from RPC.
|
|
480
|
-
* Call this after a payment failure to get accurate balance.
|
|
481
|
-
*/
|
|
482
|
-
invalidate() {
|
|
483
|
-
this.cachedBalance = null;
|
|
484
|
-
this.cachedAt = 0;
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Force refresh balance from RPC (ignores cache).
|
|
488
|
-
*/
|
|
489
|
-
async refresh() {
|
|
490
|
-
this.invalidate();
|
|
491
|
-
return this.checkBalance();
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* Format USDC amount (in micros) as "$X.XX".
|
|
495
|
-
*/
|
|
496
|
-
formatUSDC(amountMicros) {
|
|
497
|
-
const dollars = Number(amountMicros) / 1e6;
|
|
498
|
-
return `$${dollars.toFixed(2)}`;
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* Get the wallet address being monitored.
|
|
502
|
-
*/
|
|
503
|
-
getWalletAddress() {
|
|
504
|
-
return this.walletAddress;
|
|
505
|
-
}
|
|
506
|
-
/** Fetch balance from RPC */
|
|
507
|
-
async fetchBalance() {
|
|
508
|
-
try {
|
|
509
|
-
const balance = await this.client.readContract({
|
|
510
|
-
address: USDC_BASE,
|
|
511
|
-
abi: erc20Abi,
|
|
512
|
-
functionName: "balanceOf",
|
|
513
|
-
args: [this.walletAddress]
|
|
514
|
-
});
|
|
515
|
-
return balance;
|
|
516
|
-
} catch (error) {
|
|
517
|
-
throw new RpcError(error instanceof Error ? error.message : "Unknown error", error);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
/** Build BalanceInfo from raw balance */
|
|
521
|
-
buildInfo(balance) {
|
|
522
|
-
return {
|
|
523
|
-
balance,
|
|
524
|
-
balanceUSD: this.formatUSDC(balance),
|
|
525
|
-
isLow: balance < BALANCE_THRESHOLDS.LOW_BALANCE_MICROS,
|
|
526
|
-
isEmpty: balance < BALANCE_THRESHOLDS.ZERO_THRESHOLD,
|
|
527
|
-
walletAddress: this.walletAddress
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
};
|
|
531
|
-
|
|
532
|
-
// src/solana-balance.ts
|
|
533
|
-
import { address as solAddress, createSolanaRpc } from "@solana/kit";
|
|
534
|
-
var SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
535
|
-
var SOLANA_DEFAULT_RPC = "https://api.mainnet-beta.solana.com";
|
|
536
|
-
var BALANCE_TIMEOUT_MS = 1e4;
|
|
537
|
-
var CACHE_TTL_MS2 = 3e4;
|
|
538
|
-
var SolanaBalanceMonitor = class {
|
|
539
|
-
rpc;
|
|
540
|
-
walletAddress;
|
|
541
|
-
cachedBalance = null;
|
|
542
|
-
cachedAt = 0;
|
|
543
|
-
constructor(walletAddress, rpcUrl) {
|
|
544
|
-
this.walletAddress = walletAddress;
|
|
545
|
-
const url = rpcUrl || process["env"].CLAWROUTER_SOLANA_RPC_URL || SOLANA_DEFAULT_RPC;
|
|
546
|
-
this.rpc = createSolanaRpc(url);
|
|
547
|
-
}
|
|
548
|
-
async checkBalance() {
|
|
549
|
-
const now = Date.now();
|
|
550
|
-
if (this.cachedBalance !== null && this.cachedBalance > 0n && now - this.cachedAt < CACHE_TTL_MS2) {
|
|
551
|
-
return this.buildInfo(this.cachedBalance);
|
|
552
|
-
}
|
|
553
|
-
const balance = await this.fetchBalance();
|
|
554
|
-
if (balance > 0n) {
|
|
555
|
-
this.cachedBalance = balance;
|
|
556
|
-
this.cachedAt = now;
|
|
557
|
-
}
|
|
558
|
-
return this.buildInfo(balance);
|
|
559
|
-
}
|
|
560
|
-
deductEstimated(amountMicros) {
|
|
561
|
-
if (this.cachedBalance !== null && this.cachedBalance >= amountMicros) {
|
|
562
|
-
this.cachedBalance -= amountMicros;
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
invalidate() {
|
|
566
|
-
this.cachedBalance = null;
|
|
567
|
-
this.cachedAt = 0;
|
|
568
|
-
}
|
|
569
|
-
async refresh() {
|
|
570
|
-
this.invalidate();
|
|
571
|
-
return this.checkBalance();
|
|
572
|
-
}
|
|
573
|
-
/**
|
|
574
|
-
* Check if balance is sufficient for an estimated cost.
|
|
575
|
-
*/
|
|
576
|
-
async checkSufficient(estimatedCostMicros) {
|
|
577
|
-
const info = await this.checkBalance();
|
|
578
|
-
if (info.balance >= estimatedCostMicros) {
|
|
579
|
-
return { sufficient: true, info };
|
|
485
|
+
envVars: ["CKCLOUD_API_KEY"],
|
|
486
|
+
// Model definitions — dynamically set to proxy URL
|
|
487
|
+
get models() {
|
|
488
|
+
if (!activeProxy) {
|
|
489
|
+
return buildProviderModels(BASE_API_URL);
|
|
580
490
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
491
|
+
return buildProviderModels(activeProxy?.baseUrl);
|
|
492
|
+
},
|
|
493
|
+
// No auth required — the x402 proxy handles wallet-based payments internally.
|
|
494
|
+
// The proxy auto-generates a wallet on first run and stores it at
|
|
495
|
+
// ~/.openclaw/ckcloud/wallet.key. Users just fund that wallet with USDC.
|
|
496
|
+
auth: []
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
// src/proxy.ts
|
|
500
|
+
import { createServer } from "http";
|
|
501
|
+
import { finished } from "stream";
|
|
502
|
+
import { homedir as homedir4 } from "os";
|
|
503
|
+
import { join as join5 } from "path";
|
|
504
|
+
import { mkdir as mkdir3, writeFile as writeFile2, readFile, stat as fsStat } from "fs/promises";
|
|
505
|
+
import { createPublicClient as createPublicClient2, http as http2 } from "viem";
|
|
506
|
+
import { base as base2 } from "viem/chains";
|
|
507
|
+
import { toClientEvmSigner } from "@x402/evm";
|
|
508
|
+
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
509
|
+
import { x402Client } from "@x402/fetch";
|
|
510
|
+
|
|
511
|
+
// src/payment-preauth.ts
|
|
512
|
+
import { x402HTTPClient } from "@x402/fetch";
|
|
513
|
+
var DEFAULT_TTL_MS = 36e5;
|
|
514
|
+
if (typeof process !== "undefined") {
|
|
515
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
516
|
+
}
|
|
517
|
+
function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, options) {
|
|
518
|
+
const httpClient = new x402HTTPClient(client);
|
|
519
|
+
const cache = /* @__PURE__ */ new Map();
|
|
520
|
+
return async (input, init) => {
|
|
521
|
+
const request = new Request(input, init);
|
|
522
|
+
const urlPath = new URL(request.url).pathname;
|
|
523
|
+
const cached = !options?.skipPreAuth ? cache.get(urlPath) : void 0;
|
|
524
|
+
if (cached && Date.now() - cached.cachedAt < ttlMs) {
|
|
525
|
+
try {
|
|
526
|
+
const payload2 = await client.createPaymentPayload(cached.paymentRequired);
|
|
527
|
+
const headers = httpClient.encodePaymentSignatureHeader(payload2);
|
|
528
|
+
const preAuthRequest = request.clone();
|
|
529
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
530
|
+
preAuthRequest.headers.set(key, value);
|
|
531
|
+
}
|
|
532
|
+
const response2 = await baseFetch(preAuthRequest);
|
|
533
|
+
if (response2.status !== 402) {
|
|
534
|
+
return response2;
|
|
535
|
+
}
|
|
536
|
+
cache.delete(urlPath);
|
|
537
|
+
} catch {
|
|
538
|
+
cache.delete(urlPath);
|
|
539
|
+
}
|
|
605
540
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
541
|
+
const clonedRequest = request.clone();
|
|
542
|
+
const response = await baseFetch(request);
|
|
543
|
+
if (response.status !== 402) {
|
|
544
|
+
return response;
|
|
545
|
+
}
|
|
546
|
+
let paymentRequired;
|
|
611
547
|
try {
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
548
|
+
const getHeader = (name) => response.headers.get(name);
|
|
549
|
+
let body;
|
|
550
|
+
try {
|
|
551
|
+
const responseText = await Promise.race([
|
|
552
|
+
response.text(),
|
|
553
|
+
new Promise(
|
|
554
|
+
(_, reject) => setTimeout(() => reject(new Error("Body read timeout")), 3e4)
|
|
555
|
+
)
|
|
556
|
+
]);
|
|
557
|
+
if (responseText) body = JSON.parse(responseText);
|
|
558
|
+
} catch {
|
|
618
559
|
}
|
|
619
|
-
|
|
620
|
-
|
|
560
|
+
paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, body);
|
|
561
|
+
cache.set(urlPath, { paymentRequired, cachedAt: Date.now() });
|
|
562
|
+
} catch (error) {
|
|
621
563
|
throw new Error(
|
|
622
|
-
`Failed to
|
|
623
|
-
{ cause:
|
|
564
|
+
`Failed to parse payment requirements: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
565
|
+
{ cause: error }
|
|
624
566
|
);
|
|
625
|
-
} finally {
|
|
626
|
-
clearTimeout(timer);
|
|
627
567
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
};
|
|
568
|
+
const payload = await client.createPaymentPayload(paymentRequired);
|
|
569
|
+
const paymentHeaders = httpClient.encodePaymentSignatureHeader(payload);
|
|
570
|
+
for (const [key, value] of Object.entries(paymentHeaders)) {
|
|
571
|
+
clonedRequest.headers.set(key, value);
|
|
572
|
+
}
|
|
573
|
+
return baseFetch(clonedRequest);
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/proxy.ts
|
|
578
|
+
import { registerExactEvmScheme } from "@x402/evm/exact/client";
|
|
640
579
|
|
|
641
580
|
// src/stats.ts
|
|
642
581
|
import { readdir, unlink } from "fs/promises";
|
|
@@ -4029,8 +3968,6 @@ var IMAGE_DIR = join5(homedir4(), ".openclaw", "ckcloud", "images");
|
|
|
4029
3968
|
var AUTO_MODEL = "ckcloud/auto";
|
|
4030
3969
|
var BALANCE_CHECK_BUFFER = 1.5;
|
|
4031
3970
|
var ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
4032
|
-
"ckcloud/free",
|
|
4033
|
-
"free",
|
|
4034
3971
|
"ckcloud/eco",
|
|
4035
3972
|
"eco",
|
|
4036
3973
|
"ckcloud/auto",
|
|
@@ -4038,7 +3975,6 @@ var ROUTING_PROFILES = /* @__PURE__ */ new Set([
|
|
|
4038
3975
|
"ckcloud/premium",
|
|
4039
3976
|
"premium"
|
|
4040
3977
|
]);
|
|
4041
|
-
var FREE_MODEL = "kimi-k2";
|
|
4042
3978
|
var MAX_MESSAGES = 200;
|
|
4043
3979
|
var CONTEXT_LIMIT_KB = 5120;
|
|
4044
3980
|
var HEARTBEAT_INTERVAL_MS = 2e3;
|
|
@@ -4605,7 +4541,7 @@ function transformPaymentError(errorBody) {
|
|
|
4605
4541
|
wallet,
|
|
4606
4542
|
current_balance_usd: currentUSD,
|
|
4607
4543
|
required_usd: requiredUSD,
|
|
4608
|
-
help: `Fund wallet ${shortWallet} with USDC on Base
|
|
4544
|
+
help: `Fund wallet ${shortWallet} with USDC on Base`
|
|
4609
4545
|
}
|
|
4610
4546
|
});
|
|
4611
4547
|
}
|
|
@@ -4627,7 +4563,7 @@ function transformPaymentError(errorBody) {
|
|
|
4627
4563
|
error: {
|
|
4628
4564
|
message: "Solana payment simulation failed. Retrying with a different model.",
|
|
4629
4565
|
type: "transaction_simulation_failed",
|
|
4630
|
-
help: "This is usually temporary. If it persists, check your Solana USDC balance
|
|
4566
|
+
help: "This is usually temporary. If it persists, check your Solana USDC balance."
|
|
4631
4567
|
}
|
|
4632
4568
|
});
|
|
4633
4569
|
}
|
|
@@ -4640,7 +4576,7 @@ function transformPaymentError(errorBody) {
|
|
|
4640
4576
|
error: {
|
|
4641
4577
|
message: gasError ? "Payment failed: network congestion or gas issue. Try again." : "Payment settlement failed. Try again in a moment.",
|
|
4642
4578
|
type: "settlement_failed",
|
|
4643
|
-
help: "This is usually temporary. If it persists, try
|
|
4579
|
+
help: "This is usually temporary. If it persists, try again later."
|
|
4644
4580
|
}
|
|
4645
4581
|
});
|
|
4646
4582
|
}
|
|
@@ -5152,7 +5088,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5152
5088
|
const estimatedTokens = Math.ceil(fullText.length / 4);
|
|
5153
5089
|
const normalizedModel2 = typeof parsed.model === "string" ? parsed.model.trim().toLowerCase() : "";
|
|
5154
5090
|
const profileName = normalizedModel2.replace("ckcloud/", "");
|
|
5155
|
-
const debugProfile = ["
|
|
5091
|
+
const debugProfile = ["eco", "auto", "premium"].includes(profileName) ? profileName : "auto";
|
|
5156
5092
|
const scoring = classifyByRules(
|
|
5157
5093
|
debugPrompt,
|
|
5158
5094
|
systemPrompt,
|
|
@@ -5463,121 +5399,44 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5463
5399
|
modelId = resolvedModel;
|
|
5464
5400
|
}
|
|
5465
5401
|
if (isRoutingProfile) {
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
latencyMs: 0
|
|
5481
|
-
});
|
|
5482
|
-
} else {
|
|
5483
|
-
effectiveSessionId = getSessionId(req.headers) ?? deriveSessionId(parsedMessages);
|
|
5484
|
-
const existingSession = effectiveSessionId ? sessionStore.getSession(effectiveSessionId) : void 0;
|
|
5485
|
-
const rawPrompt = lastUserMsg?.content;
|
|
5486
|
-
const prompt = typeof rawPrompt === "string" ? rawPrompt : Array.isArray(rawPrompt) ? rawPrompt.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
|
|
5487
|
-
const systemMsg = parsedMessages.find((m) => m.role === "system");
|
|
5488
|
-
const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
|
|
5489
|
-
const tools = parsed.tools;
|
|
5490
|
-
hasTools = Array.isArray(tools) && tools.length > 0;
|
|
5491
|
-
if (hasTools && tools) {
|
|
5492
|
-
logger.info(`[ckcloud] Tools detected (${tools.length}), forcing agentic tiers`);
|
|
5493
|
-
}
|
|
5494
|
-
hasVision = parsedMessages.some((m) => {
|
|
5495
|
-
if (Array.isArray(m.content)) {
|
|
5496
|
-
return m.content.some((p) => p.type === "image_url");
|
|
5497
|
-
}
|
|
5498
|
-
return false;
|
|
5499
|
-
});
|
|
5500
|
-
if (hasVision) {
|
|
5501
|
-
logger.info(`[ckcloud] Vision content detected, filtering to vision-capable models`);
|
|
5402
|
+
effectiveSessionId = getSessionId(req.headers) ?? deriveSessionId(parsedMessages);
|
|
5403
|
+
const existingSession = effectiveSessionId ? sessionStore.getSession(effectiveSessionId) : void 0;
|
|
5404
|
+
const rawPrompt = lastUserMsg?.content;
|
|
5405
|
+
const prompt = typeof rawPrompt === "string" ? rawPrompt : Array.isArray(rawPrompt) ? rawPrompt.filter((b) => b.type === "text").map((b) => b.text ?? "").join(" ") : "";
|
|
5406
|
+
const systemMsg = parsedMessages.find((m) => m.role === "system");
|
|
5407
|
+
const systemPrompt = typeof systemMsg?.content === "string" ? systemMsg.content : void 0;
|
|
5408
|
+
const tools = parsed.tools;
|
|
5409
|
+
hasTools = Array.isArray(tools) && tools.length > 0;
|
|
5410
|
+
if (hasTools && tools) {
|
|
5411
|
+
logger.info(`[ckcloud] Tools detected (${tools.length}), forcing agentic tiers`);
|
|
5412
|
+
}
|
|
5413
|
+
hasVision = parsedMessages.some((m) => {
|
|
5414
|
+
if (Array.isArray(m.content)) {
|
|
5415
|
+
return m.content.some((p) => p.type === "image_url");
|
|
5502
5416
|
}
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
effectiveSessionId,
|
|
5527
|
-
routingDecision.model,
|
|
5528
|
-
routingDecision.tier
|
|
5529
|
-
);
|
|
5530
|
-
}
|
|
5531
|
-
} else {
|
|
5532
|
-
logger.info(
|
|
5533
|
-
`[ckcloud] Session ${effectiveSessionId?.slice(0, 8)}... keeping pinned model: ${existingSession.model} (${existingSession.tier} >= ${routingDecision.tier})`
|
|
5534
|
-
);
|
|
5535
|
-
parsed.model = existingSession.model;
|
|
5536
|
-
modelId = existingSession.model;
|
|
5537
|
-
bodyModified = true;
|
|
5538
|
-
sessionStore.touchSession(effectiveSessionId);
|
|
5539
|
-
routingDecision = {
|
|
5540
|
-
...routingDecision,
|
|
5541
|
-
model: existingSession.model,
|
|
5542
|
-
tier: existingSession.tier
|
|
5543
|
-
};
|
|
5544
|
-
}
|
|
5545
|
-
const lastAssistantMsg = [...parsedMessages].reverse().find((m) => m.role === "assistant");
|
|
5546
|
-
const assistantToolCalls = lastAssistantMsg?.tool_calls;
|
|
5547
|
-
const toolCallNames = Array.isArray(assistantToolCalls) ? assistantToolCalls.map((tc) => tc.function?.name).filter((n) => Boolean(n)) : void 0;
|
|
5548
|
-
const contentHash = hashRequestContent(prompt, toolCallNames);
|
|
5549
|
-
const shouldEscalate = sessionStore.recordRequestHash(effectiveSessionId, contentHash);
|
|
5550
|
-
if (shouldEscalate) {
|
|
5551
|
-
const activeTierConfigs = (() => {
|
|
5552
|
-
if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
|
|
5553
|
-
return routerOpts.config.agenticTiers;
|
|
5554
|
-
}
|
|
5555
|
-
if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
|
|
5556
|
-
return routerOpts.config.ecoTiers;
|
|
5557
|
-
}
|
|
5558
|
-
if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
|
|
5559
|
-
return routerOpts.config.premiumTiers;
|
|
5560
|
-
}
|
|
5561
|
-
return routerOpts.config.tiers;
|
|
5562
|
-
})();
|
|
5563
|
-
const escalation = sessionStore.escalateSession(
|
|
5564
|
-
effectiveSessionId,
|
|
5565
|
-
activeTierConfigs
|
|
5566
|
-
);
|
|
5567
|
-
if (escalation) {
|
|
5568
|
-
logger.info(
|
|
5569
|
-
`[ckcloud] \u26A1 3-strike escalation: ${existingSession.model} \u2192 ${escalation.model} (${existingSession.tier} \u2192 ${escalation.tier})`
|
|
5570
|
-
);
|
|
5571
|
-
parsed.model = escalation.model;
|
|
5572
|
-
modelId = escalation.model;
|
|
5573
|
-
routingDecision = {
|
|
5574
|
-
...routingDecision,
|
|
5575
|
-
model: escalation.model,
|
|
5576
|
-
tier: escalation.tier
|
|
5577
|
-
};
|
|
5578
|
-
}
|
|
5579
|
-
}
|
|
5580
|
-
} else {
|
|
5417
|
+
return false;
|
|
5418
|
+
});
|
|
5419
|
+
if (hasVision) {
|
|
5420
|
+
logger.info(`[ckcloud] Vision content detected, filtering to vision-capable models`);
|
|
5421
|
+
}
|
|
5422
|
+
routingDecision = route(prompt, systemPrompt, maxTokens, {
|
|
5423
|
+
...routerOpts,
|
|
5424
|
+
routingProfile: routingProfile ?? void 0,
|
|
5425
|
+
hasTools
|
|
5426
|
+
});
|
|
5427
|
+
if (existingSession) {
|
|
5428
|
+
const tierRank = {
|
|
5429
|
+
SIMPLE: 0,
|
|
5430
|
+
MEDIUM: 1,
|
|
5431
|
+
COMPLEX: 2,
|
|
5432
|
+
REASONING: 3
|
|
5433
|
+
};
|
|
5434
|
+
const existingRank = tierRank[existingSession.tier] ?? 0;
|
|
5435
|
+
const newRank = tierRank[routingDecision.tier] ?? 0;
|
|
5436
|
+
if (newRank > existingRank) {
|
|
5437
|
+
logger.info(
|
|
5438
|
+
`[ckcloud] Session ${effectiveSessionId?.slice(0, 8)}... upgrading: ${existingSession.tier} \u2192 ${routingDecision.tier} (${routingDecision.model})`
|
|
5439
|
+
);
|
|
5581
5440
|
parsed.model = routingDecision.model;
|
|
5582
5441
|
modelId = routingDecision.model;
|
|
5583
5442
|
bodyModified = true;
|
|
@@ -5587,13 +5446,72 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5587
5446
|
routingDecision.model,
|
|
5588
5447
|
routingDecision.tier
|
|
5589
5448
|
);
|
|
5449
|
+
}
|
|
5450
|
+
} else {
|
|
5451
|
+
logger.info(
|
|
5452
|
+
`[ckcloud] Session ${effectiveSessionId?.slice(0, 8)}... keeping pinned model: ${existingSession.model} (${existingSession.tier} >= ${routingDecision.tier})`
|
|
5453
|
+
);
|
|
5454
|
+
parsed.model = existingSession.model;
|
|
5455
|
+
modelId = existingSession.model;
|
|
5456
|
+
bodyModified = true;
|
|
5457
|
+
sessionStore.touchSession(effectiveSessionId);
|
|
5458
|
+
routingDecision = {
|
|
5459
|
+
...routingDecision,
|
|
5460
|
+
model: existingSession.model,
|
|
5461
|
+
tier: existingSession.tier
|
|
5462
|
+
};
|
|
5463
|
+
}
|
|
5464
|
+
const lastAssistantMsg = [...parsedMessages].reverse().find((m) => m.role === "assistant");
|
|
5465
|
+
const assistantToolCalls = lastAssistantMsg?.tool_calls;
|
|
5466
|
+
const toolCallNames = Array.isArray(assistantToolCalls) ? assistantToolCalls.map((tc) => tc.function?.name).filter((n) => Boolean(n)) : void 0;
|
|
5467
|
+
const contentHash = hashRequestContent(prompt, toolCallNames);
|
|
5468
|
+
const shouldEscalate = sessionStore.recordRequestHash(effectiveSessionId, contentHash);
|
|
5469
|
+
if (shouldEscalate) {
|
|
5470
|
+
const activeTierConfigs = (() => {
|
|
5471
|
+
if (routingDecision.reasoning?.includes("agentic") && routerOpts.config.agenticTiers) {
|
|
5472
|
+
return routerOpts.config.agenticTiers;
|
|
5473
|
+
}
|
|
5474
|
+
if (routingProfile === "eco" && routerOpts.config.ecoTiers) {
|
|
5475
|
+
return routerOpts.config.ecoTiers;
|
|
5476
|
+
}
|
|
5477
|
+
if (routingProfile === "premium" && routerOpts.config.premiumTiers) {
|
|
5478
|
+
return routerOpts.config.premiumTiers;
|
|
5479
|
+
}
|
|
5480
|
+
return routerOpts.config.tiers;
|
|
5481
|
+
})();
|
|
5482
|
+
const escalation = sessionStore.escalateSession(
|
|
5483
|
+
effectiveSessionId,
|
|
5484
|
+
activeTierConfigs
|
|
5485
|
+
);
|
|
5486
|
+
if (escalation) {
|
|
5590
5487
|
logger.info(
|
|
5591
|
-
`[ckcloud]
|
|
5488
|
+
`[ckcloud] \u26A1 3-strike escalation: ${existingSession.model} \u2192 ${escalation.model} (${existingSession.tier} \u2192 ${escalation.tier})`
|
|
5592
5489
|
);
|
|
5490
|
+
parsed.model = escalation.model;
|
|
5491
|
+
modelId = escalation.model;
|
|
5492
|
+
routingDecision = {
|
|
5493
|
+
...routingDecision,
|
|
5494
|
+
model: escalation.model,
|
|
5495
|
+
tier: escalation.tier
|
|
5496
|
+
};
|
|
5593
5497
|
}
|
|
5594
5498
|
}
|
|
5595
|
-
|
|
5499
|
+
} else {
|
|
5500
|
+
parsed.model = routingDecision.model;
|
|
5501
|
+
modelId = routingDecision.model;
|
|
5502
|
+
bodyModified = true;
|
|
5503
|
+
if (effectiveSessionId) {
|
|
5504
|
+
sessionStore.setSession(
|
|
5505
|
+
effectiveSessionId,
|
|
5506
|
+
routingDecision.model,
|
|
5507
|
+
routingDecision.tier
|
|
5508
|
+
);
|
|
5509
|
+
logger.info(
|
|
5510
|
+
`[ckcloud] Session ${effectiveSessionId.slice(0, 8)}... pinned to model: ${routingDecision.model}`
|
|
5511
|
+
);
|
|
5512
|
+
}
|
|
5596
5513
|
}
|
|
5514
|
+
options.onRouted?.(routingDecision);
|
|
5597
5515
|
}
|
|
5598
5516
|
if (bodyModified) {
|
|
5599
5517
|
body = Buffer.from(JSON.stringify(parsed));
|
|
@@ -5685,32 +5603,55 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5685
5603
|
}
|
|
5686
5604
|
deduplicator.markInflight(dedupKey);
|
|
5687
5605
|
let estimatedCostMicros;
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5606
|
+
const sendBalanceError = (status, payload) => {
|
|
5607
|
+
if (isStreaming) {
|
|
5608
|
+
res.writeHead(status, {
|
|
5609
|
+
"Content-Type": "text/event-stream",
|
|
5610
|
+
"Cache-Control": "no-cache",
|
|
5611
|
+
Connection: "keep-alive"
|
|
5612
|
+
});
|
|
5613
|
+
res.write(`data: ${JSON.stringify(payload)}
|
|
5614
|
+
|
|
5615
|
+
`);
|
|
5616
|
+
res.write("data: [DONE]\n\n");
|
|
5617
|
+
res.end();
|
|
5618
|
+
return;
|
|
5619
|
+
}
|
|
5620
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
5621
|
+
res.end(JSON.stringify(payload));
|
|
5622
|
+
};
|
|
5623
|
+
if (modelId && !options.skipBalanceCheck) {
|
|
5691
5624
|
const estimated = estimateAmount(modelId, body.length, maxTokens);
|
|
5692
5625
|
if (estimated) {
|
|
5693
5626
|
estimatedCostMicros = BigInt(estimated);
|
|
5694
5627
|
const bufferedCostMicros = estimatedCostMicros * BigInt(Math.ceil(BALANCE_CHECK_BUFFER * 100)) / 100n;
|
|
5695
5628
|
const sufficiency = await balanceMonitor.checkSufficient(bufferedCostMicros);
|
|
5696
5629
|
if (sufficiency.info.isEmpty || !sufficiency.sufficient) {
|
|
5697
|
-
const
|
|
5630
|
+
const balanceMicros = sufficiency.info.balance;
|
|
5631
|
+
const currentUSD = (Number(balanceMicros) / 1e6).toFixed(6);
|
|
5632
|
+
const requiredUSD = (Number(bufferedCostMicros) / 1e6).toFixed(6);
|
|
5633
|
+
const wallet = sufficiency.info.walletAddress;
|
|
5634
|
+
const shortWallet = wallet.length > 12 ? `${wallet.slice(0, 6)}...${wallet.slice(-4)}` : wallet;
|
|
5698
5635
|
logger.info(
|
|
5699
|
-
`[ckcloud] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} (${sufficiency.info.balanceUSD}),
|
|
5636
|
+
`[ckcloud] Wallet ${sufficiency.info.isEmpty ? "empty" : "insufficient"} (${sufficiency.info.balanceUSD}), request blocked (model: ${modelId})`
|
|
5700
5637
|
);
|
|
5701
|
-
|
|
5702
|
-
const parsed = JSON.parse(body.toString());
|
|
5703
|
-
parsed.model = FREE_MODEL;
|
|
5704
|
-
body = Buffer.from(JSON.stringify(parsed));
|
|
5705
|
-
balanceFallbackNotice = sufficiency.info.isEmpty ? `> **\u26A0\uFE0F Wallet empty** \u2014 using free model. Fund your wallet to use ${originalModel}.
|
|
5706
|
-
|
|
5707
|
-
` : `> **\u26A0\uFE0F Insufficient balance** (${sufficiency.info.balanceUSD}) \u2014 using free model instead of ${originalModel}.
|
|
5708
|
-
|
|
5709
|
-
`;
|
|
5710
|
-
options.onLowBalance?.({
|
|
5638
|
+
options.onInsufficientFunds?.({
|
|
5711
5639
|
balanceUSD: sufficiency.info.balanceUSD,
|
|
5640
|
+
requiredUSD: `$${requiredUSD}`,
|
|
5712
5641
|
walletAddress: sufficiency.info.walletAddress
|
|
5713
5642
|
});
|
|
5643
|
+
const payload = {
|
|
5644
|
+
error: {
|
|
5645
|
+
message: sufficiency.info.isEmpty ? "No USDC balance. Fund your wallet to continue." : `Insufficient USDC balance. Current: $${currentUSD}, Required: ~$${requiredUSD}`,
|
|
5646
|
+
type: sufficiency.info.isEmpty ? "empty_wallet" : "insufficient_funds",
|
|
5647
|
+
wallet,
|
|
5648
|
+
current_balance_usd: currentUSD,
|
|
5649
|
+
required_usd: requiredUSD,
|
|
5650
|
+
help: `Fund wallet ${shortWallet} with USDC`
|
|
5651
|
+
}
|
|
5652
|
+
};
|
|
5653
|
+
sendBalanceError(402, payload);
|
|
5654
|
+
return;
|
|
5714
5655
|
} else if (sufficiency.info.isLow) {
|
|
5715
5656
|
options.onLowBalance?.({
|
|
5716
5657
|
balanceUSD: sufficiency.info.balanceUSD,
|
|
@@ -5829,9 +5770,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5829
5770
|
} else {
|
|
5830
5771
|
modelsToTry = modelId ? [modelId] : [];
|
|
5831
5772
|
}
|
|
5832
|
-
if (!modelsToTry.includes(FREE_MODEL)) {
|
|
5833
|
-
modelsToTry.push(FREE_MODEL);
|
|
5834
|
-
}
|
|
5835
5773
|
let upstream;
|
|
5836
5774
|
let lastError;
|
|
5837
5775
|
let actualModelUsed = modelId;
|
|
@@ -5882,13 +5820,9 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
5882
5820
|
const isPaymentErr = /payment.*verification.*failed|payment.*settlement.*failed|insufficient.*funds|transaction_simulation_failed/i.test(
|
|
5883
5821
|
result.errorBody || ""
|
|
5884
5822
|
);
|
|
5885
|
-
if (isPaymentErr
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
logger.info(`[ckcloud] Payment error \u2014 skipping to free model: ${FREE_MODEL}`);
|
|
5889
|
-
i = freeIdx - 1;
|
|
5890
|
-
continue;
|
|
5891
|
-
}
|
|
5823
|
+
if (isPaymentErr) {
|
|
5824
|
+
logger.info(`[ckcloud] Payment error \u2014 stopping retries`);
|
|
5825
|
+
break;
|
|
5892
5826
|
}
|
|
5893
5827
|
logger.info(
|
|
5894
5828
|
`[ckcloud] Provider error from ${tryModel}, trying fallback: ${result.errorBody?.slice(0, 100)}`
|
|
@@ -6018,25 +5952,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
6018
5952
|
`;
|
|
6019
5953
|
safeWrite(res, roleData);
|
|
6020
5954
|
responseChunks.push(Buffer.from(roleData));
|
|
6021
|
-
if (balanceFallbackNotice) {
|
|
6022
|
-
const noticeChunk = {
|
|
6023
|
-
...baseChunk,
|
|
6024
|
-
choices: [
|
|
6025
|
-
{
|
|
6026
|
-
index,
|
|
6027
|
-
delta: { content: balanceFallbackNotice },
|
|
6028
|
-
logprobs: null,
|
|
6029
|
-
finish_reason: null
|
|
6030
|
-
}
|
|
6031
|
-
]
|
|
6032
|
-
};
|
|
6033
|
-
const noticeData = `data: ${JSON.stringify(noticeChunk)}
|
|
6034
|
-
|
|
6035
|
-
`;
|
|
6036
|
-
safeWrite(res, noticeData);
|
|
6037
|
-
responseChunks.push(Buffer.from(noticeData));
|
|
6038
|
-
balanceFallbackNotice = void 0;
|
|
6039
|
-
}
|
|
6040
5955
|
if (content) {
|
|
6041
5956
|
const contentChunk = {
|
|
6042
5957
|
...baseChunk,
|
|
@@ -6129,17 +6044,6 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
6129
6044
|
}
|
|
6130
6045
|
}
|
|
6131
6046
|
let responseBody = Buffer.concat(bodyParts);
|
|
6132
|
-
if (balanceFallbackNotice && responseBody.length > 0) {
|
|
6133
|
-
try {
|
|
6134
|
-
const parsed = JSON.parse(responseBody.toString());
|
|
6135
|
-
if (parsed.choices?.[0]?.message?.content !== void 0) {
|
|
6136
|
-
parsed.choices[0].message.content = balanceFallbackNotice + parsed.choices[0].message.content;
|
|
6137
|
-
responseBody = Buffer.from(JSON.stringify(parsed));
|
|
6138
|
-
}
|
|
6139
|
-
} catch {
|
|
6140
|
-
}
|
|
6141
|
-
balanceFallbackNotice = void 0;
|
|
6142
|
-
}
|
|
6143
6047
|
responseHeaders["content-length"] = String(responseBody.length);
|
|
6144
6048
|
res.writeHead(upstream.status, responseHeaders);
|
|
6145
6049
|
safeWrite(res, responseBody);
|
|
@@ -6277,6 +6181,13 @@ var plugin = {
|
|
|
6277
6181
|
`Failed to refresh models list: ${err instanceof Error ? err.message : String(err)}`
|
|
6278
6182
|
);
|
|
6279
6183
|
});
|
|
6184
|
+
createWalletInfoCommand().then((walletCommand) => {
|
|
6185
|
+
api.registerCommand(walletCommand);
|
|
6186
|
+
}).catch((err) => {
|
|
6187
|
+
api.logger.warn(
|
|
6188
|
+
`Failed to register /wallet command: ${err instanceof Error ? err.message : String(err)}`
|
|
6189
|
+
);
|
|
6190
|
+
});
|
|
6280
6191
|
createStatsCommand().then((statsCommand) => {
|
|
6281
6192
|
api.registerCommand(statsCommand);
|
|
6282
6193
|
}).catch((err) => {
|
|
@@ -6462,12 +6373,12 @@ function injectModelsConfig(logger2) {
|
|
|
6462
6373
|
}
|
|
6463
6374
|
const model = defaults.model;
|
|
6464
6375
|
if (!model.primary) {
|
|
6465
|
-
model.primary = "ckcloud/
|
|
6376
|
+
model.primary = "ckcloud/auto";
|
|
6466
6377
|
logger2.info("Set default model");
|
|
6467
6378
|
needsWrite = true;
|
|
6468
6379
|
}
|
|
6469
6380
|
const TOP_MODELS = [
|
|
6470
|
-
"
|
|
6381
|
+
"auto",
|
|
6471
6382
|
"eco",
|
|
6472
6383
|
"premium"
|
|
6473
6384
|
];
|
|
@@ -6605,6 +6516,67 @@ async function createStatsCommand() {
|
|
|
6605
6516
|
}
|
|
6606
6517
|
};
|
|
6607
6518
|
}
|
|
6519
|
+
async function createWalletInfoCommand() {
|
|
6520
|
+
return {
|
|
6521
|
+
name: "wallet",
|
|
6522
|
+
description: "Show ckcloud wallet info (address, chain, balance)",
|
|
6523
|
+
acceptsArgs: false,
|
|
6524
|
+
requireAuth: false,
|
|
6525
|
+
handler: async (_ctx) => {
|
|
6526
|
+
try {
|
|
6527
|
+
const wallet = await resolveOrGenerateWalletKey();
|
|
6528
|
+
const chain = await resolvePaymentChain();
|
|
6529
|
+
let solanaAddress;
|
|
6530
|
+
if (wallet.solanaPrivateKeyBytes) {
|
|
6531
|
+
solanaAddress = await getSolanaAddress(wallet.solanaPrivateKeyBytes).catch(() => void 0);
|
|
6532
|
+
}
|
|
6533
|
+
const evmAddress = wallet.address;
|
|
6534
|
+
const solAddress2 = solanaAddress || "(not set up)";
|
|
6535
|
+
let balanceInfo;
|
|
6536
|
+
if (activeProxyHandle) {
|
|
6537
|
+
balanceInfo = await activeProxyHandle.balanceMonitor.checkBalance().catch(() => void 0);
|
|
6538
|
+
}
|
|
6539
|
+
if (!balanceInfo) {
|
|
6540
|
+
if (chain === "solana") {
|
|
6541
|
+
if (solanaAddress) {
|
|
6542
|
+
const monitor = new SolanaBalanceMonitor(solanaAddress);
|
|
6543
|
+
balanceInfo = await monitor.checkBalance().catch(() => void 0);
|
|
6544
|
+
}
|
|
6545
|
+
} else {
|
|
6546
|
+
const monitor = new BalanceMonitor(wallet.address);
|
|
6547
|
+
balanceInfo = await monitor.checkBalance().catch(() => void 0);
|
|
6548
|
+
}
|
|
6549
|
+
}
|
|
6550
|
+
let balanceLine = "Balance: (checking...)";
|
|
6551
|
+
if (chain === "solana" && !solanaAddress) {
|
|
6552
|
+
balanceLine = "Balance: (unavailable \u2014 Solana wallet not set up)";
|
|
6553
|
+
} else if (balanceInfo) {
|
|
6554
|
+
if (balanceInfo.isEmpty) {
|
|
6555
|
+
balanceLine = "Balance: $0.00";
|
|
6556
|
+
} else if (balanceInfo.isLow) {
|
|
6557
|
+
balanceLine = `Balance: ${balanceInfo.balanceUSD} (low)`;
|
|
6558
|
+
} else {
|
|
6559
|
+
balanceLine = `Balance: ${balanceInfo.balanceUSD}`;
|
|
6560
|
+
}
|
|
6561
|
+
}
|
|
6562
|
+
const lines = [
|
|
6563
|
+
"Wallet info",
|
|
6564
|
+
`Chain: ${chain}`,
|
|
6565
|
+
`EVM Address: ${evmAddress}`,
|
|
6566
|
+
`Solana Address: ${solAddress2}`,
|
|
6567
|
+
`Source: ${wallet.source}`,
|
|
6568
|
+
balanceLine
|
|
6569
|
+
];
|
|
6570
|
+
return { text: lines.join("\n") };
|
|
6571
|
+
} catch (err) {
|
|
6572
|
+
return {
|
|
6573
|
+
text: `Failed to load wallet info: ${err instanceof Error ? err.message : String(err)}`,
|
|
6574
|
+
isError: true
|
|
6575
|
+
};
|
|
6576
|
+
}
|
|
6577
|
+
}
|
|
6578
|
+
};
|
|
6579
|
+
}
|
|
6608
6580
|
async function startProxyInBackground(api) {
|
|
6609
6581
|
const wallet = await resolveOrGenerateWalletKey();
|
|
6610
6582
|
if (wallet.source === "generated") {
|