@blockrun/clawrouter 0.10.5 → 0.10.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.d.ts CHANGED
@@ -738,6 +738,10 @@ type UsageEntry = {
738
738
  baselineCost: number;
739
739
  savings: number;
740
740
  latencyMs: number;
741
+ /** Partner service ID (e.g., "x_users_lookup") — only set for partner API calls */
742
+ partnerId?: string;
743
+ /** Partner service name (e.g., "AttentionVC") — only set for partner API calls */
744
+ service?: string;
741
745
  };
742
746
  /**
743
747
  * Log a usage entry as a JSON line.
@@ -991,6 +995,81 @@ declare function getStats(days?: number): Promise<AggregatedStats>;
991
995
  */
992
996
  declare function formatStatsAscii(stats: AggregatedStats): string;
993
997
 
998
+ /**
999
+ * Partner Service Registry
1000
+ *
1001
+ * Defines available partner APIs that can be called through ClawRouter's proxy.
1002
+ * Partners provide specialized data (Twitter/X, etc.) via x402 micropayments.
1003
+ * The same wallet used for LLM calls pays for partner API calls — zero extra setup.
1004
+ */
1005
+ type PartnerServiceParam = {
1006
+ name: string;
1007
+ type: "string" | "string[]" | "number";
1008
+ description: string;
1009
+ required: boolean;
1010
+ };
1011
+ type PartnerServiceDefinition = {
1012
+ /** Unique service ID used in tool names: blockrun_{id} */
1013
+ id: string;
1014
+ /** Human-readable name */
1015
+ name: string;
1016
+ /** Partner providing this service */
1017
+ partner: string;
1018
+ /** Short description for tool listing */
1019
+ description: string;
1020
+ /** Proxy path (relative to /v1) */
1021
+ proxyPath: string;
1022
+ /** HTTP method */
1023
+ method: "GET" | "POST";
1024
+ /** Parameters for the tool's JSON Schema */
1025
+ params: PartnerServiceParam[];
1026
+ /** Pricing info for display */
1027
+ pricing: {
1028
+ perUnit: string;
1029
+ unit: string;
1030
+ minimum: string;
1031
+ maximum: string;
1032
+ };
1033
+ /** Example usage for help text */
1034
+ example: {
1035
+ input: Record<string, unknown>;
1036
+ description: string;
1037
+ };
1038
+ };
1039
+ /**
1040
+ * All registered partner services.
1041
+ * New partners are added here — the rest of the system picks them up automatically.
1042
+ */
1043
+ declare const PARTNER_SERVICES: PartnerServiceDefinition[];
1044
+ /**
1045
+ * Get a partner service by ID.
1046
+ */
1047
+ declare function getPartnerService(id: string): PartnerServiceDefinition | undefined;
1048
+
1049
+ /**
1050
+ * Partner Tool Builder
1051
+ *
1052
+ * Converts partner service definitions into OpenClaw tool definitions.
1053
+ * Each tool's execute() calls through the local proxy which handles
1054
+ * x402 payment transparently using the same wallet.
1055
+ */
1056
+ /** OpenClaw tool definition shape (duck-typed) */
1057
+ type PartnerToolDefinition = {
1058
+ name: string;
1059
+ description: string;
1060
+ inputSchema: {
1061
+ type: "object";
1062
+ properties: Record<string, unknown>;
1063
+ required: string[];
1064
+ };
1065
+ execute: (args: Record<string, unknown>) => Promise<unknown>;
1066
+ };
1067
+ /**
1068
+ * Build OpenClaw tool definitions for all registered partner services.
1069
+ * @param proxyBaseUrl - Local proxy base URL (e.g., "http://127.0.0.1:8402")
1070
+ */
1071
+ declare function buildPartnerTools(proxyBaseUrl: string): PartnerToolDefinition[];
1072
+
994
1073
  /**
995
1074
  * @blockrun/clawrouter
996
1075
  *
@@ -1012,4 +1091,4 @@ declare function formatStatsAscii(stats: AggregatedStats): string;
1012
1091
 
1013
1092
  declare const plugin: OpenClawPluginDefinition;
1014
1093
 
1015
- export { type AggregatedStats, BALANCE_THRESHOLDS, BLOCKRUN_MODELS, type BalanceInfo, BalanceMonitor, type CachedLLMResponse, type CachedPaymentParams, type CachedResponse, DEFAULT_RETRY_CONFIG, DEFAULT_ROUTING_CONFIG, DEFAULT_SESSION_CONFIG, type DailyStats, EmptyWalletError, InsufficientFundsError, type InsufficientFundsInfo, type LowBalanceInfo, MODEL_ALIASES, OPENCLAW_MODELS, PaymentCache, type PaymentFetchResult, type PreAuthParams, type ProxyHandle, type ProxyOptions, RequestDeduplicator, ResponseCache, type ResponseCacheConfig, type RetryConfig, type RoutingConfig, type RoutingDecision, RpcError, type SessionConfig, type SessionEntry, SessionStore, type SufficiencyResult, type Tier, type UsageEntry, blockrunProvider, buildProviderModels, calculateModelCost, createPaymentFetch, plugin as default, fetchWithRetry, formatStatsAscii, getAgenticModels, getFallbackChain, getFallbackChainFiltered, getModelContextWindow, getProxyPort, getSessionId, getStats, isAgenticModel, isBalanceError, isEmptyWalletError, isInsufficientFundsError, isRetryable, isRpcError, logUsage, resolveModelAlias, route, startProxy };
1094
+ export { type AggregatedStats, BALANCE_THRESHOLDS, BLOCKRUN_MODELS, type BalanceInfo, BalanceMonitor, type CachedLLMResponse, type CachedPaymentParams, type CachedResponse, DEFAULT_RETRY_CONFIG, DEFAULT_ROUTING_CONFIG, DEFAULT_SESSION_CONFIG, type DailyStats, EmptyWalletError, InsufficientFundsError, type InsufficientFundsInfo, type LowBalanceInfo, MODEL_ALIASES, OPENCLAW_MODELS, PARTNER_SERVICES, type PartnerServiceDefinition, type PartnerToolDefinition, PaymentCache, type PaymentFetchResult, type PreAuthParams, type ProxyHandle, type ProxyOptions, RequestDeduplicator, ResponseCache, type ResponseCacheConfig, type RetryConfig, type RoutingConfig, type RoutingDecision, RpcError, type SessionConfig, type SessionEntry, SessionStore, type SufficiencyResult, type Tier, type UsageEntry, blockrunProvider, buildPartnerTools, buildProviderModels, calculateModelCost, createPaymentFetch, plugin as default, fetchWithRetry, formatStatsAscii, getAgenticModels, getFallbackChain, getFallbackChainFiltered, getModelContextWindow, getPartnerService, getProxyPort, getSessionId, getStats, isAgenticModel, isBalanceError, isEmptyWalletError, isInsufficientFundsError, isRetryable, isRpcError, logUsage, resolveModelAlias, route, startProxy };
package/dist/index.js CHANGED
@@ -1,3 +1,126 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/partners/registry.ts
12
+ function getPartnerService(id) {
13
+ return PARTNER_SERVICES.find((s) => s.id === id);
14
+ }
15
+ var PARTNER_SERVICES;
16
+ var init_registry = __esm({
17
+ "src/partners/registry.ts"() {
18
+ "use strict";
19
+ PARTNER_SERVICES = [
20
+ {
21
+ id: "x_users_lookup",
22
+ name: "Twitter/X User Lookup",
23
+ partner: "AttentionVC",
24
+ description: "Look up Twitter/X user profiles by username. Returns follower counts, verification status, bio, and more. Accepts up to 100 usernames per request.",
25
+ proxyPath: "/x/users/lookup",
26
+ method: "POST",
27
+ params: [
28
+ {
29
+ name: "usernames",
30
+ type: "string[]",
31
+ description: 'Array of Twitter/X usernames to look up (without @ prefix). Example: ["elonmusk", "naval"]',
32
+ required: true
33
+ }
34
+ ],
35
+ pricing: {
36
+ perUnit: "$0.001",
37
+ unit: "user",
38
+ minimum: "$0.01 (10 users)",
39
+ maximum: "$0.10 (100 users)"
40
+ },
41
+ example: {
42
+ input: { usernames: ["elonmusk", "naval", "balaboris"] },
43
+ description: "Look up 3 Twitter/X user profiles"
44
+ }
45
+ }
46
+ ];
47
+ }
48
+ });
49
+
50
+ // src/partners/tools.ts
51
+ function buildTool(service, proxyBaseUrl) {
52
+ const properties = {};
53
+ const required = [];
54
+ for (const param of service.params) {
55
+ const prop = {
56
+ description: param.description
57
+ };
58
+ if (param.type === "string[]") {
59
+ prop.type = "array";
60
+ prop.items = { type: "string" };
61
+ } else {
62
+ prop.type = param.type;
63
+ }
64
+ properties[param.name] = prop;
65
+ if (param.required) {
66
+ required.push(param.name);
67
+ }
68
+ }
69
+ return {
70
+ name: `blockrun_${service.id}`,
71
+ description: [
72
+ service.description,
73
+ "",
74
+ `Partner: ${service.partner}`,
75
+ `Pricing: ${service.pricing.perUnit} per ${service.pricing.unit} (min: ${service.pricing.minimum}, max: ${service.pricing.maximum})`
76
+ ].join("\n"),
77
+ inputSchema: {
78
+ type: "object",
79
+ properties,
80
+ required
81
+ },
82
+ execute: async (args) => {
83
+ const url = `${proxyBaseUrl}/v1${service.proxyPath}`;
84
+ const response = await fetch(url, {
85
+ method: service.method,
86
+ headers: { "Content-Type": "application/json" },
87
+ body: JSON.stringify(args)
88
+ });
89
+ if (!response.ok) {
90
+ const errText = await response.text().catch(() => "");
91
+ throw new Error(
92
+ `Partner API error (${response.status}): ${errText || response.statusText}`
93
+ );
94
+ }
95
+ return response.json();
96
+ }
97
+ };
98
+ }
99
+ function buildPartnerTools(proxyBaseUrl) {
100
+ return PARTNER_SERVICES.map((service) => buildTool(service, proxyBaseUrl));
101
+ }
102
+ var init_tools = __esm({
103
+ "src/partners/tools.ts"() {
104
+ "use strict";
105
+ init_registry();
106
+ }
107
+ });
108
+
109
+ // src/partners/index.ts
110
+ var partners_exports = {};
111
+ __export(partners_exports, {
112
+ PARTNER_SERVICES: () => PARTNER_SERVICES,
113
+ buildPartnerTools: () => buildPartnerTools,
114
+ getPartnerService: () => getPartnerService
115
+ });
116
+ var init_partners = __esm({
117
+ "src/partners/index.ts"() {
118
+ "use strict";
119
+ init_registry();
120
+ init_tools();
121
+ }
122
+ });
123
+
1
124
  // src/models.ts
2
125
  var MODEL_ALIASES = {
3
126
  // Claude - use newest versions (4.6)
@@ -2635,7 +2758,7 @@ function formatStatsAscii(stats) {
2635
2758
  lines.push(`\u2551 Avg Latency: ${stats.avgLatencyMs.toFixed(0)}ms`.padEnd(61) + "\u2551");
2636
2759
  lines.push("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563");
2637
2760
  lines.push("\u2551 Routing by Tier: \u2551");
2638
- const knownTiers = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"];
2761
+ const knownTiers = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING", "DIRECT"];
2639
2762
  const allTiers = Object.keys(stats.byTier);
2640
2763
  const otherTiers = allTiers.filter((t) => !knownTiers.includes(t));
2641
2764
  const tierOrder = [...knownTiers.filter((t) => stats.byTier[t]), ...otherTiers];
@@ -4745,6 +4868,63 @@ function estimateAmount(modelId, bodyLength, maxTokens) {
4745
4868
  const amountMicros = Math.max(100, Math.ceil(costUsd * 1.2 * 1e6));
4746
4869
  return amountMicros.toString();
4747
4870
  }
4871
+ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
4872
+ const startTime = Date.now();
4873
+ const upstreamUrl = `${apiBase}${req.url}`;
4874
+ const bodyChunks = [];
4875
+ for await (const chunk of req) {
4876
+ bodyChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
4877
+ }
4878
+ const body = Buffer.concat(bodyChunks);
4879
+ const headers = {};
4880
+ for (const [key, value] of Object.entries(req.headers)) {
4881
+ if (key === "host" || key === "connection" || key === "transfer-encoding" || key === "content-length")
4882
+ continue;
4883
+ if (typeof value === "string") headers[key] = value;
4884
+ }
4885
+ if (!headers["content-type"]) headers["content-type"] = "application/json";
4886
+ headers["user-agent"] = USER_AGENT;
4887
+ console.log(`[ClawRouter] Partner request: ${req.method} ${req.url}`);
4888
+ const upstream = await payFetch(upstreamUrl, {
4889
+ method: req.method ?? "POST",
4890
+ headers,
4891
+ body: body.length > 0 ? new Uint8Array(body) : void 0
4892
+ });
4893
+ const responseHeaders = {};
4894
+ upstream.headers.forEach((value, key) => {
4895
+ if (key === "transfer-encoding" || key === "connection" || key === "content-encoding") return;
4896
+ responseHeaders[key] = value;
4897
+ });
4898
+ res.writeHead(upstream.status, responseHeaders);
4899
+ if (upstream.body) {
4900
+ const reader = upstream.body.getReader();
4901
+ try {
4902
+ while (true) {
4903
+ const { done, value } = await reader.read();
4904
+ if (done) break;
4905
+ safeWrite(res, Buffer.from(value));
4906
+ }
4907
+ } finally {
4908
+ reader.releaseLock();
4909
+ }
4910
+ }
4911
+ res.end();
4912
+ const latencyMs = Date.now() - startTime;
4913
+ console.log(`[ClawRouter] Partner response: ${upstream.status} (${latencyMs}ms)`);
4914
+ logUsage({
4915
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4916
+ model: "partner",
4917
+ tier: "PARTNER",
4918
+ cost: 0,
4919
+ // Actual cost handled by x402 settlement
4920
+ baselineCost: 0,
4921
+ savings: 0,
4922
+ latencyMs,
4923
+ partnerId: (req.url?.split("?")[0] ?? "").replace(/^\/v1\//, "").replace(/\//g, "_") || "unknown",
4924
+ service: "partner"
4925
+ }).catch(() => {
4926
+ });
4927
+ }
4748
4928
  async function startProxy(options) {
4749
4929
  const apiBase = options.apiBase ?? BLOCKRUN_API;
4750
4930
  const listenPort = options.port ?? getProxyPort();
@@ -4855,6 +5035,23 @@ async function startProxy(options) {
4855
5035
  res.end(JSON.stringify({ object: "list", data: models }));
4856
5036
  return;
4857
5037
  }
5038
+ if (req.url?.match(/^\/v1\/(?:x|partner)\//)) {
5039
+ try {
5040
+ await proxyPartnerRequest(req, res, apiBase, payFetch);
5041
+ } catch (err) {
5042
+ const error = err instanceof Error ? err : new Error(String(err));
5043
+ options.onError?.(error);
5044
+ if (!res.headersSent) {
5045
+ res.writeHead(502, { "Content-Type": "application/json" });
5046
+ res.end(
5047
+ JSON.stringify({
5048
+ error: { message: `Partner proxy error: ${error.message}`, type: "partner_error" }
5049
+ })
5050
+ );
5051
+ }
5052
+ }
5053
+ return;
5054
+ }
4858
5055
  if (!req.url?.startsWith("/v1")) {
4859
5056
  res.writeHead(404, { "Content-Type": "application/json" });
4860
5057
  res.end(JSON.stringify({ error: "Not found" }));
@@ -5717,10 +5914,11 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5717
5914
  }
5718
5915
  throw err;
5719
5916
  }
5720
- if (routingDecision) {
5917
+ const logModel = routingDecision?.model ?? modelId;
5918
+ if (logModel) {
5721
5919
  const estimatedInputTokens = Math.ceil(body.length / 4);
5722
5920
  const accurateCosts = calculateModelCost(
5723
- routingDecision.model,
5921
+ logModel,
5724
5922
  routerOpts.modelPricing,
5725
5923
  estimatedInputTokens,
5726
5924
  maxTokens,
@@ -5730,8 +5928,8 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
5730
5928
  const baselineWithBuffer = accurateCosts.baselineCost * 1.2;
5731
5929
  const entry = {
5732
5930
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5733
- model: routingDecision.model,
5734
- tier: routingDecision.tier,
5931
+ model: logModel,
5932
+ tier: routingDecision?.tier ?? "DIRECT",
5735
5933
  cost: costWithBuffer,
5736
5934
  baselineCost: baselineWithBuffer,
5737
5935
  savings: accurateCosts.savings,
@@ -5869,6 +6067,7 @@ function isRetryable(errorOrResponse, config) {
5869
6067
  }
5870
6068
 
5871
6069
  // src/index.ts
6070
+ init_partners();
5872
6071
  async function waitForProxyHealth(port, timeoutMs = 3e3) {
5873
6072
  const start = Date.now();
5874
6073
  while (Date.now() - start < timeoutMs) {
@@ -6314,6 +6513,44 @@ var plugin = {
6314
6513
  models: OPENCLAW_MODELS
6315
6514
  };
6316
6515
  api.logger.info("BlockRun provider registered (30+ models via x402)");
6516
+ try {
6517
+ const { buildPartnerTools: buildPartnerTools2, PARTNER_SERVICES: PARTNER_SERVICES2 } = await Promise.resolve().then(() => (init_partners(), partners_exports));
6518
+ const proxyBaseUrl = `http://127.0.0.1:${runtimePort}`;
6519
+ const partnerTools = buildPartnerTools2(proxyBaseUrl);
6520
+ for (const tool of partnerTools) {
6521
+ api.registerTool(tool);
6522
+ }
6523
+ if (partnerTools.length > 0) {
6524
+ api.logger.info(`Registered ${partnerTools.length} partner tool(s): ${partnerTools.map((t) => t.name).join(", ")}`);
6525
+ }
6526
+ api.registerCommand({
6527
+ name: "partners",
6528
+ description: "List available partner APIs and pricing",
6529
+ acceptsArgs: false,
6530
+ requireAuth: false,
6531
+ handler: async () => {
6532
+ if (PARTNER_SERVICES2.length === 0) {
6533
+ return { text: "No partner APIs available." };
6534
+ }
6535
+ const lines = [
6536
+ "**Partner APIs** (paid via your ClawRouter wallet)",
6537
+ ""
6538
+ ];
6539
+ for (const svc of PARTNER_SERVICES2) {
6540
+ lines.push(`**${svc.name}** (${svc.partner})`);
6541
+ lines.push(` ${svc.description}`);
6542
+ lines.push(` Tool: \`${`blockrun_${svc.id}`}\``);
6543
+ lines.push(` Pricing: ${svc.pricing.perUnit} per ${svc.pricing.unit} (min ${svc.pricing.minimum}, max ${svc.pricing.maximum})`);
6544
+ lines.push("");
6545
+ }
6546
+ return { text: lines.join("\n") };
6547
+ }
6548
+ });
6549
+ } catch (err) {
6550
+ api.logger.warn(
6551
+ `Failed to register partner tools: ${err instanceof Error ? err.message : String(err)}`
6552
+ );
6553
+ }
6317
6554
  createWalletCommand().then((walletCommand) => {
6318
6555
  api.registerCommand(walletCommand);
6319
6556
  }).catch((err) => {
@@ -6388,12 +6625,14 @@ export {
6388
6625
  InsufficientFundsError,
6389
6626
  MODEL_ALIASES,
6390
6627
  OPENCLAW_MODELS,
6628
+ PARTNER_SERVICES,
6391
6629
  PaymentCache,
6392
6630
  RequestDeduplicator,
6393
6631
  ResponseCache,
6394
6632
  RpcError,
6395
6633
  SessionStore,
6396
6634
  blockrunProvider,
6635
+ buildPartnerTools,
6397
6636
  buildProviderModels,
6398
6637
  calculateModelCost,
6399
6638
  createPaymentFetch,
@@ -6404,6 +6643,7 @@ export {
6404
6643
  getFallbackChain,
6405
6644
  getFallbackChainFiltered,
6406
6645
  getModelContextWindow,
6646
+ getPartnerService,
6407
6647
  getProxyPort,
6408
6648
  getSessionId,
6409
6649
  getStats,