@t54-labs/clawcredit-blockrun-sdk 0.1.0 → 0.1.2

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 CHANGED
@@ -22,7 +22,7 @@ npm run build
22
22
 
23
23
  export CLAWCREDIT_API_TOKEN=claw_xxx
24
24
  export CLAWCREDIT_CHAIN=BASE
25
- export CLAWCREDIT_ASSET=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
25
+ export CLAWCREDIT_ASSET=USDC
26
26
 
27
27
  node dist/cli.js
28
28
  # listening on http://127.0.0.1:3402
@@ -38,7 +38,7 @@ const gateway = await startGateway({
38
38
  clawCredit: {
39
39
  apiToken: process.env.CLAWCREDIT_API_TOKEN!,
40
40
  chain: "BASE",
41
- asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
41
+ asset: "USDC",
42
42
  },
43
43
  });
44
44
  ```
@@ -54,9 +54,10 @@ bash scripts/setup-openclaw-clawcredit-gateway.sh --token claw_xxx
54
54
  This will:
55
55
 
56
56
  - build/start the standalone gateway in background
57
+ - fetch latest real model IDs from `https://blockrun.ai/api/v1/models`
57
58
  - patch OpenClaw provider config (`blockruncc` -> `http://127.0.0.1:3402/v1`)
58
59
  - restart OpenClaw gateway
59
- - set active model to `blockruncc/premium`
60
+ - set active model to `blockruncc/openai/gpt-4o` (default)
60
61
 
61
62
  Dry-run first:
62
63
 
@@ -70,7 +71,7 @@ Then call:
70
71
  curl -sS http://127.0.0.1:3402/v1/chat/completions \
71
72
  -H 'Content-Type: application/json' \
72
73
  -d '{
73
- "model":"blockrun/premium",
74
+ "model":"openai/gpt-4o",
74
75
  "messages":[{"role":"user","content":"hi"}],
75
76
  "max_tokens":64
76
77
  }'
@@ -81,7 +82,7 @@ curl -sS http://127.0.0.1:3402/v1/chat/completions \
81
82
  - `CLAWCREDIT_API_TOKEN` (required)
82
83
  - `CLAWCREDIT_API_BASE` (default: `https://api.claw.credit`)
83
84
  - `CLAWCREDIT_CHAIN` (default: `BASE`)
84
- - `CLAWCREDIT_ASSET` (default: Base USDC)
85
+ - `CLAWCREDIT_ASSET` (default: `USDC` on BASE)
85
86
  - `CLAWCREDIT_AGENT` (optional)
86
87
  - `CLAWCREDIT_AGENT_ID` (optional)
87
88
  - `CLAWCREDIT_DEFAULT_AMOUNT_USD` (default: `0.1`)
@@ -89,6 +90,24 @@ curl -sS http://127.0.0.1:3402/v1/chat/completions \
89
90
  - `HOST` (default: `127.0.0.1`)
90
91
  - `PORT` / `GATEWAY_PORT` (default: `3402`)
91
92
 
93
+ ## Supported models
94
+
95
+ This gateway syncs model IDs from `BLOCKRUN_API_BASE/v1/models` (no meta models).
96
+ Examples include:
97
+
98
+ - `openai/gpt-4o`
99
+ - `openai/gpt-5.2`
100
+ - `anthropic/claude-opus-4.5`
101
+ - `anthropic/claude-sonnet-4`
102
+ - `google/gemini-3-pro-preview`
103
+ - `moonshot/kimi-k2.5`
104
+
105
+ List current synced models in OpenClaw:
106
+
107
+ ```bash
108
+ openclaw models show | rg '^ blockruncc/'
109
+ ```
110
+
92
111
  ## Endpoints
93
112
 
94
113
  - `GET /health`
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-K57GMQDY.js";
5
5
 
6
6
  // src/cli.ts
