@blockrun/mcp 0.22.3 → 0.23.0
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 +2 -2
- package/dist/index.js +227 -128
- package/package.json +6 -3
- package/skills/exa-research/SKILL.md +177 -0
- package/skills/image-prompting/SKILL.md +308 -0
- package/skills/image-prompting/example-100t-poster.jpg +0 -0
- package/skills/modal/SKILL.md +118 -0
- package/skills/phone/SKILL.md +176 -0
- package/skills/prediction-markets/SKILL.md +263 -0
- package/skills/rpc/SKILL.md +106 -0
- package/skills/search/SKILL.md +96 -0
- package/skills/surf/SKILL.md +295 -0
package/dist/index.js
CHANGED
|
@@ -221,6 +221,58 @@ async function loadModels(llm, cache) {
|
|
|
221
221
|
return cache.models;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
// src/utils/budget.ts
|
|
225
|
+
var EPSILON = 1e-9;
|
|
226
|
+
function formatUsd(amount) {
|
|
227
|
+
return `$${amount.toFixed(amount >= 1 ? 2 : 4)}`;
|
|
228
|
+
}
|
|
229
|
+
function checkBudget(budget, agentId, estimatedCost = 1e-3) {
|
|
230
|
+
const cost = Math.max(0, estimatedCost);
|
|
231
|
+
if (cost > 0 && budget.limit !== null && budget.spent + cost > budget.limit + EPSILON) {
|
|
232
|
+
const remaining = Math.max(0, budget.limit - budget.spent);
|
|
233
|
+
return {
|
|
234
|
+
allowed: false,
|
|
235
|
+
reason: `Global budget limit ${formatUsd(budget.limit)} would be exceeded (${formatUsd(budget.spent)} spent, ${formatUsd(remaining)} remaining, next call estimated ${formatUsd(cost)})`
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (agentId) {
|
|
239
|
+
const agentBudget = budget.agents.get(agentId);
|
|
240
|
+
if (cost > 0 && agentBudget && agentBudget.spent + cost > agentBudget.limit + EPSILON) {
|
|
241
|
+
const remaining = Math.max(0, agentBudget.limit - agentBudget.spent);
|
|
242
|
+
return {
|
|
243
|
+
allowed: false,
|
|
244
|
+
reason: `Agent "${agentId}" budget ${formatUsd(agentBudget.limit)} would be exceeded (${formatUsd(agentBudget.spent)} spent, ${formatUsd(remaining)} remaining, next call estimated ${formatUsd(cost)})`
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return { allowed: true };
|
|
249
|
+
}
|
|
250
|
+
function recordSpending(budget, cost, agentId) {
|
|
251
|
+
budget.spent += cost;
|
|
252
|
+
budget.calls += 1;
|
|
253
|
+
if (agentId) {
|
|
254
|
+
const agentBudget = budget.agents.get(agentId);
|
|
255
|
+
if (agentBudget) {
|
|
256
|
+
agentBudget.spent += cost;
|
|
257
|
+
agentBudget.calls += 1;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function amountToUsd(amount) {
|
|
262
|
+
const n = typeof amount === "string" ? Number(amount) : typeof amount === "number" ? amount : NaN;
|
|
263
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
264
|
+
return n / 1e6;
|
|
265
|
+
}
|
|
266
|
+
function recordActualSpend(budget, actualUsd, estimate, agentId) {
|
|
267
|
+
const cost = typeof actualUsd === "number" && Number.isFinite(actualUsd) && actualUsd > 0 ? actualUsd : Math.max(0, estimate);
|
|
268
|
+
recordSpending(budget, cost, agentId);
|
|
269
|
+
}
|
|
270
|
+
function parseBudgetLimitEnv(raw) {
|
|
271
|
+
if (!raw) return null;
|
|
272
|
+
const n = Number(raw.trim().replace(/^\$/, ""));
|
|
273
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
274
|
+
}
|
|
275
|
+
|
|
224
276
|
// src/tools/wallet.ts
|
|
225
277
|
import { z } from "zod";
|
|
226
278
|
|
|
@@ -229,7 +281,6 @@ import QRCode from "qrcode";
|
|
|
229
281
|
import open from "open";
|
|
230
282
|
import * as fs2 from "fs";
|
|
231
283
|
import * as path3 from "path";
|
|
232
|
-
import sharp from "sharp";
|
|
233
284
|
|
|
234
285
|
// src/utils/constants.ts
|
|
235
286
|
import * as path2 from "path";
|
|
@@ -255,6 +306,17 @@ var MODEL_TIERS = {
|
|
|
255
306
|
|
|
256
307
|
// src/utils/qr.ts
|
|
257
308
|
var SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
309
|
+
var sharpModule;
|
|
310
|
+
async function loadSharp() {
|
|
311
|
+
if (sharpModule !== void 0) return sharpModule;
|
|
312
|
+
try {
|
|
313
|
+
const mod = await import("sharp");
|
|
314
|
+
sharpModule = mod.default;
|
|
315
|
+
} catch {
|
|
316
|
+
sharpModule = null;
|
|
317
|
+
}
|
|
318
|
+
return sharpModule;
|
|
319
|
+
}
|
|
258
320
|
function getEip681Uri(address, amountUsdc = 1) {
|
|
259
321
|
const amountWei = Math.floor(amountUsdc * 1e6);
|
|
260
322
|
return `ethereum:${USDC_ADDRESS}@${BASE_CHAIN_ID}/transfer?address=${address}&uint256=${amountWei}`;
|
|
@@ -279,6 +341,8 @@ function buildSolanaLogoSvg(size) {
|
|
|
279
341
|
}
|
|
280
342
|
async function overlayLogo(qrBuf, chain, qrSize) {
|
|
281
343
|
if (chain !== "solana") return qrBuf;
|
|
344
|
+
const sharp = await loadSharp();
|
|
345
|
+
if (!sharp) return qrBuf;
|
|
282
346
|
const logoSize = Math.round(qrSize * 0.18);
|
|
283
347
|
const pad = Math.round(logoSize * 0.08);
|
|
284
348
|
const logoBuf = await sharp(Buffer.from(buildSolanaLogoSvg(logoSize))).resize(logoSize, logoSize).extend({ top: pad, bottom: pad, left: pad, right: pad, background: { r: 255, g: 255, b: 255, alpha: 1 } }).toBuffer();
|
|
@@ -394,8 +458,9 @@ To pay on Solana (no env vars, no file editing, no restart):
|
|
|
394
458
|
1. action:"chain" chain:"solana" \u2192 provisions + activates the Solana wallet
|
|
395
459
|
2. action:"setup" \u2192 Solana address + funding QR (send USDC SPL on Solana)
|
|
396
460
|
Switch back with action:"chain" chain:"base". Base-only \u2014 these ignore Solana and
|
|
397
|
-
need Base: blockrun_image, blockrun_music, blockrun_speech, blockrun_video,
|
|
398
|
-
blockrun_price, blockrun_chat routing:"smart", and native
|
|
461
|
+
need Base: blockrun_image, blockrun_music, blockrun_speech, blockrun_video,
|
|
462
|
+
blockrun_realface, paid blockrun_price, blockrun_chat routing:"smart", and native
|
|
463
|
+
Anthropic (claude-*).
|
|
399
464
|
|
|
400
465
|
Actions:
|
|
401
466
|
- status (default): Both wallet addresses + USDC balances, active chain, session spending
|
|
@@ -671,45 +736,24 @@ Paying on ${chain} | View active: ${info.explorerUrl}${info.isNew ? "\nNEW WALLE
|
|
|
671
736
|
import { z as z2 } from "zod";
|
|
672
737
|
import { LLMClient as LLMClient2 } from "@blockrun/llm";
|
|
673
738
|
|
|
674
|
-
// src/
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
if (agentId) {
|
|
689
|
-
const agentBudget = budget.agents.get(agentId);
|
|
690
|
-
if (cost > 0 && agentBudget && agentBudget.spent + cost > agentBudget.limit + EPSILON) {
|
|
691
|
-
const remaining = Math.max(0, agentBudget.limit - agentBudget.spent);
|
|
692
|
-
return {
|
|
693
|
-
allowed: false,
|
|
694
|
-
reason: `Agent "${agentId}" budget ${formatUsd(agentBudget.limit)} would be exceeded (${formatUsd(agentBudget.spent)} spent, ${formatUsd(remaining)} remaining, next call estimated ${formatUsd(cost)})`
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
return { allowed: true };
|
|
699
|
-
}
|
|
700
|
-
function recordSpending(budget, cost, agentId) {
|
|
701
|
-
budget.spent += cost;
|
|
702
|
-
budget.calls += 1;
|
|
703
|
-
if (agentId) {
|
|
704
|
-
const agentBudget = budget.agents.get(agentId);
|
|
705
|
-
if (agentBudget) {
|
|
706
|
-
agentBudget.spent += cost;
|
|
707
|
-
agentBudget.calls += 1;
|
|
708
|
-
}
|
|
739
|
+
// src/tools/chat-anthropic.ts
|
|
740
|
+
function anthropicCallCost(model, usage) {
|
|
741
|
+
if (!usage) return null;
|
|
742
|
+
const id = model.toLowerCase();
|
|
743
|
+
let inRate = 5, outRate = 25;
|
|
744
|
+
if (id.includes("opus")) {
|
|
745
|
+
inRate = 15;
|
|
746
|
+
outRate = 75;
|
|
747
|
+
} else if (id.includes("haiku")) {
|
|
748
|
+
inRate = 1;
|
|
749
|
+
outRate = 5;
|
|
750
|
+
} else if (id.includes("sonnet")) {
|
|
751
|
+
inRate = 3;
|
|
752
|
+
outRate = 15;
|
|
709
753
|
}
|
|
754
|
+
const cost = (usage.input_tokens ?? 0) / 1e6 * inRate + (usage.output_tokens ?? 0) / 1e6 * outRate;
|
|
755
|
+
return cost > 0 ? cost : null;
|
|
710
756
|
}
|
|
711
|
-
|
|
712
|
-
// src/tools/chat-anthropic.ts
|
|
713
757
|
function isAnthropicModel(model) {
|
|
714
758
|
const id = model.trim();
|
|
715
759
|
return /^anthropic\//i.test(id) || /^claude-/i.test(id);
|
|
@@ -797,10 +841,9 @@ async function handleAnthropicNative(args) {
|
|
|
797
841
|
try {
|
|
798
842
|
native = await client.messages.create(params);
|
|
799
843
|
} catch (error) {
|
|
800
|
-
|
|
801
|
-
return { content: [{ type: "text", text: formatError(errorMessage) }], isError: true };
|
|
844
|
+
return { content: [{ type: "text", text: formatError(extractErrorMessage(error)) }], isError: true };
|
|
802
845
|
}
|
|
803
|
-
|
|
846
|
+
recordActualSpend(budget, anthropicCallCost(native.model, native.usage), estimatedCost, agentId);
|
|
804
847
|
const thinkingBlocks = native.content.filter(isThinkingBlock);
|
|
805
848
|
const textBlocks = native.content.filter(isTextBlock);
|
|
806
849
|
const answerText = textBlocks.map((b) => b.text).join("\n");
|
|
@@ -838,23 +881,32 @@ ${thinkingText}` });
|
|
|
838
881
|
}
|
|
839
882
|
|
|
840
883
|
// src/tools/chat.ts
|
|
841
|
-
function estimateChatCost(mode, model, routing, routingProfile) {
|
|
884
|
+
function estimateChatCost(maxTokens, mode, model, routing, routingProfile) {
|
|
842
885
|
if (mode === "free") return 0;
|
|
843
886
|
if (model?.startsWith("nvidia/")) return 0;
|
|
844
887
|
if (routing === "smart" && routingProfile === "free") return 0;
|
|
888
|
+
const out = Math.max(maxTokens ?? 1024, 256);
|
|
889
|
+
const frontierReserve = Math.max(0.01, out / 1e6 * 20);
|
|
845
890
|
if (routing === "smart") {
|
|
846
891
|
switch (routingProfile) {
|
|
847
892
|
case "eco":
|
|
848
|
-
return
|
|
893
|
+
return 0.01;
|
|
849
894
|
case "premium":
|
|
850
|
-
return
|
|
895
|
+
return frontierReserve;
|
|
851
896
|
case "auto":
|
|
852
897
|
default:
|
|
853
|
-
return 0.01;
|
|
898
|
+
return Math.max(0.01, frontierReserve * 0.5);
|
|
854
899
|
}
|
|
855
900
|
}
|
|
856
|
-
if (mode === "reasoning" || mode === "powerful") return
|
|
857
|
-
return
|
|
901
|
+
if (mode === "reasoning" || mode === "powerful") return frontierReserve;
|
|
902
|
+
if (model) return frontierReserve;
|
|
903
|
+
return Math.max(2e-3, out / 1e6 * 3);
|
|
904
|
+
}
|
|
905
|
+
async function withSettledCost(client, run) {
|
|
906
|
+
const before = client.getSpending().totalUsd;
|
|
907
|
+
const result = await run();
|
|
908
|
+
const settledUsd = client.getSpending().totalUsd - before;
|
|
909
|
+
return { result, settledUsd };
|
|
858
910
|
}
|
|
859
911
|
function registerChatTool(server, budget) {
|
|
860
912
|
server.registerTool(
|
|
@@ -866,13 +918,13 @@ Notable modes:
|
|
|
866
918
|
- mode:"glm" \u2192 Zhipu GLM-5 / GLM-5-Turbo ($0.001/call, excellent for coding tasks, pays via USDC on BlockRun)
|
|
867
919
|
- mode:"coding" \u2192 GLM-5 first, then code-specialized models
|
|
868
920
|
- mode:"cheap" \u2192 GLM-5, NVIDIA free, DeepSeek
|
|
869
|
-
- mode:"reasoning" \u2192 o3, o1,
|
|
921
|
+
- mode:"reasoning" \u2192 Claude Opus, o3, o1, deepseek-reasoner
|
|
870
922
|
- mode:"free" \u2192 NVIDIA models (no cost)
|
|
871
923
|
- routing:"smart" \u2192 auto-select via ClawRouter
|
|
872
924
|
|
|
873
925
|
Pick directly: model:"zai/glm-5", model:"openai/o3", model:"nvidia/deepseek-v4-flash" (free).
|
|
874
926
|
|
|
875
|
-
Run blockrun_models to see all
|
|
927
|
+
Run blockrun_models to see all available models with pricing.`,
|
|
876
928
|
inputSchema: {
|
|
877
929
|
message: z2.string().describe("Your message to the AI"),
|
|
878
930
|
model: z2.string().optional().describe("Specific model ID (e.g., 'zai/glm-5', 'openai/o3')"),
|
|
@@ -904,7 +956,7 @@ Run blockrun_models to see all 41+ models with pricing.`,
|
|
|
904
956
|
async ({ message, model, mode, routing, routing_profile, system, max_tokens, temperature, response_format, stop, thinking, agent_id, messages }) => {
|
|
905
957
|
const llm = getClient();
|
|
906
958
|
const responseFormat = response_format ? { type: response_format } : void 0;
|
|
907
|
-
const estimatedCost = estimateChatCost(mode, model, routing, routing_profile);
|
|
959
|
+
const estimatedCost = estimateChatCost(max_tokens, mode, model, routing, routing_profile);
|
|
908
960
|
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
909
961
|
if (!budgetCheck.allowed) {
|
|
910
962
|
return {
|
|
@@ -940,7 +992,7 @@ Run blockrun_models to see all 41+ models with pricing.`,
|
|
|
940
992
|
};
|
|
941
993
|
}
|
|
942
994
|
try {
|
|
943
|
-
const result = await llm.smartChat(message, {
|
|
995
|
+
const { result, settledUsd } = await withSettledCost(llm, () => llm.smartChat(message, {
|
|
944
996
|
system,
|
|
945
997
|
maxTokens: max_tokens,
|
|
946
998
|
maxOutputTokens: max_tokens,
|
|
@@ -951,8 +1003,8 @@ Run blockrun_models to see all 41+ models with pricing.`,
|
|
|
951
1003
|
routingProfile: routing_profile === "free" ? void 0 : routing_profile,
|
|
952
1004
|
responseFormat,
|
|
953
1005
|
stop
|
|
954
|
-
});
|
|
955
|
-
|
|
1006
|
+
}));
|
|
1007
|
+
recordActualSpend(budget, settledUsd, result.routing.costEstimate || estimatedCost, agent_id);
|
|
956
1008
|
return {
|
|
957
1009
|
content: [{ type: "text", text: `[${result.model} | ${result.routing.tier} | $${result.routing.costEstimate.toFixed(4)} | ${Math.round((result.routing.savings ?? 0) * 100)}% savings]
|
|
958
1010
|
|
|
@@ -964,8 +1016,7 @@ ${result.response}` }],
|
|
|
964
1016
|
}
|
|
965
1017
|
};
|
|
966
1018
|
} catch (error) {
|
|
967
|
-
|
|
968
|
-
return { content: [{ type: "text", text: formatError(errorMessage2) }], isError: true };
|
|
1019
|
+
return { content: [{ type: "text", text: formatError(extractErrorMessage(error)) }], isError: true };
|
|
969
1020
|
}
|
|
970
1021
|
}
|
|
971
1022
|
if (messages && messages.length > 0) {
|
|
@@ -976,14 +1027,14 @@ ${result.response}` }],
|
|
|
976
1027
|
{ role: "user", content: message }
|
|
977
1028
|
];
|
|
978
1029
|
try {
|
|
979
|
-
const result = await llm.chatCompletion(targetModel, fullMessages, {
|
|
1030
|
+
const { result, settledUsd } = await withSettledCost(llm, () => llm.chatCompletion(targetModel, fullMessages, {
|
|
980
1031
|
maxTokens: max_tokens,
|
|
981
1032
|
temperature,
|
|
982
1033
|
responseFormat,
|
|
983
1034
|
stop
|
|
984
|
-
});
|
|
1035
|
+
}));
|
|
985
1036
|
const reply = result.choices?.[0]?.message?.content || "";
|
|
986
|
-
|
|
1037
|
+
recordActualSpend(budget, settledUsd, estimatedCost, agent_id);
|
|
987
1038
|
return {
|
|
988
1039
|
content: [{ type: "text", text: `[${targetModel} | ${fullMessages.length} msgs]
|
|
989
1040
|
|
|
@@ -991,25 +1042,23 @@ ${reply}` }],
|
|
|
991
1042
|
structuredContent: { model_used: targetModel, response: reply, message_count: fullMessages.length }
|
|
992
1043
|
};
|
|
993
1044
|
} catch (error) {
|
|
994
|
-
|
|
995
|
-
return { content: [{ type: "text", text: formatError(errorMessage2) }], isError: true };
|
|
1045
|
+
return { content: [{ type: "text", text: formatError(extractErrorMessage(error)) }], isError: true };
|
|
996
1046
|
}
|
|
997
1047
|
}
|
|
998
1048
|
if (model) {
|
|
999
1049
|
try {
|
|
1000
|
-
const response = await llm.chat(model, message, {
|
|
1050
|
+
const { result: response, settledUsd } = await withSettledCost(llm, () => llm.chat(model, message, {
|
|
1001
1051
|
system,
|
|
1002
1052
|
maxTokens: max_tokens,
|
|
1003
1053
|
temperature,
|
|
1004
1054
|
responseFormat,
|
|
1005
1055
|
stop
|
|
1006
|
-
});
|
|
1007
|
-
|
|
1056
|
+
}));
|
|
1057
|
+
recordActualSpend(budget, settledUsd, estimatedCost, agent_id);
|
|
1008
1058
|
return { content: [{ type: "text", text: response }] };
|
|
1009
1059
|
} catch (error) {
|
|
1010
|
-
const errorMessage2 = error instanceof Error ? error.message : String(error);
|
|
1011
1060
|
return {
|
|
1012
|
-
content: [{ type: "text", text: formatError(
|
|
1061
|
+
content: [{ type: "text", text: formatError(extractErrorMessage(error)) }],
|
|
1013
1062
|
isError: true
|
|
1014
1063
|
};
|
|
1015
1064
|
}
|
|
@@ -1019,14 +1068,14 @@ ${reply}` }],
|
|
|
1019
1068
|
let lastError = null;
|
|
1020
1069
|
for (const m of models) {
|
|
1021
1070
|
try {
|
|
1022
|
-
const response = await llm.chat(m, message, {
|
|
1071
|
+
const { result: response, settledUsd } = await withSettledCost(llm, () => llm.chat(m, message, {
|
|
1023
1072
|
system,
|
|
1024
1073
|
maxTokens: max_tokens,
|
|
1025
1074
|
temperature,
|
|
1026
1075
|
responseFormat,
|
|
1027
1076
|
stop
|
|
1028
|
-
});
|
|
1029
|
-
|
|
1077
|
+
}));
|
|
1078
|
+
recordActualSpend(budget, settledUsd, estimatedCost, agent_id);
|
|
1030
1079
|
return {
|
|
1031
1080
|
content: [{ type: "text", text: `[${m}]
|
|
1032
1081
|
|
|
@@ -1038,7 +1087,7 @@ ${response}` }],
|
|
|
1038
1087
|
continue;
|
|
1039
1088
|
}
|
|
1040
1089
|
}
|
|
1041
|
-
const errorMessage = lastError
|
|
1090
|
+
const errorMessage = lastError ? extractErrorMessage(lastError) : "All models failed";
|
|
1042
1091
|
return {
|
|
1043
1092
|
content: [{ type: "text", text: formatError(errorMessage) }],
|
|
1044
1093
|
isError: true
|
|
@@ -1063,40 +1112,47 @@ function registerModelsTool(server, modelCache) {
|
|
|
1063
1112
|
}
|
|
1064
1113
|
},
|
|
1065
1114
|
async ({ category, provider }) => {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
if (category
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1115
|
+
try {
|
|
1116
|
+
let models = await loadModels(getClient(), modelCache);
|
|
1117
|
+
if (provider) {
|
|
1118
|
+
const p = provider.toLowerCase();
|
|
1119
|
+
models = models.filter((m) => m.id.toLowerCase().startsWith(p + "/"));
|
|
1120
|
+
}
|
|
1121
|
+
if (category && category !== "all") {
|
|
1122
|
+
if (category === "image") {
|
|
1123
|
+
models = models.filter((m) => getModelType(m) === "image");
|
|
1124
|
+
} else if (category === "embedding") {
|
|
1125
|
+
models = models.filter((m) => m.id.includes("embed"));
|
|
1126
|
+
} else {
|
|
1127
|
+
models = models.filter((m) => "categories" in m && m.categories?.includes(category));
|
|
1128
|
+
}
|
|
1078
1129
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
content: [{ type: "text", text: `Models (${models.length}):
|
|
1130
|
+
const lines = models.map((m) => {
|
|
1131
|
+
if (getModelType(m) === "image") {
|
|
1132
|
+
const image = m;
|
|
1133
|
+
const pricing2 = image.pricePerImage ? `$${image.pricePerImage}/image` : "";
|
|
1134
|
+
const sizes = image.supportedSizes?.length ? ` | sizes: ${image.supportedSizes.join(", ")}` : "";
|
|
1135
|
+
return `- ${image.id}${pricing2 ? ` (${pricing2})` : ""}${sizes} [image]`;
|
|
1136
|
+
}
|
|
1137
|
+
const llmModel = m;
|
|
1138
|
+
const input = llmModel.inputPrice ? `$${llmModel.inputPrice}/M in` : "";
|
|
1139
|
+
const output = llmModel.outputPrice ? `$${llmModel.outputPrice}/M out` : "";
|
|
1140
|
+
const pricing = [input, output].filter(Boolean).join(", ");
|
|
1141
|
+
const ctx = llmModel.contextWindow ? ` | ${Math.round(llmModel.contextWindow / 1e3)}K ctx` : "";
|
|
1142
|
+
const cats = llmModel.categories?.length ? ` [${llmModel.categories.join(", ")}]` : "";
|
|
1143
|
+
return `- ${llmModel.id}${pricing ? ` (${pricing})` : ""}${ctx}${cats}`;
|
|
1144
|
+
});
|
|
1145
|
+
return {
|
|
1146
|
+
content: [{ type: "text", text: `Models (${models.length}):
|
|
1097
1147
|
${lines.join("\n")}` }],
|
|
1098
|
-
|
|
1099
|
-
|
|
1148
|
+
structuredContent: { count: models.length, models }
|
|
1149
|
+
};
|
|
1150
|
+
} catch (err) {
|
|
1151
|
+
return {
|
|
1152
|
+
content: [{ type: "text", text: formatError(extractErrorMessage(err)) }],
|
|
1153
|
+
isError: true
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1100
1156
|
}
|
|
1101
1157
|
);
|
|
1102
1158
|
}
|
|
@@ -1176,9 +1232,14 @@ var IMAGE_MODELS = [
|
|
|
1176
1232
|
"xai/grok-imagine-image",
|
|
1177
1233
|
"xai/grok-imagine-image-pro"
|
|
1178
1234
|
];
|
|
1235
|
+
function isLargerThanBase(size) {
|
|
1236
|
+
const m = /^\s*(\d+)\s*[x×]\s*(\d+)\s*$/i.exec(size);
|
|
1237
|
+
if (!m) return false;
|
|
1238
|
+
return Math.max(Number(m[1]), Number(m[2])) > 1024;
|
|
1239
|
+
}
|
|
1179
1240
|
function estimateCost(model, size) {
|
|
1180
1241
|
const base = GENERATE_MODEL_COST[model] ?? 0.06;
|
|
1181
|
-
if (
|
|
1242
|
+
if (LARGE_SIZE_COST[model] && isLargerThanBase(size)) {
|
|
1182
1243
|
return LARGE_SIZE_COST[model];
|
|
1183
1244
|
}
|
|
1184
1245
|
return base;
|
|
@@ -1455,7 +1516,7 @@ Returns a time-limited CDN URL \u2014 download immediately if you need to keep t
|
|
|
1455
1516
|
const track = data.data?.[0];
|
|
1456
1517
|
if (!track?.url) throw new Error("No track URL in response");
|
|
1457
1518
|
const txHash = resp.headers.get("X-Payment-Receipt") || resp.headers.get("x-payment-receipt");
|
|
1458
|
-
|
|
1519
|
+
recordActualSpend(budget, amountToUsd(details.amount), MUSIC_COST, agent_id);
|
|
1459
1520
|
const lines = [
|
|
1460
1521
|
`\u{1F3B5} Track ready!`,
|
|
1461
1522
|
`URL: ${track.url}`,
|
|
@@ -1631,6 +1692,7 @@ Returns a hosted audio URL \u2014 download immediately if you need to keep the f
|
|
|
1631
1692
|
if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
|
|
1632
1693
|
const paymentRequired = parsePaymentRequired2(prHeader);
|
|
1633
1694
|
const details = extractPaymentDetails2(paymentRequired);
|
|
1695
|
+
const billedUsd = amountToUsd(details.amount) ?? cost;
|
|
1634
1696
|
const paymentPayload = await createPaymentPayload2(
|
|
1635
1697
|
privateKey,
|
|
1636
1698
|
account.address,
|
|
@@ -1663,7 +1725,7 @@ Returns a hosted audio URL \u2014 download immediately if you need to keep the f
|
|
|
1663
1725
|
const clip = data.data?.[0];
|
|
1664
1726
|
if (!clip?.url) throw new Error("No audio URL in response");
|
|
1665
1727
|
const txHash = resp.headers.get("X-Payment-Receipt") || resp.headers.get("x-payment-receipt");
|
|
1666
|
-
|
|
1728
|
+
recordActualSpend(budget, billedUsd, cost, agent_id);
|
|
1667
1729
|
const lines = [
|
|
1668
1730
|
action === "sound_effect" ? `\u{1F50A} Sound effect ready!` : `\u{1F5E3}\uFE0F Speech ready!`,
|
|
1669
1731
|
`URL: ${clip.url}`,
|
|
@@ -1671,7 +1733,7 @@ Returns a hosted audio URL \u2014 download immediately if you need to keep the f
|
|
|
1671
1733
|
...clip.characters !== void 0 ? [`Characters: ${clip.characters}`] : [],
|
|
1672
1734
|
...clip.duration_seconds !== void 0 ? [`Duration: ${clip.duration_seconds}s`] : [],
|
|
1673
1735
|
`Model: ${data.model || (action === "sound_effect" ? "elevenlabs/sound-effects" : model)}`,
|
|
1674
|
-
`Cost: $${
|
|
1736
|
+
`Cost: $${billedUsd.toFixed(4)}`,
|
|
1675
1737
|
...txHash ? [`Tx: ${txHash}`] : [],
|
|
1676
1738
|
``,
|
|
1677
1739
|
`Note: This URL may expire \u2014 download it now if you need to keep the file.`
|
|
@@ -1684,7 +1746,7 @@ Returns a hosted audio URL \u2014 download immediately if you need to keep the f
|
|
|
1684
1746
|
...clip.characters !== void 0 ? { characters: clip.characters } : {},
|
|
1685
1747
|
...clip.duration_seconds !== void 0 ? { duration_seconds: clip.duration_seconds } : {},
|
|
1686
1748
|
model: data.model || (action === "sound_effect" ? "elevenlabs/sound-effects" : model),
|
|
1687
|
-
cost_usd:
|
|
1749
|
+
cost_usd: billedUsd,
|
|
1688
1750
|
...txHash ? { txHash } : {}
|
|
1689
1751
|
}
|
|
1690
1752
|
};
|
|
@@ -1785,8 +1847,8 @@ function registerVideoTool(server, budget) {
|
|
|
1785
1847
|
Turns a text prompt (and optional seed image) into a short MP4 clip. The tool submits the job, then polls until the video is ready (typical total wall-time 60-180s; 5 min hard cap). Payment is settled only when upstream returns a finished video \u2014 if the job fails or we give up, you are not charged.
|
|
1786
1848
|
|
|
1787
1849
|
Models (Seedance defaults bumped to 720p + synced audio on the gateway):
|
|
1788
|
-
- azure/sora-2 ($0.10/sec, 720p + synced audio, text-to-video) \u2014 OpenAI Sora 2 via Azure AI Foundry. duration_seconds must be 4, 8, or 12 (4s default -> ~$0.
|
|
1789
|
-
- xai/grok-imagine-video ($0.05/sec, 8s default -> $0.
|
|
1850
|
+
- azure/sora-2 ($0.10/sec, 720p + synced audio, text-to-video) \u2014 OpenAI Sora 2 via Azure AI Foundry. duration_seconds must be 4, 8, or 12 (4s default -> ~$0.40/clip). No image_url / RealFace.
|
|
1851
|
+
- xai/grok-imagine-video ($0.05/sec, 8s default -> $0.40/clip) \u2014 stylized, fast
|
|
1790
1852
|
- bytedance/seedance-1.5-pro (~$0.092/sec, 720p + audio t2v, 5s default up to 10s) \u2014 cheapest Seedance, token-priced upstream
|
|
1791
1853
|
- bytedance/seedance-2.0-fast (~$0.238/sec text \xB7 ~$0.140/sec image-to-video, 720p + audio, ~60-80s gen) \u2014 sweet-spot price/quality; supports BytePlus RealFace assets
|
|
1792
1854
|
- bytedance/seedance-2.0 (~$0.298/sec text \xB7 ~$0.183/sec image-to-video, 720p + audio Pro) \u2014 highest quality; supports BytePlus RealFace assets
|
|
@@ -1799,11 +1861,15 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1799
1861
|
image_url: z7.string().url().optional().describe("Optional seed image URL for image-to-video generation"),
|
|
1800
1862
|
real_face_asset_id: z7.string().regex(/^ta_[A-Za-z0-9]+$/, "token360 asset id like 'ta_xxxx'").optional().describe("BytePlus RealFace asset id (from blockrun_realface enroll/list) to generate video of a specific real person. Seedance 2.0 / 2.0-fast only. Mutually exclusive with image_url."),
|
|
1801
1863
|
duration_seconds: z7.number().int().min(1).max(60).optional().describe("Duration to bill for (defaults to the model's default \u2014 8s for xAI, 5s for Seedance; Seedance supports up to 10s)."),
|
|
1864
|
+
generate_audio: z7.boolean().optional().describe("Seedance only: whether to generate a synced audio track. Defaults ON for text-to-video and OFF for image/RealFace-conditioned. The auto-generated audio is occasionally rejected by upstream moderation ('output audio may contain sensitive information') even for benign prompts \u2014 pass false to skip audio and avoid that failure. Ignored by xAI/Sora."),
|
|
1865
|
+
resolution: z7.enum(["360p", "480p", "540p", "720p", "1080p", "1K", "2K", "4K"]).optional().describe("Seedance only: output resolution. Defaults to 720p. Higher resolutions cost more (token-priced upstream) \u2014 the final price is set by the 402 challenge, so the up-front estimate may understate 1080p/4K. Ignored by xAI/Sora."),
|
|
1866
|
+
aspect_ratio: z7.enum(["adaptive", "16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "9:21"]).optional().describe("Seedance only: output aspect ratio, e.g. '9:16' for vertical/mobile, '16:9' for landscape. Defaults to the model's own default. Ignored by xAI/Sora."),
|
|
1867
|
+
last_frame_url: z7.string().url().optional().describe("Seedance only: first-and-last-frame interpolation. A second image URL that seeds the FINAL frame so the model tweens from image_url (first frame) \u2192 last_frame_url (last frame). Requires image_url; mutually exclusive with real_face_asset_id. Priced as image-to-video."),
|
|
1802
1868
|
model: z7.enum(["azure/sora-2", "xai/grok-imagine-video", "bytedance/seedance-1.5-pro", "bytedance/seedance-2.0-fast", "bytedance/seedance-2.0"]).optional().default("xai/grok-imagine-video").describe("Video model to use"),
|
|
1803
1869
|
agent_id: z7.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1804
1870
|
}
|
|
1805
1871
|
},
|
|
1806
|
-
async ({ prompt, image_url, real_face_asset_id, duration_seconds, model, agent_id }) => {
|
|
1872
|
+
async ({ prompt, image_url, real_face_asset_id, duration_seconds, generate_audio, resolution, aspect_ratio, last_frame_url, model, agent_id }) => {
|
|
1807
1873
|
try {
|
|
1808
1874
|
if (getChain() !== "base") {
|
|
1809
1875
|
return {
|
|
@@ -1826,6 +1892,20 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1826
1892
|
};
|
|
1827
1893
|
}
|
|
1828
1894
|
}
|
|
1895
|
+
if (last_frame_url) {
|
|
1896
|
+
if (!image_url) {
|
|
1897
|
+
return {
|
|
1898
|
+
content: [{ type: "text", text: formatError("last_frame_url (first-and-last-frame interpolation) requires image_url as the first frame.") }],
|
|
1899
|
+
isError: true
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
if (real_face_asset_id) {
|
|
1903
|
+
return {
|
|
1904
|
+
content: [{ type: "text", text: formatError("last_frame_url cannot be combined with real_face_asset_id.") }],
|
|
1905
|
+
isError: true
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1829
1909
|
const billedSeconds = duration_seconds ?? VIDEO_DEFAULT_DURATION[selectedModel] ?? 8;
|
|
1830
1910
|
const hasImageInput = Boolean(image_url || real_face_asset_id);
|
|
1831
1911
|
const perSecond = (hasImageInput ? VIDEO_PRICE_PER_SECOND_IMAGE[selectedModel] : void 0) ?? VIDEO_PRICE_PER_SECOND[selectedModel] ?? 0.05;
|
|
@@ -1844,6 +1924,10 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1844
1924
|
if (image_url) body.image_url = image_url;
|
|
1845
1925
|
if (real_face_asset_id) body.real_face_asset_id = real_face_asset_id;
|
|
1846
1926
|
if (duration_seconds !== void 0) body.duration_seconds = duration_seconds;
|
|
1927
|
+
if (generate_audio !== void 0) body.generate_audio = generate_audio;
|
|
1928
|
+
if (resolution !== void 0) body.resolution = resolution;
|
|
1929
|
+
if (aspect_ratio !== void 0) body.aspect_ratio = aspect_ratio;
|
|
1930
|
+
if (last_frame_url) body.last_frame_url = last_frame_url;
|
|
1847
1931
|
const resp402 = await fetchWithTimeout(submitUrl, {
|
|
1848
1932
|
method: "POST",
|
|
1849
1933
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1857,6 +1941,7 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1857
1941
|
if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
|
|
1858
1942
|
const paymentRequired = parsePaymentRequired3(prHeader);
|
|
1859
1943
|
const details = extractPaymentDetails3(paymentRequired);
|
|
1944
|
+
const settledUsd = amountToUsd(details.amount);
|
|
1860
1945
|
const paymentPayload = await createPaymentPayload3(
|
|
1861
1946
|
privateKey,
|
|
1862
1947
|
account.address,
|
|
@@ -1939,7 +2024,7 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1939
2024
|
...completed.request_id ? [`Request ID: ${completed.request_id}`] : [],
|
|
1940
2025
|
...completed.txHash ? [`Tx: ${completed.txHash}`] : []
|
|
1941
2026
|
];
|
|
1942
|
-
|
|
2027
|
+
recordActualSpend(budget, settledUsd, estimatedCost, agent_id);
|
|
1943
2028
|
return {
|
|
1944
2029
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
1945
2030
|
structuredContent: {
|
|
@@ -2025,7 +2110,7 @@ async function payAndPostJson(url, reqBody, fallbackDescription) {
|
|
|
2025
2110
|
body: reqBody
|
|
2026
2111
|
}, 9e4);
|
|
2027
2112
|
const data = await resp.json().catch(() => ({}));
|
|
2028
|
-
return { status: resp.status, data };
|
|
2113
|
+
return { status: resp.status, data, settledUsd: amountToUsd(details.amount) };
|
|
2029
2114
|
}
|
|
2030
2115
|
function registerRealfaceTool(server, budget) {
|
|
2031
2116
|
server.registerTool(
|
|
@@ -2188,7 +2273,7 @@ Enroll one: blockrun_realface action:"init" name:"\u2026" (real person) or actio
|
|
|
2188
2273
|
if (!budgetCheck.allowed) {
|
|
2189
2274
|
return { content: [{ type: "text", text: `${budgetCheck.reason}. Use blockrun_wallet action:"report" to see usage or action:"delegate" to increase agent budget.` }], isError: true };
|
|
2190
2275
|
}
|
|
2191
|
-
const { status, data } = await payAndPostJson(
|
|
2276
|
+
const { status, data, settledUsd } = await payAndPostJson(
|
|
2192
2277
|
`${BLOCKRUN_API4}/v1/portrait/enroll`,
|
|
2193
2278
|
JSON.stringify({ name, image_url }),
|
|
2194
2279
|
"BlockRun Virtual Portrait enrollment"
|
|
@@ -2204,7 +2289,7 @@ Enroll one: blockrun_realface action:"init" name:"\u2026" (real person) or actio
|
|
|
2204
2289
|
}
|
|
2205
2290
|
const assetId = data.asset_id;
|
|
2206
2291
|
if (!assetId) throw new Error(`Portrait response missing asset_id: ${JSON.stringify(data)}`);
|
|
2207
|
-
|
|
2292
|
+
recordActualSpend(budget, settledUsd, ENROLLMENT_PRICE_USD, agent_id);
|
|
2208
2293
|
const txHash = data.settlement?.tx_hash || void 0;
|
|
2209
2294
|
const lines = [
|
|
2210
2295
|
`\u2705 Virtual Portrait enrolled!`,
|
|
@@ -2255,6 +2340,7 @@ Enroll one: blockrun_realface action:"init" name:"\u2026" (real person) or actio
|
|
|
2255
2340
|
if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
|
|
2256
2341
|
const paymentRequired = parsePaymentRequired4(prHeader);
|
|
2257
2342
|
const details = extractPaymentDetails4(paymentRequired);
|
|
2343
|
+
const settledUsd = amountToUsd(details.amount);
|
|
2258
2344
|
const paymentPayload = await createPaymentPayload4(
|
|
2259
2345
|
privateKey,
|
|
2260
2346
|
account.address,
|
|
@@ -2291,7 +2377,7 @@ Enroll one: blockrun_realface action:"init" name:"\u2026" (real person) or actio
|
|
|
2291
2377
|
}
|
|
2292
2378
|
const assetId = data.asset_id;
|
|
2293
2379
|
if (!assetId) throw new Error(`Enroll response missing asset_id: ${JSON.stringify(data)}`);
|
|
2294
|
-
|
|
2380
|
+
recordActualSpend(budget, settledUsd, ENROLLMENT_PRICE_USD, agent_id);
|
|
2295
2381
|
const txHash = data.settlement?.tx_hash || void 0;
|
|
2296
2382
|
const lines = [
|
|
2297
2383
|
`\u2705 RealFace enrolled!`,
|
|
@@ -2336,13 +2422,16 @@ import { z as z9 } from "zod";
|
|
|
2336
2422
|
function coerceBody(body) {
|
|
2337
2423
|
if (typeof body !== "string") return body;
|
|
2338
2424
|
const trimmed = body.trim();
|
|
2339
|
-
if (trimmed === "") return
|
|
2425
|
+
if (trimmed === "") return void 0;
|
|
2340
2426
|
try {
|
|
2341
2427
|
return JSON.parse(trimmed);
|
|
2342
2428
|
} catch {
|
|
2343
2429
|
return body;
|
|
2344
2430
|
}
|
|
2345
2431
|
}
|
|
2432
|
+
function asStructuredContent(result) {
|
|
2433
|
+
return typeof result === "object" && result !== null && !Array.isArray(result) ? result : { result };
|
|
2434
|
+
}
|
|
2346
2435
|
|
|
2347
2436
|
// src/tools/search.ts
|
|
2348
2437
|
var SEARCH_PRICE_PER_SOURCE = 0.025;
|
|
@@ -2389,7 +2478,7 @@ Full request shape + worked examples in the \`search\` skill (\`skills/search/SK
|
|
|
2389
2478
|
recordSpending(budget, estimatedCost, agent_id);
|
|
2390
2479
|
return {
|
|
2391
2480
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2392
|
-
structuredContent: result
|
|
2481
|
+
structuredContent: asStructuredContent(result)
|
|
2393
2482
|
};
|
|
2394
2483
|
} catch (err) {
|
|
2395
2484
|
return { content: [{ type: "text", text: formatError(extractErrorMessage(err)) }], isError: true };
|
|
@@ -2447,7 +2536,7 @@ Full request/response shapes + worked research workflows in the \`exa-research\`
|
|
|
2447
2536
|
recordSpending(budget, estimatedCost, agent_id);
|
|
2448
2537
|
return {
|
|
2449
2538
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2450
|
-
structuredContent: result
|
|
2539
|
+
structuredContent: asStructuredContent(result)
|
|
2451
2540
|
};
|
|
2452
2541
|
} catch (err) {
|
|
2453
2542
|
return { content: [{ type: "text", text: formatError(extractErrorMessage(err)) }], isError: true };
|
|
@@ -2544,12 +2633,11 @@ Pass query params via 'params' (GET). Use 'body' only for POST endpoints (e.g. p
|
|
|
2544
2633
|
recordSpending(budget, estimatedCost, agent_id);
|
|
2545
2634
|
return {
|
|
2546
2635
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2547
|
-
structuredContent: result
|
|
2636
|
+
structuredContent: asStructuredContent(result)
|
|
2548
2637
|
};
|
|
2549
2638
|
} catch (err) {
|
|
2550
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2551
2639
|
return {
|
|
2552
|
-
content: [{ type: "text", text: formatError(
|
|
2640
|
+
content: [{ type: "text", text: formatError(extractErrorMessage(err)) }],
|
|
2553
2641
|
isError: true
|
|
2554
2642
|
};
|
|
2555
2643
|
}
|
|
@@ -2674,9 +2762,8 @@ Examples:
|
|
|
2674
2762
|
structuredContent: result
|
|
2675
2763
|
};
|
|
2676
2764
|
} catch (err) {
|
|
2677
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2678
2765
|
return {
|
|
2679
|
-
content: [{ type: "text", text: formatError(
|
|
2766
|
+
content: [{ type: "text", text: formatError(extractErrorMessage(err)) }],
|
|
2680
2767
|
isError: true
|
|
2681
2768
|
};
|
|
2682
2769
|
}
|
|
@@ -2809,7 +2896,7 @@ Full action shapes + GPU type details in the \`modal\` skill.`,
|
|
|
2809
2896
|
recordSpending(budget, estimatedCost, agent_id);
|
|
2810
2897
|
return {
|
|
2811
2898
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2812
|
-
structuredContent: result
|
|
2899
|
+
structuredContent: asStructuredContent(result)
|
|
2813
2900
|
};
|
|
2814
2901
|
} catch (err) {
|
|
2815
2902
|
return { content: [{ type: "text", text: formatError(extractErrorMessage(err)) }], isError: true };
|
|
@@ -2875,7 +2962,7 @@ Voice call flow + voice preset details + full body shapes in the \`phone\` skill
|
|
|
2875
2962
|
if (estimatedCost > 0) recordSpending(budget, estimatedCost, agent_id);
|
|
2876
2963
|
return {
|
|
2877
2964
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2878
|
-
structuredContent: result
|
|
2965
|
+
structuredContent: asStructuredContent(result)
|
|
2879
2966
|
};
|
|
2880
2967
|
} catch (err) {
|
|
2881
2968
|
return { content: [{ type: "text", text: formatError(extractErrorMessage(err)) }], isError: true };
|
|
@@ -2973,7 +3060,7 @@ Each Surf endpoint pre-validates required params before settling \u2014 you get
|
|
|
2973
3060
|
recordSpending(budget, estimatedCost, agent_id);
|
|
2974
3061
|
return {
|
|
2975
3062
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2976
|
-
structuredContent: result
|
|
3063
|
+
structuredContent: asStructuredContent(result)
|
|
2977
3064
|
};
|
|
2978
3065
|
} catch (err) {
|
|
2979
3066
|
return {
|
|
@@ -3170,7 +3257,13 @@ function resolveTools(argv, env) {
|
|
|
3170
3257
|
|
|
3171
3258
|
// src/mcp-handler.ts
|
|
3172
3259
|
function initializeMcpServer(server, profileArgs) {
|
|
3173
|
-
const
|
|
3260
|
+
const env = profileArgs?.env ?? process.env;
|
|
3261
|
+
const budget = {
|
|
3262
|
+
limit: parseBudgetLimitEnv(env.BLOCKRUN_BUDGET_LIMIT),
|
|
3263
|
+
spent: 0,
|
|
3264
|
+
calls: 0,
|
|
3265
|
+
agents: /* @__PURE__ */ new Map()
|
|
3266
|
+
};
|
|
3174
3267
|
const modelCache = { models: null };
|
|
3175
3268
|
const { profile, tools } = resolveTools(profileArgs?.argv, profileArgs?.env);
|
|
3176
3269
|
const registrars = {
|
|
@@ -3243,9 +3336,15 @@ function looksLikeRawPrivateKey(value) {
|
|
|
3243
3336
|
if (value.length >= 80 && value.length <= 100 && /^[1-9A-HJ-NP-Za-km-z]+$/.test(value)) return true;
|
|
3244
3337
|
return false;
|
|
3245
3338
|
}
|
|
3339
|
+
function looksLikeSolanaSecretKeyArray(value) {
|
|
3340
|
+
return Array.isArray(value) && value.length === 64 && value.every((n) => typeof n === "number" && Number.isInteger(n) && n >= 0 && n <= 255);
|
|
3341
|
+
}
|
|
3246
3342
|
function walk(obj, file, jsonPath, out) {
|
|
3247
3343
|
if (obj === null || typeof obj !== "object") return;
|
|
3248
3344
|
if (Array.isArray(obj)) {
|
|
3345
|
+
if (looksLikeSolanaSecretKeyArray(obj)) {
|
|
3346
|
+
out.push({ file, path: jsonPath || "(root)" });
|
|
3347
|
+
}
|
|
3249
3348
|
obj.forEach((v, i) => walk(v, file, `${jsonPath}[${i}]`, out));
|
|
3250
3349
|
return;
|
|
3251
3350
|
}
|