@blockrun/mcp 0.22.3 → 0.23.1
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 +247 -131
- 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
|
@@ -100,9 +100,10 @@ function getOrCreateWalletKey() {
|
|
|
100
100
|
const info = ensureEvmWallet();
|
|
101
101
|
return info.privateKey;
|
|
102
102
|
}
|
|
103
|
-
function buildSolanaClient() {
|
|
103
|
+
function buildSolanaClient(timeout) {
|
|
104
104
|
const privateKey = process.env.SOLANA_WALLET_KEY || loadSolanaWallet() || void 0;
|
|
105
|
-
|
|
105
|
+
const opts = { ...privateKey ? { privateKey } : {}, ...timeout ? { timeout } : {} };
|
|
106
|
+
return new SolanaLLMClient(Object.keys(opts).length ? opts : void 0);
|
|
106
107
|
}
|
|
107
108
|
function getClient() {
|
|
108
109
|
if (getChain() === "solana") {
|
|
@@ -117,6 +118,13 @@ function getClient() {
|
|
|
117
118
|
}
|
|
118
119
|
return _evmClient;
|
|
119
120
|
}
|
|
121
|
+
function buildClientWithTimeout(timeoutMs) {
|
|
122
|
+
if (getChain() === "solana") {
|
|
123
|
+
return buildSolanaClient(timeoutMs);
|
|
124
|
+
}
|
|
125
|
+
const privateKey = getOrCreateWalletKey();
|
|
126
|
+
return new LLMClient({ privateKey, timeout: timeoutMs });
|
|
127
|
+
}
|
|
120
128
|
function getAnthropicClient() {
|
|
121
129
|
if (!_anthropicClient) {
|
|
122
130
|
const privateKey = getOrCreateWalletKey();
|
|
@@ -221,6 +229,58 @@ async function loadModels(llm, cache) {
|
|
|
221
229
|
return cache.models;
|
|
222
230
|
}
|
|
223
231
|
|
|
232
|
+
// src/utils/budget.ts
|
|
233
|
+
var EPSILON = 1e-9;
|
|
234
|
+
function formatUsd(amount) {
|
|
235
|
+
return `$${amount.toFixed(amount >= 1 ? 2 : 4)}`;
|
|
236
|
+
}
|
|
237
|
+
function checkBudget(budget, agentId, estimatedCost = 1e-3) {
|
|
238
|
+
const cost = Math.max(0, estimatedCost);
|
|
239
|
+
if (cost > 0 && budget.limit !== null && budget.spent + cost > budget.limit + EPSILON) {
|
|
240
|
+
const remaining = Math.max(0, budget.limit - budget.spent);
|
|
241
|
+
return {
|
|
242
|
+
allowed: false,
|
|
243
|
+
reason: `Global budget limit ${formatUsd(budget.limit)} would be exceeded (${formatUsd(budget.spent)} spent, ${formatUsd(remaining)} remaining, next call estimated ${formatUsd(cost)})`
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
if (agentId) {
|
|
247
|
+
const agentBudget = budget.agents.get(agentId);
|
|
248
|
+
if (cost > 0 && agentBudget && agentBudget.spent + cost > agentBudget.limit + EPSILON) {
|
|
249
|
+
const remaining = Math.max(0, agentBudget.limit - agentBudget.spent);
|
|
250
|
+
return {
|
|
251
|
+
allowed: false,
|
|
252
|
+
reason: `Agent "${agentId}" budget ${formatUsd(agentBudget.limit)} would be exceeded (${formatUsd(agentBudget.spent)} spent, ${formatUsd(remaining)} remaining, next call estimated ${formatUsd(cost)})`
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { allowed: true };
|
|
257
|
+
}
|
|
258
|
+
function recordSpending(budget, cost, agentId) {
|
|
259
|
+
budget.spent += cost;
|
|
260
|
+
budget.calls += 1;
|
|
261
|
+
if (agentId) {
|
|
262
|
+
const agentBudget = budget.agents.get(agentId);
|
|
263
|
+
if (agentBudget) {
|
|
264
|
+
agentBudget.spent += cost;
|
|
265
|
+
agentBudget.calls += 1;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function amountToUsd(amount) {
|
|
270
|
+
const n = typeof amount === "string" ? Number(amount) : typeof amount === "number" ? amount : NaN;
|
|
271
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
272
|
+
return n / 1e6;
|
|
273
|
+
}
|
|
274
|
+
function recordActualSpend(budget, actualUsd, estimate, agentId) {
|
|
275
|
+
const cost = typeof actualUsd === "number" && Number.isFinite(actualUsd) && actualUsd > 0 ? actualUsd : Math.max(0, estimate);
|
|
276
|
+
recordSpending(budget, cost, agentId);
|
|
277
|
+
}
|
|
278
|
+
function parseBudgetLimitEnv(raw) {
|
|
279
|
+
if (!raw) return null;
|
|
280
|
+
const n = Number(raw.trim().replace(/^\$/, ""));
|
|
281
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
282
|
+
}
|
|
283
|
+
|
|
224
284
|
// src/tools/wallet.ts
|
|
225
285
|
import { z } from "zod";
|
|
226
286
|
|
|
@@ -229,7 +289,6 @@ import QRCode from "qrcode";
|
|
|
229
289
|
import open from "open";
|
|
230
290
|
import * as fs2 from "fs";
|
|
231
291
|
import * as path3 from "path";
|
|
232
|
-
import sharp from "sharp";
|
|
233
292
|
|
|
234
293
|
// src/utils/constants.ts
|
|
235
294
|
import * as path2 from "path";
|
|
@@ -255,6 +314,17 @@ var MODEL_TIERS = {
|
|
|
255
314
|
|
|
256
315
|
// src/utils/qr.ts
|
|
257
316
|
var SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
317
|
+
var sharpModule;
|
|
318
|
+
async function loadSharp() {
|
|
319
|
+
if (sharpModule !== void 0) return sharpModule;
|
|
320
|
+
try {
|
|
321
|
+
const mod = await import("sharp");
|
|
322
|
+
sharpModule = mod.default;
|
|
323
|
+
} catch {
|
|
324
|
+
sharpModule = null;
|
|
325
|
+
}
|
|
326
|
+
return sharpModule;
|
|
327
|
+
}
|
|
258
328
|
function getEip681Uri(address, amountUsdc = 1) {
|
|
259
329
|
const amountWei = Math.floor(amountUsdc * 1e6);
|
|
260
330
|
return `ethereum:${USDC_ADDRESS}@${BASE_CHAIN_ID}/transfer?address=${address}&uint256=${amountWei}`;
|
|
@@ -279,6 +349,8 @@ function buildSolanaLogoSvg(size) {
|
|
|
279
349
|
}
|
|
280
350
|
async function overlayLogo(qrBuf, chain, qrSize) {
|
|
281
351
|
if (chain !== "solana") return qrBuf;
|
|
352
|
+
const sharp = await loadSharp();
|
|
353
|
+
if (!sharp) return qrBuf;
|
|
282
354
|
const logoSize = Math.round(qrSize * 0.18);
|
|
283
355
|
const pad = Math.round(logoSize * 0.08);
|
|
284
356
|
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 +466,9 @@ To pay on Solana (no env vars, no file editing, no restart):
|
|
|
394
466
|
1. action:"chain" chain:"solana" \u2192 provisions + activates the Solana wallet
|
|
395
467
|
2. action:"setup" \u2192 Solana address + funding QR (send USDC SPL on Solana)
|
|
396
468
|
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
|
|
469
|
+
need Base: blockrun_image, blockrun_music, blockrun_speech, blockrun_video,
|
|
470
|
+
blockrun_realface, paid blockrun_price, blockrun_chat routing:"smart", and native
|
|
471
|
+
Anthropic (claude-*).
|
|
399
472
|
|
|
400
473
|
Actions:
|
|
401
474
|
- status (default): Both wallet addresses + USDC balances, active chain, session spending
|
|
@@ -671,45 +744,24 @@ Paying on ${chain} | View active: ${info.explorerUrl}${info.isNew ? "\nNEW WALLE
|
|
|
671
744
|
import { z as z2 } from "zod";
|
|
672
745
|
import { LLMClient as LLMClient2 } from "@blockrun/llm";
|
|
673
746
|
|
|
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
|
-
}
|
|
747
|
+
// src/tools/chat-anthropic.ts
|
|
748
|
+
function anthropicCallCost(model, usage) {
|
|
749
|
+
if (!usage) return null;
|
|
750
|
+
const id = model.toLowerCase();
|
|
751
|
+
let inRate = 5, outRate = 25;
|
|
752
|
+
if (id.includes("opus")) {
|
|
753
|
+
inRate = 15;
|
|
754
|
+
outRate = 75;
|
|
755
|
+
} else if (id.includes("haiku")) {
|
|
756
|
+
inRate = 1;
|
|
757
|
+
outRate = 5;
|
|
758
|
+
} else if (id.includes("sonnet")) {
|
|
759
|
+
inRate = 3;
|
|
760
|
+
outRate = 15;
|
|
709
761
|
}
|
|
762
|
+
const cost = (usage.input_tokens ?? 0) / 1e6 * inRate + (usage.output_tokens ?? 0) / 1e6 * outRate;
|
|
763
|
+
return cost > 0 ? cost : null;
|
|
710
764
|
}
|
|
711
|
-
|
|
712
|
-
// src/tools/chat-anthropic.ts
|
|
713
765
|
function isAnthropicModel(model) {
|
|
714
766
|
const id = model.trim();
|
|
715
767
|
return /^anthropic\//i.test(id) || /^claude-/i.test(id);
|
|
@@ -797,10 +849,9 @@ async function handleAnthropicNative(args) {
|
|
|
797
849
|
try {
|
|
798
850
|
native = await client.messages.create(params);
|
|
799
851
|
} catch (error) {
|
|
800
|
-
|
|
801
|
-
return { content: [{ type: "text", text: formatError(errorMessage) }], isError: true };
|
|
852
|
+
return { content: [{ type: "text", text: formatError(extractErrorMessage(error)) }], isError: true };
|
|
802
853
|
}
|
|
803
|
-
|
|
854
|
+
recordActualSpend(budget, anthropicCallCost(native.model, native.usage), estimatedCost, agentId);
|
|
804
855
|
const thinkingBlocks = native.content.filter(isThinkingBlock);
|
|
805
856
|
const textBlocks = native.content.filter(isTextBlock);
|
|
806
857
|
const answerText = textBlocks.map((b) => b.text).join("\n");
|
|
@@ -838,23 +889,32 @@ ${thinkingText}` });
|
|
|
838
889
|
}
|
|
839
890
|
|
|
840
891
|
// src/tools/chat.ts
|
|
841
|
-
function estimateChatCost(mode, model, routing, routingProfile) {
|
|
892
|
+
function estimateChatCost(maxTokens, mode, model, routing, routingProfile) {
|
|
842
893
|
if (mode === "free") return 0;
|
|
843
894
|
if (model?.startsWith("nvidia/")) return 0;
|
|
844
895
|
if (routing === "smart" && routingProfile === "free") return 0;
|
|
896
|
+
const out = Math.max(maxTokens ?? 1024, 256);
|
|
897
|
+
const frontierReserve = Math.max(0.01, out / 1e6 * 20);
|
|
845
898
|
if (routing === "smart") {
|
|
846
899
|
switch (routingProfile) {
|
|
847
900
|
case "eco":
|
|
848
|
-
return
|
|
901
|
+
return 0.01;
|
|
849
902
|
case "premium":
|
|
850
|
-
return
|
|
903
|
+
return frontierReserve;
|
|
851
904
|
case "auto":
|
|
852
905
|
default:
|
|
853
|
-
return 0.01;
|
|
906
|
+
return Math.max(0.01, frontierReserve * 0.5);
|
|
854
907
|
}
|
|
855
908
|
}
|
|
856
|
-
if (mode === "reasoning" || mode === "powerful") return
|
|
857
|
-
return
|
|
909
|
+
if (mode === "reasoning" || mode === "powerful") return frontierReserve;
|
|
910
|
+
if (model) return frontierReserve;
|
|
911
|
+
return Math.max(2e-3, out / 1e6 * 3);
|
|
912
|
+
}
|
|
913
|
+
async function withSettledCost(client, run) {
|
|
914
|
+
const before = client.getSpending().totalUsd;
|
|
915
|
+
const result = await run();
|
|
916
|
+
const settledUsd = client.getSpending().totalUsd - before;
|
|
917
|
+
return { result, settledUsd };
|
|
858
918
|
}
|
|
859
919
|
function registerChatTool(server, budget) {
|
|
860
920
|
server.registerTool(
|
|
@@ -866,13 +926,13 @@ Notable modes:
|
|
|
866
926
|
- mode:"glm" \u2192 Zhipu GLM-5 / GLM-5-Turbo ($0.001/call, excellent for coding tasks, pays via USDC on BlockRun)
|
|
867
927
|
- mode:"coding" \u2192 GLM-5 first, then code-specialized models
|
|
868
928
|
- mode:"cheap" \u2192 GLM-5, NVIDIA free, DeepSeek
|
|
869
|
-
- mode:"reasoning" \u2192 o3, o1,
|
|
929
|
+
- mode:"reasoning" \u2192 Claude Opus, o3, o1, deepseek-reasoner
|
|
870
930
|
- mode:"free" \u2192 NVIDIA models (no cost)
|
|
871
931
|
- routing:"smart" \u2192 auto-select via ClawRouter
|
|
872
932
|
|
|
873
933
|
Pick directly: model:"zai/glm-5", model:"openai/o3", model:"nvidia/deepseek-v4-flash" (free).
|
|
874
934
|
|
|
875
|
-
Run blockrun_models to see all
|
|
935
|
+
Run blockrun_models to see all available models with pricing.`,
|
|
876
936
|
inputSchema: {
|
|
877
937
|
message: z2.string().describe("Your message to the AI"),
|
|
878
938
|
model: z2.string().optional().describe("Specific model ID (e.g., 'zai/glm-5', 'openai/o3')"),
|
|
@@ -904,7 +964,7 @@ Run blockrun_models to see all 41+ models with pricing.`,
|
|
|
904
964
|
async ({ message, model, mode, routing, routing_profile, system, max_tokens, temperature, response_format, stop, thinking, agent_id, messages }) => {
|
|
905
965
|
const llm = getClient();
|
|
906
966
|
const responseFormat = response_format ? { type: response_format } : void 0;
|
|
907
|
-
const estimatedCost = estimateChatCost(mode, model, routing, routing_profile);
|
|
967
|
+
const estimatedCost = estimateChatCost(max_tokens, mode, model, routing, routing_profile);
|
|
908
968
|
const budgetCheck = checkBudget(budget, agent_id, estimatedCost);
|
|
909
969
|
if (!budgetCheck.allowed) {
|
|
910
970
|
return {
|
|
@@ -940,7 +1000,7 @@ Run blockrun_models to see all 41+ models with pricing.`,
|
|
|
940
1000
|
};
|
|
941
1001
|
}
|
|
942
1002
|
try {
|
|
943
|
-
const result = await llm.smartChat(message, {
|
|
1003
|
+
const { result, settledUsd } = await withSettledCost(llm, () => llm.smartChat(message, {
|
|
944
1004
|
system,
|
|
945
1005
|
maxTokens: max_tokens,
|
|
946
1006
|
maxOutputTokens: max_tokens,
|
|
@@ -951,8 +1011,8 @@ Run blockrun_models to see all 41+ models with pricing.`,
|
|
|
951
1011
|
routingProfile: routing_profile === "free" ? void 0 : routing_profile,
|
|
952
1012
|
responseFormat,
|
|
953
1013
|
stop
|
|
954
|
-
});
|
|
955
|
-
|
|
1014
|
+
}));
|
|
1015
|
+
recordActualSpend(budget, settledUsd, result.routing.costEstimate || estimatedCost, agent_id);
|
|
956
1016
|
return {
|
|
957
1017
|
content: [{ type: "text", text: `[${result.model} | ${result.routing.tier} | $${result.routing.costEstimate.toFixed(4)} | ${Math.round((result.routing.savings ?? 0) * 100)}% savings]
|
|
958
1018
|
|
|
@@ -964,8 +1024,7 @@ ${result.response}` }],
|
|
|
964
1024
|
}
|
|
965
1025
|
};
|
|
966
1026
|
} catch (error) {
|
|
967
|
-
|
|
968
|
-
return { content: [{ type: "text", text: formatError(errorMessage2) }], isError: true };
|
|
1027
|
+
return { content: [{ type: "text", text: formatError(extractErrorMessage(error)) }], isError: true };
|
|
969
1028
|
}
|
|
970
1029
|
}
|
|
971
1030
|
if (messages && messages.length > 0) {
|
|
@@ -976,14 +1035,14 @@ ${result.response}` }],
|
|
|
976
1035
|
{ role: "user", content: message }
|
|
977
1036
|
];
|
|
978
1037
|
try {
|
|
979
|
-
const result = await llm.chatCompletion(targetModel, fullMessages, {
|
|
1038
|
+
const { result, settledUsd } = await withSettledCost(llm, () => llm.chatCompletion(targetModel, fullMessages, {
|
|
980
1039
|
maxTokens: max_tokens,
|
|
981
1040
|
temperature,
|
|
982
1041
|
responseFormat,
|
|
983
1042
|
stop
|
|
984
|
-
});
|
|
1043
|
+
}));
|
|
985
1044
|
const reply = result.choices?.[0]?.message?.content || "";
|
|
986
|
-
|
|
1045
|
+
recordActualSpend(budget, settledUsd, estimatedCost, agent_id);
|
|
987
1046
|
return {
|
|
988
1047
|
content: [{ type: "text", text: `[${targetModel} | ${fullMessages.length} msgs]
|
|
989
1048
|
|
|
@@ -991,25 +1050,23 @@ ${reply}` }],
|
|
|
991
1050
|
structuredContent: { model_used: targetModel, response: reply, message_count: fullMessages.length }
|
|
992
1051
|
};
|
|
993
1052
|
} catch (error) {
|
|
994
|
-
|
|
995
|
-
return { content: [{ type: "text", text: formatError(errorMessage2) }], isError: true };
|
|
1053
|
+
return { content: [{ type: "text", text: formatError(extractErrorMessage(error)) }], isError: true };
|
|
996
1054
|
}
|
|
997
1055
|
}
|
|
998
1056
|
if (model) {
|
|
999
1057
|
try {
|
|
1000
|
-
const response = await llm.chat(model, message, {
|
|
1058
|
+
const { result: response, settledUsd } = await withSettledCost(llm, () => llm.chat(model, message, {
|
|
1001
1059
|
system,
|
|
1002
1060
|
maxTokens: max_tokens,
|
|
1003
1061
|
temperature,
|
|
1004
1062
|
responseFormat,
|
|
1005
1063
|
stop
|
|
1006
|
-
});
|
|
1007
|
-
|
|
1064
|
+
}));
|
|
1065
|
+
recordActualSpend(budget, settledUsd, estimatedCost, agent_id);
|
|
1008
1066
|
return { content: [{ type: "text", text: response }] };
|
|
1009
1067
|
} catch (error) {
|
|
1010
|
-
const errorMessage2 = error instanceof Error ? error.message : String(error);
|
|
1011
1068
|
return {
|
|
1012
|
-
content: [{ type: "text", text: formatError(
|
|
1069
|
+
content: [{ type: "text", text: formatError(extractErrorMessage(error)) }],
|
|
1013
1070
|
isError: true
|
|
1014
1071
|
};
|
|
1015
1072
|
}
|
|
@@ -1019,14 +1076,14 @@ ${reply}` }],
|
|
|
1019
1076
|
let lastError = null;
|
|
1020
1077
|
for (const m of models) {
|
|
1021
1078
|
try {
|
|
1022
|
-
const response = await llm.chat(m, message, {
|
|
1079
|
+
const { result: response, settledUsd } = await withSettledCost(llm, () => llm.chat(m, message, {
|
|
1023
1080
|
system,
|
|
1024
1081
|
maxTokens: max_tokens,
|
|
1025
1082
|
temperature,
|
|
1026
1083
|
responseFormat,
|
|
1027
1084
|
stop
|
|
1028
|
-
});
|
|
1029
|
-
|
|
1085
|
+
}));
|
|
1086
|
+
recordActualSpend(budget, settledUsd, estimatedCost, agent_id);
|
|
1030
1087
|
return {
|
|
1031
1088
|
content: [{ type: "text", text: `[${m}]
|
|
1032
1089
|
|
|
@@ -1038,7 +1095,7 @@ ${response}` }],
|
|
|
1038
1095
|
continue;
|
|
1039
1096
|
}
|
|
1040
1097
|
}
|
|
1041
|
-
const errorMessage = lastError
|
|
1098
|
+
const errorMessage = lastError ? extractErrorMessage(lastError) : "All models failed";
|
|
1042
1099
|
return {
|
|
1043
1100
|
content: [{ type: "text", text: formatError(errorMessage) }],
|
|
1044
1101
|
isError: true
|
|
@@ -1063,40 +1120,47 @@ function registerModelsTool(server, modelCache) {
|
|
|
1063
1120
|
}
|
|
1064
1121
|
},
|
|
1065
1122
|
async ({ category, provider }) => {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
if (category
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1123
|
+
try {
|
|
1124
|
+
let models = await loadModels(getClient(), modelCache);
|
|
1125
|
+
if (provider) {
|
|
1126
|
+
const p = provider.toLowerCase();
|
|
1127
|
+
models = models.filter((m) => m.id.toLowerCase().startsWith(p + "/"));
|
|
1128
|
+
}
|
|
1129
|
+
if (category && category !== "all") {
|
|
1130
|
+
if (category === "image") {
|
|
1131
|
+
models = models.filter((m) => getModelType(m) === "image");
|
|
1132
|
+
} else if (category === "embedding") {
|
|
1133
|
+
models = models.filter((m) => m.id.includes("embed"));
|
|
1134
|
+
} else {
|
|
1135
|
+
models = models.filter((m) => "categories" in m && m.categories?.includes(category));
|
|
1136
|
+
}
|
|
1078
1137
|
}
|
|
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}):
|
|
1138
|
+
const lines = models.map((m) => {
|
|
1139
|
+
if (getModelType(m) === "image") {
|
|
1140
|
+
const image = m;
|
|
1141
|
+
const pricing2 = image.pricePerImage ? `$${image.pricePerImage}/image` : "";
|
|
1142
|
+
const sizes = image.supportedSizes?.length ? ` | sizes: ${image.supportedSizes.join(", ")}` : "";
|
|
1143
|
+
return `- ${image.id}${pricing2 ? ` (${pricing2})` : ""}${sizes} [image]`;
|
|
1144
|
+
}
|
|
1145
|
+
const llmModel = m;
|
|
1146
|
+
const input = llmModel.inputPrice ? `$${llmModel.inputPrice}/M in` : "";
|
|
1147
|
+
const output = llmModel.outputPrice ? `$${llmModel.outputPrice}/M out` : "";
|
|
1148
|
+
const pricing = [input, output].filter(Boolean).join(", ");
|
|
1149
|
+
const ctx = llmModel.contextWindow ? ` | ${Math.round(llmModel.contextWindow / 1e3)}K ctx` : "";
|
|
1150
|
+
const cats = llmModel.categories?.length ? ` [${llmModel.categories.join(", ")}]` : "";
|
|
1151
|
+
return `- ${llmModel.id}${pricing ? ` (${pricing})` : ""}${ctx}${cats}`;
|
|
1152
|
+
});
|
|
1153
|
+
return {
|
|
1154
|
+
content: [{ type: "text", text: `Models (${models.length}):
|
|
1097
1155
|
${lines.join("\n")}` }],
|
|
1098
|
-
|
|
1099
|
-
|
|
1156
|
+
structuredContent: { count: models.length, models }
|
|
1157
|
+
};
|
|
1158
|
+
} catch (err) {
|
|
1159
|
+
return {
|
|
1160
|
+
content: [{ type: "text", text: formatError(extractErrorMessage(err)) }],
|
|
1161
|
+
isError: true
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1100
1164
|
}
|
|
1101
1165
|
);
|
|
1102
1166
|
}
|
|
@@ -1176,9 +1240,14 @@ var IMAGE_MODELS = [
|
|
|
1176
1240
|
"xai/grok-imagine-image",
|
|
1177
1241
|
"xai/grok-imagine-image-pro"
|
|
1178
1242
|
];
|
|
1243
|
+
function isLargerThanBase(size) {
|
|
1244
|
+
const m = /^\s*(\d+)\s*[x×]\s*(\d+)\s*$/i.exec(size);
|
|
1245
|
+
if (!m) return false;
|
|
1246
|
+
return Math.max(Number(m[1]), Number(m[2])) > 1024;
|
|
1247
|
+
}
|
|
1179
1248
|
function estimateCost(model, size) {
|
|
1180
1249
|
const base = GENERATE_MODEL_COST[model] ?? 0.06;
|
|
1181
|
-
if (
|
|
1250
|
+
if (LARGE_SIZE_COST[model] && isLargerThanBase(size)) {
|
|
1182
1251
|
return LARGE_SIZE_COST[model];
|
|
1183
1252
|
}
|
|
1184
1253
|
return base;
|
|
@@ -1455,7 +1524,7 @@ Returns a time-limited CDN URL \u2014 download immediately if you need to keep t
|
|
|
1455
1524
|
const track = data.data?.[0];
|
|
1456
1525
|
if (!track?.url) throw new Error("No track URL in response");
|
|
1457
1526
|
const txHash = resp.headers.get("X-Payment-Receipt") || resp.headers.get("x-payment-receipt");
|
|
1458
|
-
|
|
1527
|
+
recordActualSpend(budget, amountToUsd(details.amount), MUSIC_COST, agent_id);
|
|
1459
1528
|
const lines = [
|
|
1460
1529
|
`\u{1F3B5} Track ready!`,
|
|
1461
1530
|
`URL: ${track.url}`,
|
|
@@ -1631,6 +1700,7 @@ Returns a hosted audio URL \u2014 download immediately if you need to keep the f
|
|
|
1631
1700
|
if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
|
|
1632
1701
|
const paymentRequired = parsePaymentRequired2(prHeader);
|
|
1633
1702
|
const details = extractPaymentDetails2(paymentRequired);
|
|
1703
|
+
const billedUsd = amountToUsd(details.amount) ?? cost;
|
|
1634
1704
|
const paymentPayload = await createPaymentPayload2(
|
|
1635
1705
|
privateKey,
|
|
1636
1706
|
account.address,
|
|
@@ -1663,7 +1733,7 @@ Returns a hosted audio URL \u2014 download immediately if you need to keep the f
|
|
|
1663
1733
|
const clip = data.data?.[0];
|
|
1664
1734
|
if (!clip?.url) throw new Error("No audio URL in response");
|
|
1665
1735
|
const txHash = resp.headers.get("X-Payment-Receipt") || resp.headers.get("x-payment-receipt");
|
|
1666
|
-
|
|
1736
|
+
recordActualSpend(budget, billedUsd, cost, agent_id);
|
|
1667
1737
|
const lines = [
|
|
1668
1738
|
action === "sound_effect" ? `\u{1F50A} Sound effect ready!` : `\u{1F5E3}\uFE0F Speech ready!`,
|
|
1669
1739
|
`URL: ${clip.url}`,
|
|
@@ -1671,7 +1741,7 @@ Returns a hosted audio URL \u2014 download immediately if you need to keep the f
|
|
|
1671
1741
|
...clip.characters !== void 0 ? [`Characters: ${clip.characters}`] : [],
|
|
1672
1742
|
...clip.duration_seconds !== void 0 ? [`Duration: ${clip.duration_seconds}s`] : [],
|
|
1673
1743
|
`Model: ${data.model || (action === "sound_effect" ? "elevenlabs/sound-effects" : model)}`,
|
|
1674
|
-
`Cost: $${
|
|
1744
|
+
`Cost: $${billedUsd.toFixed(4)}`,
|
|
1675
1745
|
...txHash ? [`Tx: ${txHash}`] : [],
|
|
1676
1746
|
``,
|
|
1677
1747
|
`Note: This URL may expire \u2014 download it now if you need to keep the file.`
|
|
@@ -1684,7 +1754,7 @@ Returns a hosted audio URL \u2014 download immediately if you need to keep the f
|
|
|
1684
1754
|
...clip.characters !== void 0 ? { characters: clip.characters } : {},
|
|
1685
1755
|
...clip.duration_seconds !== void 0 ? { duration_seconds: clip.duration_seconds } : {},
|
|
1686
1756
|
model: data.model || (action === "sound_effect" ? "elevenlabs/sound-effects" : model),
|
|
1687
|
-
cost_usd:
|
|
1757
|
+
cost_usd: billedUsd,
|
|
1688
1758
|
...txHash ? { txHash } : {}
|
|
1689
1759
|
}
|
|
1690
1760
|
};
|
|
@@ -1785,8 +1855,8 @@ function registerVideoTool(server, budget) {
|
|
|
1785
1855
|
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
1856
|
|
|
1787
1857
|
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.
|
|
1858
|
+
- 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.
|
|
1859
|
+
- xai/grok-imagine-video ($0.05/sec, 8s default -> $0.40/clip) \u2014 stylized, fast
|
|
1790
1860
|
- bytedance/seedance-1.5-pro (~$0.092/sec, 720p + audio t2v, 5s default up to 10s) \u2014 cheapest Seedance, token-priced upstream
|
|
1791
1861
|
- 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
1862
|
- 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 +1869,15 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1799
1869
|
image_url: z7.string().url().optional().describe("Optional seed image URL for image-to-video generation"),
|
|
1800
1870
|
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
1871
|
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)."),
|
|
1872
|
+
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."),
|
|
1873
|
+
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."),
|
|
1874
|
+
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."),
|
|
1875
|
+
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
1876
|
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
1877
|
agent_id: z7.string().optional().describe("Agent identifier for budget tracking and enforcement.")
|
|
1804
1878
|
}
|
|
1805
1879
|
},
|
|
1806
|
-
async ({ prompt, image_url, real_face_asset_id, duration_seconds, model, agent_id }) => {
|
|
1880
|
+
async ({ prompt, image_url, real_face_asset_id, duration_seconds, generate_audio, resolution, aspect_ratio, last_frame_url, model, agent_id }) => {
|
|
1807
1881
|
try {
|
|
1808
1882
|
if (getChain() !== "base") {
|
|
1809
1883
|
return {
|
|
@@ -1826,6 +1900,20 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1826
1900
|
};
|
|
1827
1901
|
}
|
|
1828
1902
|
}
|
|
1903
|
+
if (last_frame_url) {
|
|
1904
|
+
if (!image_url) {
|
|
1905
|
+
return {
|
|
1906
|
+
content: [{ type: "text", text: formatError("last_frame_url (first-and-last-frame interpolation) requires image_url as the first frame.") }],
|
|
1907
|
+
isError: true
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
if (real_face_asset_id) {
|
|
1911
|
+
return {
|
|
1912
|
+
content: [{ type: "text", text: formatError("last_frame_url cannot be combined with real_face_asset_id.") }],
|
|
1913
|
+
isError: true
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1829
1917
|
const billedSeconds = duration_seconds ?? VIDEO_DEFAULT_DURATION[selectedModel] ?? 8;
|
|
1830
1918
|
const hasImageInput = Boolean(image_url || real_face_asset_id);
|
|
1831
1919
|
const perSecond = (hasImageInput ? VIDEO_PRICE_PER_SECOND_IMAGE[selectedModel] : void 0) ?? VIDEO_PRICE_PER_SECOND[selectedModel] ?? 0.05;
|
|
@@ -1844,6 +1932,10 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1844
1932
|
if (image_url) body.image_url = image_url;
|
|
1845
1933
|
if (real_face_asset_id) body.real_face_asset_id = real_face_asset_id;
|
|
1846
1934
|
if (duration_seconds !== void 0) body.duration_seconds = duration_seconds;
|
|
1935
|
+
if (generate_audio !== void 0) body.generate_audio = generate_audio;
|
|
1936
|
+
if (resolution !== void 0) body.resolution = resolution;
|
|
1937
|
+
if (aspect_ratio !== void 0) body.aspect_ratio = aspect_ratio;
|
|
1938
|
+
if (last_frame_url) body.last_frame_url = last_frame_url;
|
|
1847
1939
|
const resp402 = await fetchWithTimeout(submitUrl, {
|
|
1848
1940
|
method: "POST",
|
|
1849
1941
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1857,6 +1949,7 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1857
1949
|
if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
|
|
1858
1950
|
const paymentRequired = parsePaymentRequired3(prHeader);
|
|
1859
1951
|
const details = extractPaymentDetails3(paymentRequired);
|
|
1952
|
+
const settledUsd = amountToUsd(details.amount);
|
|
1860
1953
|
const paymentPayload = await createPaymentPayload3(
|
|
1861
1954
|
privateKey,
|
|
1862
1955
|
account.address,
|
|
@@ -1939,7 +2032,7 @@ Returns a permanent blockrun-hosted MP4 URL (the gateway mirrors the asset to GC
|
|
|
1939
2032
|
...completed.request_id ? [`Request ID: ${completed.request_id}`] : [],
|
|
1940
2033
|
...completed.txHash ? [`Tx: ${completed.txHash}`] : []
|
|
1941
2034
|
];
|
|
1942
|
-
|
|
2035
|
+
recordActualSpend(budget, settledUsd, estimatedCost, agent_id);
|
|
1943
2036
|
return {
|
|
1944
2037
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
1945
2038
|
structuredContent: {
|
|
@@ -2025,7 +2118,7 @@ async function payAndPostJson(url, reqBody, fallbackDescription) {
|
|
|
2025
2118
|
body: reqBody
|
|
2026
2119
|
}, 9e4);
|
|
2027
2120
|
const data = await resp.json().catch(() => ({}));
|
|
2028
|
-
return { status: resp.status, data };
|
|
2121
|
+
return { status: resp.status, data, settledUsd: amountToUsd(details.amount) };
|
|
2029
2122
|
}
|
|
2030
2123
|
function registerRealfaceTool(server, budget) {
|
|
2031
2124
|
server.registerTool(
|
|
@@ -2188,7 +2281,7 @@ Enroll one: blockrun_realface action:"init" name:"\u2026" (real person) or actio
|
|
|
2188
2281
|
if (!budgetCheck.allowed) {
|
|
2189
2282
|
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
2283
|
}
|
|
2191
|
-
const { status, data } = await payAndPostJson(
|
|
2284
|
+
const { status, data, settledUsd } = await payAndPostJson(
|
|
2192
2285
|
`${BLOCKRUN_API4}/v1/portrait/enroll`,
|
|
2193
2286
|
JSON.stringify({ name, image_url }),
|
|
2194
2287
|
"BlockRun Virtual Portrait enrollment"
|
|
@@ -2204,7 +2297,7 @@ Enroll one: blockrun_realface action:"init" name:"\u2026" (real person) or actio
|
|
|
2204
2297
|
}
|
|
2205
2298
|
const assetId = data.asset_id;
|
|
2206
2299
|
if (!assetId) throw new Error(`Portrait response missing asset_id: ${JSON.stringify(data)}`);
|
|
2207
|
-
|
|
2300
|
+
recordActualSpend(budget, settledUsd, ENROLLMENT_PRICE_USD, agent_id);
|
|
2208
2301
|
const txHash = data.settlement?.tx_hash || void 0;
|
|
2209
2302
|
const lines = [
|
|
2210
2303
|
`\u2705 Virtual Portrait enrolled!`,
|
|
@@ -2255,6 +2348,7 @@ Enroll one: blockrun_realface action:"init" name:"\u2026" (real person) or actio
|
|
|
2255
2348
|
if (!prHeader) throw new Error("No PAYMENT-REQUIRED header in 402 response");
|
|
2256
2349
|
const paymentRequired = parsePaymentRequired4(prHeader);
|
|
2257
2350
|
const details = extractPaymentDetails4(paymentRequired);
|
|
2351
|
+
const settledUsd = amountToUsd(details.amount);
|
|
2258
2352
|
const paymentPayload = await createPaymentPayload4(
|
|
2259
2353
|
privateKey,
|
|
2260
2354
|
account.address,
|
|
@@ -2291,7 +2385,7 @@ Enroll one: blockrun_realface action:"init" name:"\u2026" (real person) or actio
|
|
|
2291
2385
|
}
|
|
2292
2386
|
const assetId = data.asset_id;
|
|
2293
2387
|
if (!assetId) throw new Error(`Enroll response missing asset_id: ${JSON.stringify(data)}`);
|
|
2294
|
-
|
|
2388
|
+
recordActualSpend(budget, settledUsd, ENROLLMENT_PRICE_USD, agent_id);
|
|
2295
2389
|
const txHash = data.settlement?.tx_hash || void 0;
|
|
2296
2390
|
const lines = [
|
|
2297
2391
|
`\u2705 RealFace enrolled!`,
|
|
@@ -2336,13 +2430,16 @@ import { z as z9 } from "zod";
|
|
|
2336
2430
|
function coerceBody(body) {
|
|
2337
2431
|
if (typeof body !== "string") return body;
|
|
2338
2432
|
const trimmed = body.trim();
|
|
2339
|
-
if (trimmed === "") return
|
|
2433
|
+
if (trimmed === "") return void 0;
|
|
2340
2434
|
try {
|
|
2341
2435
|
return JSON.parse(trimmed);
|
|
2342
2436
|
} catch {
|
|
2343
2437
|
return body;
|
|
2344
2438
|
}
|
|
2345
2439
|
}
|
|
2440
|
+
function asStructuredContent(result) {
|
|
2441
|
+
return typeof result === "object" && result !== null && !Array.isArray(result) ? result : { result };
|
|
2442
|
+
}
|
|
2346
2443
|
|
|
2347
2444
|
// src/tools/search.ts
|
|
2348
2445
|
var SEARCH_PRICE_PER_SOURCE = 0.025;
|
|
@@ -2389,7 +2486,7 @@ Full request shape + worked examples in the \`search\` skill (\`skills/search/SK
|
|
|
2389
2486
|
recordSpending(budget, estimatedCost, agent_id);
|
|
2390
2487
|
return {
|
|
2391
2488
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2392
|
-
structuredContent: result
|
|
2489
|
+
structuredContent: asStructuredContent(result)
|
|
2393
2490
|
};
|
|
2394
2491
|
} catch (err) {
|
|
2395
2492
|
return { content: [{ type: "text", text: formatError(extractErrorMessage(err)) }], isError: true };
|
|
@@ -2447,7 +2544,7 @@ Full request/response shapes + worked research workflows in the \`exa-research\`
|
|
|
2447
2544
|
recordSpending(budget, estimatedCost, agent_id);
|
|
2448
2545
|
return {
|
|
2449
2546
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2450
|
-
structuredContent: result
|
|
2547
|
+
structuredContent: asStructuredContent(result)
|
|
2451
2548
|
};
|
|
2452
2549
|
} catch (err) {
|
|
2453
2550
|
return { content: [{ type: "text", text: formatError(extractErrorMessage(err)) }], isError: true };
|
|
@@ -2544,12 +2641,11 @@ Pass query params via 'params' (GET). Use 'body' only for POST endpoints (e.g. p
|
|
|
2544
2641
|
recordSpending(budget, estimatedCost, agent_id);
|
|
2545
2642
|
return {
|
|
2546
2643
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2547
|
-
structuredContent: result
|
|
2644
|
+
structuredContent: asStructuredContent(result)
|
|
2548
2645
|
};
|
|
2549
2646
|
} catch (err) {
|
|
2550
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2551
2647
|
return {
|
|
2552
|
-
content: [{ type: "text", text: formatError(
|
|
2648
|
+
content: [{ type: "text", text: formatError(extractErrorMessage(err)) }],
|
|
2553
2649
|
isError: true
|
|
2554
2650
|
};
|
|
2555
2651
|
}
|
|
@@ -2674,9 +2770,8 @@ Examples:
|
|
|
2674
2770
|
structuredContent: result
|
|
2675
2771
|
};
|
|
2676
2772
|
} catch (err) {
|
|
2677
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2678
2773
|
return {
|
|
2679
|
-
content: [{ type: "text", text: formatError(
|
|
2774
|
+
content: [{ type: "text", text: formatError(extractErrorMessage(err)) }],
|
|
2680
2775
|
isError: true
|
|
2681
2776
|
};
|
|
2682
2777
|
}
|
|
@@ -2770,6 +2865,15 @@ import { z as z14 } from "zod";
|
|
|
2770
2865
|
function estimateModalCost(path5) {
|
|
2771
2866
|
return path5.includes("sandbox/create") ? 0.01 : 1e-3;
|
|
2772
2867
|
}
|
|
2868
|
+
var MODAL_DEFAULT_TIMEOUT_S = 300;
|
|
2869
|
+
var MODAL_MAX_TIMEOUT_S = 1800;
|
|
2870
|
+
var MODAL_SLACK_MS = 15e3;
|
|
2871
|
+
function modalTimeoutMs(body) {
|
|
2872
|
+
const raw = body && typeof body === "object" ? body.timeout : void 0;
|
|
2873
|
+
const requested = typeof raw === "number" && raw > 0 ? raw : MODAL_DEFAULT_TIMEOUT_S;
|
|
2874
|
+
const clamped = Math.min(Math.max(requested, MODAL_DEFAULT_TIMEOUT_S), MODAL_MAX_TIMEOUT_S);
|
|
2875
|
+
return clamped * 1e3 + MODAL_SLACK_MS;
|
|
2876
|
+
}
|
|
2773
2877
|
function registerModalTool(server, budget) {
|
|
2774
2878
|
server.registerTool(
|
|
2775
2879
|
"blockrun_modal",
|
|
@@ -2803,13 +2907,13 @@ Full action shapes + GPU type details in the \`modal\` skill.`,
|
|
|
2803
2907
|
isError: true
|
|
2804
2908
|
};
|
|
2805
2909
|
}
|
|
2806
|
-
const client =
|
|
2910
|
+
const client = buildClientWithTimeout(modalTimeoutMs(body));
|
|
2807
2911
|
const endpoint = `/v1/modal/${cleanPath}`;
|
|
2808
2912
|
const result = await client.requestWithPaymentRaw(endpoint, body ?? {});
|
|
2809
2913
|
recordSpending(budget, estimatedCost, agent_id);
|
|
2810
2914
|
return {
|
|
2811
2915
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2812
|
-
structuredContent: result
|
|
2916
|
+
structuredContent: asStructuredContent(result)
|
|
2813
2917
|
};
|
|
2814
2918
|
} catch (err) {
|
|
2815
2919
|
return { content: [{ type: "text", text: formatError(extractErrorMessage(err)) }], isError: true };
|
|
@@ -2875,7 +2979,7 @@ Voice call flow + voice preset details + full body shapes in the \`phone\` skill
|
|
|
2875
2979
|
if (estimatedCost > 0) recordSpending(budget, estimatedCost, agent_id);
|
|
2876
2980
|
return {
|
|
2877
2981
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2878
|
-
structuredContent: result
|
|
2982
|
+
structuredContent: asStructuredContent(result)
|
|
2879
2983
|
};
|
|
2880
2984
|
} catch (err) {
|
|
2881
2985
|
return { content: [{ type: "text", text: formatError(extractErrorMessage(err)) }], isError: true };
|
|
@@ -2973,7 +3077,7 @@ Each Surf endpoint pre-validates required params before settling \u2014 you get
|
|
|
2973
3077
|
recordSpending(budget, estimatedCost, agent_id);
|
|
2974
3078
|
return {
|
|
2975
3079
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
2976
|
-
structuredContent: result
|
|
3080
|
+
structuredContent: asStructuredContent(result)
|
|
2977
3081
|
};
|
|
2978
3082
|
} catch (err) {
|
|
2979
3083
|
return {
|
|
@@ -3170,7 +3274,13 @@ function resolveTools(argv, env) {
|
|
|
3170
3274
|
|
|
3171
3275
|
// src/mcp-handler.ts
|
|
3172
3276
|
function initializeMcpServer(server, profileArgs) {
|
|
3173
|
-
const
|
|
3277
|
+
const env = profileArgs?.env ?? process.env;
|
|
3278
|
+
const budget = {
|
|
3279
|
+
limit: parseBudgetLimitEnv(env.BLOCKRUN_BUDGET_LIMIT),
|
|
3280
|
+
spent: 0,
|
|
3281
|
+
calls: 0,
|
|
3282
|
+
agents: /* @__PURE__ */ new Map()
|
|
3283
|
+
};
|
|
3174
3284
|
const modelCache = { models: null };
|
|
3175
3285
|
const { profile, tools } = resolveTools(profileArgs?.argv, profileArgs?.env);
|
|
3176
3286
|
const registrars = {
|
|
@@ -3243,9 +3353,15 @@ function looksLikeRawPrivateKey(value) {
|
|
|
3243
3353
|
if (value.length >= 80 && value.length <= 100 && /^[1-9A-HJ-NP-Za-km-z]+$/.test(value)) return true;
|
|
3244
3354
|
return false;
|
|
3245
3355
|
}
|
|
3356
|
+
function looksLikeSolanaSecretKeyArray(value) {
|
|
3357
|
+
return Array.isArray(value) && value.length === 64 && value.every((n) => typeof n === "number" && Number.isInteger(n) && n >= 0 && n <= 255);
|
|
3358
|
+
}
|
|
3246
3359
|
function walk(obj, file, jsonPath, out) {
|
|
3247
3360
|
if (obj === null || typeof obj !== "object") return;
|
|
3248
3361
|
if (Array.isArray(obj)) {
|
|
3362
|
+
if (looksLikeSolanaSecretKeyArray(obj)) {
|
|
3363
|
+
out.push({ file, path: jsonPath || "(root)" });
|
|
3364
|
+
}
|
|
3249
3365
|
obj.forEach((v, i) => walk(v, file, `${jsonPath}[${i}]`, out));
|
|
3250
3366
|
return;
|
|
3251
3367
|
}
|