7
- var DEFAULT_BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
7
+ var DEFAULT_ASSET = "USDC";
8
8
  function required(name) {
9
9
  const value = (process.env[name] || "").trim();
10
10
  if (!value) {
@@ -21,7 +21,7 @@ async function main() {
21
21
  baseUrl: (process.env.CLAWCREDIT_API_BASE || "https://api.claw.credit").trim(),
22
22
  apiToken: required("CLAWCREDIT_API_TOKEN"),
23
23
  chain: (process.env.CLAWCREDIT_CHAIN || "BASE").trim(),
24
- asset: (process.env.CLAWCREDIT_ASSET || DEFAULT_BASE_USDC).trim(),
24
+ asset: (process.env.CLAWCREDIT_ASSET || DEFAULT_ASSET).trim(),
25
25
  agent: (process.env.CLAWCREDIT_AGENT || "").trim() || void 0,
26
26
  agentId: (process.env.CLAWCREDIT_AGENT_ID || "").trim() || void 0
27
27
  };
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { startGateway } from \"./server.js\";\n\nconst DEFAULT_BASE_USDC = \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\";\n\nfunction required(name: string): string {\n const value = (process.env[name] || \"\").trim();\n if (!value) {\n throw new Error(`${name} is required`);\n }\n return value;\n}\n\nasync function main(): Promise<void> {\n const port = Number(process.env.PORT || process.env.GATEWAY_PORT || 3402);\n const host = (process.env.HOST || \"127.0.0.1\").trim();\n const blockrunApiBase =\n (process.env.BLOCKRUN_API_BASE || \"https://blockrun.ai/api\").trim() ||\n \"https://blockrun.ai/api\";\n const defaultAmountUsd = Number(process.env.CLAWCREDIT_DEFAULT_AMOUNT_USD || 0.1);\n\n const clawCredit = {\n baseUrl: (process.env.CLAWCREDIT_API_BASE || \"https://api.claw.credit\").trim(),\n apiToken: required(\"CLAWCREDIT_API_TOKEN\"),\n chain: (process.env.CLAWCREDIT_CHAIN || \"BASE\").trim(),\n asset: (process.env.CLAWCREDIT_ASSET || DEFAULT_BASE_USDC).trim(),\n agent: (process.env.CLAWCREDIT_AGENT || \"\").trim() || undefined,\n agentId: (process.env.CLAWCREDIT_AGENT_ID || \"\").trim() || undefined,\n };\n\n const gateway = await startGateway({\n port,\n host,\n blockrunApiBase,\n clawCredit,\n defaultAmountUsd,\n });\n\n console.log(`[clawcredit-blockrun-gateway] listening on ${gateway.baseUrl}`);\n console.log(\n `[clawcredit-blockrun-gateway] blockrun=${blockrunApiBase} clawcredit=${clawCredit.baseUrl} chain=${clawCredit.chain}`,\n );\n\n const shutdown = async () => {\n await gateway.close();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nmain().catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[clawcredit-blockrun-gateway] startup failed: ${msg}`);\n process.exit(1);\n});\n"],"mappings":";;;;;;AAGA,IAAM,oBAAoB;AAE1B,SAAS,SAAS,MAAsB;AACtC,QAAM,SAAS,QAAQ,IAAI,IAAI,KAAK,IAAI,KAAK;AAC7C,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,GAAG,IAAI,cAAc;AAAA,EACvC;AACA,SAAO;AACT;AAEA,eAAe,OAAsB;AACnC,QAAM,OAAO,OAAO,QAAQ,IAAI,QAAQ,QAAQ,IAAI,gBAAgB,IAAI;AACxE,QAAM,QAAQ,QAAQ,IAAI,QAAQ,aAAa,KAAK;AACpD,QAAM,mBACH,QAAQ,IAAI,qBAAqB,2BAA2B,KAAK,KAClE;AACF,QAAM,mBAAmB,OAAO,QAAQ,IAAI,iCAAiC,GAAG;AAEhF,QAAM,aAAa;AAAA,IACjB,UAAU,QAAQ,IAAI,uBAAuB,2BAA2B,KAAK;AAAA,IAC7E,UAAU,SAAS,sBAAsB;AAAA,IACzC,QAAQ,QAAQ,IAAI,oBAAoB,QAAQ,KAAK;AAAA,IACrD,QAAQ,QAAQ,IAAI,oBAAoB,mBAAmB,KAAK;AAAA,IAChE,QAAQ,QAAQ,IAAI,oBAAoB,IAAI,KAAK,KAAK;AAAA,IACtD,UAAU,QAAQ,IAAI,uBAAuB,IAAI,KAAK,KAAK;AAAA,EAC7D;AAEA,QAAM,UAAU,MAAM,aAAa;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,UAAQ,IAAI,8CAA8C,QAAQ,OAAO,EAAE;AAC3E,UAAQ;AAAA,IACN,0CAA0C,eAAe,eAAe,WAAW,OAAO,UAAU,WAAW,KAAK;AAAA,EACtH;AAEA,QAAM,WAAW,YAAY;AAC3B,UAAM,QAAQ,MAAM;AACpB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAChC;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,QAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,UAAQ,MAAM,iDAAiD,GAAG,EAAE;AACpE,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { startGateway } from \"./server.js\";\n\nconst DEFAULT_ASSET = \"USDC\";\n\nfunction required(name: string): string {\n const value = (process.env[name] || \"\").trim();\n if (!value) {\n throw new Error(`${name} is required`);\n }\n return value;\n}\n\nasync function main(): Promise<void> {\n const port = Number(process.env.PORT || process.env.GATEWAY_PORT || 3402);\n const host = (process.env.HOST || \"127.0.0.1\").trim();\n const blockrunApiBase =\n (process.env.BLOCKRUN_API_BASE || \"https://blockrun.ai/api\").trim() ||\n \"https://blockrun.ai/api\";\n const defaultAmountUsd = Number(process.env.CLAWCREDIT_DEFAULT_AMOUNT_USD || 0.1);\n\n const clawCredit = {\n baseUrl: (process.env.CLAWCREDIT_API_BASE || \"https://api.claw.credit\").trim(),\n apiToken: required(\"CLAWCREDIT_API_TOKEN\"),\n chain: (process.env.CLAWCREDIT_CHAIN || \"BASE\").trim(),\n asset: (process.env.CLAWCREDIT_ASSET || DEFAULT_ASSET).trim(),\n agent: (process.env.CLAWCREDIT_AGENT || \"\").trim() || undefined,\n agentId: (process.env.CLAWCREDIT_AGENT_ID || \"\").trim() || undefined,\n };\n\n const gateway = await startGateway({\n port,\n host,\n blockrunApiBase,\n clawCredit,\n defaultAmountUsd,\n });\n\n console.log(`[clawcredit-blockrun-gateway] listening on ${gateway.baseUrl}`);\n console.log(\n `[clawcredit-blockrun-gateway] blockrun=${blockrunApiBase} clawcredit=${clawCredit.baseUrl} chain=${clawCredit.chain}`,\n );\n\n const shutdown = async () => {\n await gateway.close();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nmain().catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[clawcredit-blockrun-gateway] startup failed: ${msg}`);\n process.exit(1);\n});\n"],"mappings":";;;;;;AAGA,IAAM,gBAAgB;AAEtB,SAAS,SAAS,MAAsB;AACtC,QAAM,SAAS,QAAQ,IAAI,IAAI,KAAK,IAAI,KAAK;AAC7C,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,GAAG,IAAI,cAAc;AAAA,EACvC;AACA,SAAO;AACT;AAEA,eAAe,OAAsB;AACnC,QAAM,OAAO,OAAO,QAAQ,IAAI,QAAQ,QAAQ,IAAI,gBAAgB,IAAI;AACxE,QAAM,QAAQ,QAAQ,IAAI,QAAQ,aAAa,KAAK;AACpD,QAAM,mBACH,QAAQ,IAAI,qBAAqB,2BAA2B,KAAK,KAClE;AACF,QAAM,mBAAmB,OAAO,QAAQ,IAAI,iCAAiC,GAAG;AAEhF,QAAM,aAAa;AAAA,IACjB,UAAU,QAAQ,IAAI,uBAAuB,2BAA2B,KAAK;AAAA,IAC7E,UAAU,SAAS,sBAAsB;AAAA,IACzC,QAAQ,QAAQ,IAAI,oBAAoB,QAAQ,KAAK;AAAA,IACrD,QAAQ,QAAQ,IAAI,oBAAoB,eAAe,KAAK;AAAA,IAC5D,QAAQ,QAAQ,IAAI,oBAAoB,IAAI,KAAK,KAAK;AAAA,IACtD,UAAU,QAAQ,IAAI,uBAAuB,IAAI,KAAK,KAAK;AAAA,EAC7D;AAEA,QAAM,UAAU,MAAM,aAAa;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,UAAQ,IAAI,8CAA8C,QAAQ,OAAO,EAAE;AAC3E,UAAQ;AAAA,IACN,0CAA0C,eAAe,eAAe,WAAW,OAAO,UAAU,WAAW,KAAK;AAAA,EACtH;AAEA,QAAM,WAAW,YAAY;AAC3B,UAAM,QAAQ,MAAM;AACpB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAChC;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,QAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,UAAQ,MAAM,iDAAiD,GAAG,EAAE;AACpE,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@t54-labs/clawcredit-blockrun-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "SDK and standalone OpenAI-compatible gateway for BlockRun inference paid via claw.credit",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {
9
- "clawcredit-blockrun-gateway": "./dist/cli.js",
10
- "clawcredit-blockrun-sdk": "./dist/cli.js"
9
+ "clawcredit-blockrun-gateway": "dist/cli.js",
10
+ "clawcredit-blockrun-sdk": "dist/cli.js"
11
11
  },
