@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 +24 -5
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/package.json +4 -4
- package/scripts/openclaw-model-sync.cjs +152 -0
- package/scripts/setup-openclaw-clawcredit-gateway.sh +62 -57
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=
|
|
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: "
|
|
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/
|
|
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":"
|
|
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:
|
|
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
|
|
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 ||
|
|
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
|
|
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.
|
|
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": "
|
|
10
|
-
"clawcredit-blockrun-sdk": "
|
|
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="
|
|
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="
|
|
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:
|
|
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 <
|
|
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
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
329
|
-
config
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
console.
|
|
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
|