12
12
  "exports": {
13
13
  ".": {
@@ -26,7 +26,7 @@
26
26
  "dev": "tsup --watch",
27
27
  "start": "node dist/cli.js",
28
28
  "typecheck": "tsc --noEmit",
29
- "test": "npx tsx test/gateway-integration.ts"
29
+ "test": "npx tsx test/openclaw-model-sync.ts && npx tsx test/gateway-integration.ts"
30
30
  },
31
31
  "keywords": [
32
32
  "clawcredit",
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+
3
+ const FALLBACK_MODEL_IDS = [
4
+ "openai/gpt-4o",
5
+ "openai/gpt-4o-mini",
6
+ "anthropic/claude-sonnet-4",
7
+ "anthropic/claude-haiku-4.5",
8
+ ];
9
+
10
+ function asNumber(value, fallback) {
11
+ const n = Number(value);
12
+ return Number.isFinite(n) ? n : fallback;
13
+ }
14
+
15
+ function inferReasoning(modelId) {
16
+ const id = String(modelId || "").toLowerCase();
17
+ return (
18
+ id.includes("reasoner") ||
19
+ id.includes("reasoning") ||
20
+ id.includes("/o1") ||
21
+ id.includes("/o3") ||
22
+ id.includes("/o4") ||
23
+ id.includes("opus") ||
24
+ id.includes("sonnet") ||
25
+ id.includes("gpt-5")
26
+ );
27
+ }
28
+
29
+ function inferInputModes(modelId) {
30
+ const id = String(modelId || "").toLowerCase();
31
+ if (
32
+ id.includes("gpt-4o") ||
33
+ id.includes("gemini") ||
34
+ id.includes("vision") ||
35
+ id.includes("claude-sonnet")
36
+ ) {
37
+ return ["text", "image"];
38
+ }
39
+ return ["text"];
40
+ }
41
+
42
+ function toModelConfig(raw) {
43
+ if (!raw || typeof raw !== "object") return null;
44
+ const id = typeof raw.id === "string" ? raw.id.trim() : "";
45
+ if (!id) return null;
46
+
47
+ const pricing =
48
+ raw.pricing && typeof raw.pricing === "object" && !Array.isArray(raw.pricing) ? raw.pricing : {};
49
+
50
+ return {
51
+ id,
52
+ name: typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : id,
53
+ api: "openai-completions",
54
+ reasoning: inferReasoning(id),
55
+ input: inferInputModes(id),
56
+ cost: {
57
+ input: asNumber(pricing.input, 0),
58
+ output: asNumber(pricing.output, 0),
59
+ cacheRead: 0,
60
+ cacheWrite: 0,
61
+ },
62
+ contextWindow: asNumber(raw.contextWindow ?? raw.context_window, 200000),
63
+ maxTokens: asNumber(raw.maxTokens ?? raw.max_tokens ?? raw.maxOutput, 65536),
64
+ };
65
+ }
66
+
67
+ function extractUpstreamModels(payload) {
68
+ if (!payload || typeof payload !== "object" || !Array.isArray(payload.data)) {
69
+ return [];
70
+ }
71
+
72
+ const out = [];
73
+ const seen = new Set();
74
+ for (const item of payload.data) {
75
+ const model = toModelConfig(item);
76
+ if (!model) continue;
77
+ if (seen.has(model.id)) continue;
78
+ seen.add(model.id);
79
+ out.push(model);
80
+ }
81
+ return out;
82
+ }
83
+
84
+ function buildFallbackModels() {
85
+ return FALLBACK_MODEL_IDS.map((id) =>
86
+ toModelConfig({
87
+ id,
88
+ pricing: { input: 0, output: 0 },
89
+ context_window: 200000,
90
+ max_tokens: 65536,
91
+ }),
92
+ ).filter(Boolean);
93
+ }
94
+
95
+ function pickDefaultModel(models, requestedDefaultModelId) {
96
+ const requested = String(requestedDefaultModelId || "").trim();
97
+ const ids = new Set((models || []).map((m) => m.id));
98
+ if (requested && ids.has(requested)) return requested;
99
+ if (ids.has("openai/gpt-4o")) return "openai/gpt-4o";
100
+ if (Array.isArray(models) && models.length > 0) return models[0].id;
101
+ return "openai/gpt-4o";
102
+ }
103
+
104
+ function ensureObject(value) {
105
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
106
+ }
107
+
108
+ function syncOpenClawConfig(config, options) {
109
+ const safeConfig = ensureObject(config);
110
+ const providerId = String(options.providerId || "").trim();
111
+ const baseUrl = String(options.baseUrl || "").trim();
112
+ const models = Array.isArray(options.models) ? options.models : [];
113
+
114
+ safeConfig.models = ensureObject(safeConfig.models);
115
+ safeConfig.models.providers = ensureObject(safeConfig.models.providers);
116
+
117
+ const existing = ensureObject(safeConfig.models.providers[providerId]);
118
+ existing.baseUrl = baseUrl;
119
+ existing.apiKey = "clawcredit-gateway";
120
+ existing.api = "openai-completions";
121
+ existing.models = models;
122
+ safeConfig.models.providers[providerId] = existing;
123
+
124
+ const effectiveDefaultModelId = pickDefaultModel(models, options.requestedDefaultModelId);
125
+
126
+ safeConfig.agents = ensureObject(safeConfig.agents);
127
+ safeConfig.agents.defaults = ensureObject(safeConfig.agents.defaults);
128
+ const allowlist = ensureObject(safeConfig.agents.defaults.models);
129
+
130
+ for (const key of Object.keys(allowlist)) {
131
+ if (key.startsWith(`${providerId}/`)) {
132
+ delete allowlist[key];
133
+ }
134
+ }
135
+ for (const model of models) {
136
+ allowlist[`${providerId}/${model.id}`] = {};
137
+ }
138
+ safeConfig.agents.defaults.models = allowlist;
139
+
140
+ return {
141
+ updatedConfig: safeConfig,
142
+ effectiveDefaultModelId,
143
+ };
144
+ }
145
+
146
+ module.exports = {
147
+ FALLBACK_MODEL_IDS,
148
+ extractUpstreamModels,
149
+ buildFallbackModels,
150
+ pickDefaultModel,
151
+ syncOpenClawConfig,
152
+ };
@@ -4,12 +4,12 @@ set -euo pipefail
4
4
  DEFAULT_GATEWAY_DIR="/tmp/clawcredit-blockrun-gateway"
5
5
  DEFAULT_PORT="3402"
6
6
  DEFAULT_PROVIDER_ID="blockruncc"
7
- DEFAULT_MODEL_ID="premium"
7
+ DEFAULT_MODEL_ID="openai/gpt-4o"
8
8
  DEFAULT_HOST="127.0.0.1"
9
9
  DEFAULT_BLOCKRUN_API_BASE="https://blockrun.ai/api"
10
10
  DEFAULT_CLAWCREDIT_BASE_URL="https://api.claw.credit"
11
11
  DEFAULT_CHAIN="BASE"
12
- DEFAULT_ASSET_BASE_USDC="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
12
+ DEFAULT_ASSET_BASE_USDC="USDC"
13
13
  DEFAULT_AMOUNT_USD="0.1"
14
14
 
15
15
  usage() {
@@ -31,13 +31,13 @@ Options:
31
31
  --port <port> Gateway port (default: 3402)
32
32
  --host <host> Gateway host bind (default: 127.0.0.1)
33
33
  --provider <id> OpenClaw provider id (default: blockruncc)
34
- --model <id> Model id to set active (default: premium)
34
+ --model <id> Model id to set active (default: openai/gpt-4o)
35
35
  --profile <name> OpenClaw profile for CLI commands
36
36
  --state-dir <path> OpenClaw state dir override
37
37
  --blockrun-api <url> BLOCKRUN_API_BASE (default: https://blockrun.ai/api)
38
38
  --cc-base-url <url> CLAWCREDIT_API_BASE (default: https://api.claw.credit)
39
39
  --chain <name> CLAWCREDIT_CHAIN (default: BASE)
40
- --asset <addr> CLAWCREDIT_ASSET (default: Base USDC on BASE)
40
+ --asset <value> CLAWCREDIT_ASSET (default: USDC on BASE)
41
41
  --amount-usd <num> CLAWCREDIT_DEFAULT_AMOUNT_USD (default: 0.1)
42
42
  --no-model-set Skip `openclaw models set`
43
43
  --no-restart Skip `openclaw gateway restart`
@@ -227,6 +227,7 @@ log " gateway dir: $GATEWAY_DIR"
227
227
  log " gateway listen: ${HOST}:${PORT}"
228
228
  log " OpenClaw state dir: $STATE_DIR"
229
229
  log " provider/model: ${PROVIDER_ID}/${MODEL_ID}"
230
+ log " exposed models: dynamic sync from ${BLOCKRUN_API_BASE%/}/v1/models"
230
231
  log " blockrun api: $BLOCKRUN_API_BASE"
231
232
  log " clawcredit: $CLAWCREDIT_BASE_URL ($CLAWCREDIT_CHAIN)"
232
233
  log ""
@@ -234,7 +235,8 @@ log ""
234
235
  if [[ "$DRY_RUN" == "1" ]]; then
235
236
  log "[dry-run] Would run npm install/build in $GATEWAY_DIR"
236
237
  log "[dry-run] Would start gateway and wait for $HEALTH_URL"
237
- log "[dry-run] Would patch $OPENCLAW_JSON with provider=$PROVIDER_ID baseUrl=$BASE_URL"
238
+ log "[dry-run] Would fetch upstream models from ${BLOCKRUN_API_BASE%/}/v1/models"
239
+ log "[dry-run] Would patch $OPENCLAW_JSON with provider=$PROVIDER_ID baseUrl=$BASE_URL and synced model list"
238
240
  if [[ "$NO_RESTART" != "1" ]]; then
239
241
  log "[dry-run] Would run: ${OPENCLAW_CMD[*]} gateway restart"
240
242
  fi
@@ -310,7 +312,7 @@ if [[ "$ready" != "1" ]]; then
310
312
  fi
311
313
 
312
314
  log "→ Patching OpenClaw provider config: $OPENCLAW_JSON"
313
- node - "$OPENCLAW_JSON" "$PROVIDER_ID" "$BASE_URL" "$MODEL_ID" <<'NODE'
315
+ node - "$OPENCLAW_JSON" "$PROVIDER_ID" "$BASE_URL" "$MODEL_ID" "$BLOCKRUN_API_BASE" "$GATEWAY_DIR/scripts/openclaw-model-sync.cjs" <<'NODE'
314
316
  const fs = require("fs");
315
317
  const path = require("path");
316
318
 
@@ -318,62 +320,65 @@ const configPath = process.argv[2];
318
320
  const providerId = process.argv[3];
319
321
  const baseUrl = process.argv[4];
320
322
  const defaultModelId = process.argv[5];
321
-
322
- let config = {};
323
- if (fs.existsSync(configPath)) {
324
- const raw = fs.readFileSync(configPath, "utf8");
325
- if (raw.trim()) config = JSON.parse(raw);
323
+ const blockrunApiBase = process.argv[6];
324
+ const helperPath = process.argv[7];
325
+ const {
326
+ extractUpstreamModels,
327
+ buildFallbackModels,
328
+ syncOpenClawConfig,
329
+ } = require(helperPath);
330
+
331
+ async function fetchModels(apiBase) {
332
+ const endpoint = `${String(apiBase || "").replace(/\/+$/, "")}/v1/models`;
333
+ try {
334
+ const response = await fetch(endpoint, { signal: AbortSignal.timeout(8000) });
335
+ if (!response.ok) throw new Error(`upstream models status=${response.status}`);
336
+ const payload = await response.json();
337
+ const models = extractUpstreamModels(payload);
338
+ if (!Array.isArray(models) || models.length === 0) {
339
+ throw new Error("upstream models payload empty");
340
+ }
341
+ return { models, source: "upstream", endpoint };
342
+ } catch (err) {
343
+ return {
344
+ models: buildFallbackModels(),
345
+ source: "fallback",
346
+ endpoint,
347
+ error: err instanceof Error ? err.message : String(err),
348
+ };
349
+ }
326
350
  }
327
351
 
328
- config.models ??= {};
329
- config.models.providers ??= {};
330
-
331
- const existing = config.models.providers[providerId];
332
- const blockrun = config.models.providers.blockrun;
333
- let provider = existing ? JSON.parse(JSON.stringify(existing)) : null;
334
- if (!provider && blockrun) {
335
- provider = JSON.parse(JSON.stringify(blockrun));
336
- }
337
- if (!provider) {
338
- provider = {
339
- api: "openai-completions",
340
- models: [
341
- {
342
- id: defaultModelId,
343
- name: defaultModelId,
344
- api: "openai-completions",
345
- reasoning: false,
346
- input: ["text"],
347
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
348
- contextWindow: 200000,
349
- maxTokens: 64000,
350
- },
351
- ],
352
- };
353
- }
354
-
355
- provider.baseUrl = baseUrl;
356
- provider.apiKey = "clawcredit-gateway";
357
- provider.api = "openai-completions";
358
- provider.models = Array.isArray(provider.models) ? provider.models : [];
359
- if (!provider.models.some((m) => m && typeof m.id === "string" && m.id === defaultModelId)) {
360
- provider.models.push({
361
- id: defaultModelId,
362
- name: defaultModelId,
363
- api: "openai-completions",
364
- reasoning: false,
365
- input: ["text"],
366
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
367
- contextWindow: 200000,
368
- maxTokens: 64000,
352
+ async function main() {
353
+ let config = {};
354
+ if (fs.existsSync(configPath)) {
355
+ const raw = fs.readFileSync(configPath, "utf8");
356
+ if (raw.trim()) config = JSON.parse(raw);
357
+ }
358
+
359
+ const modelResult = await fetchModels(blockrunApiBase);
360
+ const { updatedConfig, effectiveDefaultModelId } = syncOpenClawConfig(config, {
361
+ providerId,
362
+ baseUrl,
363
+ requestedDefaultModelId: defaultModelId,
364
+ models: modelResult.models,
369
365
  });
370
- }
371
366
 
372
- config.models.providers[providerId] = provider;
367
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
368
+ fs.writeFileSync(configPath, JSON.stringify(updatedConfig, null, 2));
369
+ console.log(`updated ${configPath}`);
370
+ console.log(`provider ${providerId} models synced: ${modelResult.models.length} (${modelResult.source})`);
371
+ console.log(`provider ${providerId} default model: ${effectiveDefaultModelId}`);
372
+ if (modelResult.source === "fallback") {
373
+ console.log(`provider ${providerId} sync warning: ${modelResult.error}`);
374
+ }
375
+ }
373
376
 
374
- fs.mkdirSync(path.dirname(configPath), { recursive: true });
375
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
376
- console.log(`updated ${configPath}`);
377
+ main().catch((err) => {
378
+ const msg = err instanceof Error ? err.message : String(err);
379
+ console.error(`failed to patch ${configPath}: ${msg}`);
380
+ process.exit(1);
381
+ });
377
382
  NODE
378
383
 
379
384
  if [[ "$NO_RESTART" != "1" ]]; then