OpenClaw / hf-worker-adapter.mjs
morris5444's picture
Update hf-worker-adapter.mjs
2d0a3f9 verified
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import http from "node:http";
import crypto from "node:crypto";
import { URL } from "node:url";
import httpProxy from "http-proxy";
const PUBLIC_PORT = Number(process.env.PORT || 7860);
const INTERNAL_PORT = Number(process.env.OPENCLAW_INTERNAL_PORT || 18789);
const INTERNAL_BASE = `http://127.0.0.1:${INTERNAL_PORT}`;
const STATE_DIR = process.env.OPENCLAW_STATE_DIR || "/app/.openclaw";
const RUNTIME_CONFIG_PATH = path.join(STATE_DIR, "worker-runtime.json");
const USAGE_LOG_PATH = path.join(STATE_DIR, "worker-usage.jsonl");
const GATEWAY_TOKEN = String(
process.env.OPENCLAW_GATEWAY_TOKEN || process.env.OPENCLAW_TOKEN || ""
).trim();
const WORKER_TOKEN = String(
process.env.OPENCLAW_WORKER_TOKEN || ""
).trim();
const ADMIN_TOKEN = String(
process.env.OPENCLAW_ADMIN_TOKEN || WORKER_TOKEN
).trim();
const GROQ_API_KEY = String(
process.env.GROQ_API_KEY || ""
).trim();
const HF_PROVIDER_API_KEY = String(
process.env.OPENCLAW_HUGGINGFACE_API_KEY || process.env.HF_TOKEN || ""
).trim();
if (!GATEWAY_TOKEN) {
console.error("[hf-worker-adapter] Missing OPENCLAW_GATEWAY_TOKEN / OPENCLAW_TOKEN");
process.exit(1);
}
if (!WORKER_TOKEN) {
console.error("[hf-worker-adapter] Missing OPENCLAW_WORKER_TOKEN");
process.exit(1);
}
function nowIso() {
return new Date().toISOString();
}
function ensureState() {
fs.mkdirSync(STATE_DIR, { recursive: true });
if (!fs.existsSync(RUNTIME_CONFIG_PATH)) {
fs.writeFileSync(
RUNTIME_CONFIG_PATH,
JSON.stringify(
{
version: 1,
updatedAt: nowIso(),
defaults: {
provider: "openclaw",
agentId: "main",
model: "",
sessionKey: "webchat:languageapp",
timeoutMs: 120000,
temperature: 0.2,
maxTokens: null,
},
providers: {
openclaw: {
enabled: true,
label: "OpenClaw (internal)",
models: [{ id: "openclaw:main", provider: "openclaw", label: "OpenClaw Agent: main", agentId: "main" }],
},
groq: {
enabled: false,
label: "Groq",
baseUrl: "https://api.groq.com/openai/v1/chat/completions",
models: [],
},
huggingface: {
enabled: false,
label: "Hugging Face",
baseUrl: "https://router.huggingface.co/v1/chat/completions",
models: [],
},
},
},
null,
2
),
"utf8"
);
}
if (!fs.existsSync(USAGE_LOG_PATH)) {
fs.writeFileSync(USAGE_LOG_PATH, "", "utf8");
}
}
function readRuntimeConfig() {
ensureState();
try {
return JSON.parse(fs.readFileSync(RUNTIME_CONFIG_PATH, "utf8"));
} catch {
return {
version: 1,
updatedAt: nowIso(),
defaults: {
provider: "openclaw",
agentId: "main",
model: "",
sessionKey: "webchat:languageapp",
timeoutMs: 120000,
temperature: 0.2,
maxTokens: null,
},
providers: {},
};
}
}
function writeRuntimeConfig(cfg) {
cfg.updatedAt = nowIso();
fs.writeFileSync(RUNTIME_CONFIG_PATH, JSON.stringify(cfg, null, 2), "utf8");
}
function appendUsage(entry) {
ensureState();
fs.appendFileSync(USAGE_LOG_PATH, `${JSON.stringify(entry)}\n`, "utf8");
}
function readUsage(limit = 100) {
ensureState();
const raw = fs.readFileSync(USAGE_LOG_PATH, "utf8");
const lines = raw
.split("\n")
.map((line) => line.trim())
.filter(Boolean);
const rows = lines
.slice(-Math.max(1, limit))
.map((line) => {
try {
return JSON.parse(line);
} catch {
return null;
}
})
.filter(Boolean);
return rows.reverse();
}
function summarizeUsage(entries) {
const byProvider = {};
let successCount = 0;
let errorCount = 0;
for (const row of entries) {
const provider = row.provider || "unknown";
byProvider[provider] ??= {
requests: 0,
success: 0,
errors: 0,
inputTokens: 0,
outputTokens: 0,
totalTokens: 0,
};
byProvider[provider].requests += 1;
byProvider[provider].inputTokens += Number(row.usage?.prompt_tokens || 0);
byProvider[provider].outputTokens += Number(row.usage?.completion_tokens || 0);
byProvider[provider].totalTokens += Number(row.usage?.total_tokens || 0);
if (row.success) {
successCount += 1;
byProvider[provider].success += 1;
} else {
errorCount += 1;
byProvider[provider].errors += 1;
}
}
return {
total: entries.length,
successCount,
errorCount,
byProvider,
};
}
function json(res, code, payload) {
const body = JSON.stringify(payload);
res.writeHead(code, {
"content-type": "application/json; charset=utf-8",
"content-length": Buffer.byteLength(body),
"cache-control": "no-store",
});
res.end(body);
}
function safeEqual(a, b) {
const ba = Buffer.from(String(a || ""));
const bb = Buffer.from(String(b || ""));
return ba.length === bb.length && crypto.timingSafeEqual(ba, bb);
}
function parseBearer(req) {
const auth = String(req.headers.authorization || "");
const match = auth.match(/^Bearer\s+(.+)$/i);
return match ? match[1] : "";
}
function requireWorkerBearer(req) {
const token = parseBearer(req);
return token && safeEqual(token, WORKER_TOKEN);
}
function requireAdminBearer(req) {
const token = parseBearer(req);
return token && safeEqual(token, ADMIN_TOKEN);
}
function readBody(req) {
return new Promise((resolve, reject) => {
let data = "";
req.setEncoding("utf8");
req.on("data", (chunk) => {
data += chunk;
});
req.on("end", () => resolve(data));
req.on("error", reject);
});
}
function normalizeMessages(body, fallbackMessage = "") {
if (Array.isArray(body.messages) && body.messages.length > 0) return body.messages;
if (fallbackMessage) return [{ role: "user", content: fallbackMessage }];
return [];
}
function extractTextFromOpenAIResponse(data) {
const content = data?.choices?.[0]?.message?.content;
if (typeof content === "string") return content.trim();
if (Array.isArray(content)) {
return content
.map((part) => {
if (typeof part === "string") return part;
if (part?.type === "text") return String(part?.text || "");
return String(part?.text || part?.content || "");
})
.join("")
.trim();
}
if (typeof data?.output_text === "string") return data.output_text.trim();
if (typeof data?.text === "string") return data.text.trim();
if (typeof data?.message === "string") return data.message.trim();
return "";
}
function extractUsage(data) {
return {
prompt_tokens: Number(data?.usage?.prompt_tokens || 0),
completion_tokens: Number(data?.usage?.completion_tokens || 0),
total_tokens: Number(data?.usage?.total_tokens || 0),
};
}
function classifyError({ statusCode, message }) {
const msg = String(message || "").toLowerCase();
if (statusCode === 401 || statusCode === 403) return "auth_error";
if (statusCode === 402) return "billing_error";
if (statusCode === 408) return "timeout";
if (statusCode === 429) return "rate_limit";
if (msg.includes("depleted your monthly included credits")) return "billing_error";
if (msg.includes("purchase pre-paid credits")) return "billing_error";
if (msg.includes("insufficient credits")) return "billing_error";
if (msg.includes("payment required")) return "billing_error";
if (msg.includes("aborted")) return "timeout";
if (msg.includes("timed out")) return "timeout";
if (msg.includes("network")) return "network_error";
if (msg.includes("fetch failed")) return "network_error";
if (msg.includes("econnrefused")) return "network_error";
if (msg.includes("unexpected schema")) return "unexpected_schema";
if (msg.includes("invalid_json")) return "bad_response";
return "failed";
}
function detectProviderFailureInText(text) {
const t = String(text || "").trim();
const lower = t.toLowerCase();
if (!t) return null;
if (
/^402\b/.test(t) ||
lower.includes("depleted your monthly included credits") ||
lower.includes("purchase pre-paid credits") ||
lower.includes("insufficient credits") ||
lower.includes("payment required")
) {
return {
kind: "billing_error",
statusCode: 402,
message: t,
};
}
return null;
}
function buildPreview(text, maxLen = 240) {
const value = String(text || "").replace(/\s+/g, " ").trim();
return value.length > maxLen ? `${value.slice(0, maxLen)}…` : value;
}
async function probeInternalGateway() {
try {
const res = await fetch(`${INTERNAL_BASE}/`, {
method: "GET",
redirect: "manual",
signal: AbortSignal.timeout(5000),
});
return {
reachable: true,
statusCode: res.status,
};
} catch (err) {
return {
reachable: false,
error: err?.message || String(err),
};
}
}
async function callOpenClawInternal({
agentId,
model,
sessionKey,
timeoutMs,
temperature,
maxTokens,
messages,
}) {
const effectiveModel =
typeof model === "string" && model.trim().startsWith("openclaw:")
? model.trim()
: `openclaw:${agentId}`;
const payload = {
model: effectiveModel,
stream: false,
messages,
user: sessionKey,
temperature,
};
if (Number.isFinite(maxTokens) && maxTokens > 0) {
payload.max_tokens = maxTokens;
}
const res = await fetch(`${INTERNAL_BASE}/v1/chat/completions`, {
method: "POST",
headers: {
authorization: `Bearer ${GATEWAY_TOKEN}`,
"content-type": "application/json",
"x-openclaw-agent-id": agentId,
"x-openclaw-session-key": sessionKey,
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(timeoutMs),
});
const rawText = await res.text();
let data = null;
try {
data = rawText ? JSON.parse(rawText) : null;
} catch {
data = null;
}
const responseText = extractTextFromOpenAIResponse(data) || rawText || "";
const embeddedFailure = detectProviderFailureInText(responseText);
if (!res.ok) {
const err = new Error(
data?.error?.message || rawText || `openclaw_http_${res.status}`
);
err.statusCode = res.status;
err.responseText = responseText;
throw err;
}
if (embeddedFailure) {
const err = new Error(embeddedFailure.message);
err.statusCode = embeddedFailure.statusCode;
err.responseText = responseText;
err.errorKind = embeddedFailure.kind;
throw err;
}
return {
provider: "openclaw",
model: effectiveModel,
upstreamStatus: res.status,
text: responseText,
usage: extractUsage(data),
raw: data,
};
}
async function callOpenAICompatibleProvider({
provider,
baseUrl,
apiKey,
model,
sessionKey,
timeoutMs,
temperature,
maxTokens,
messages,
}) {
const payload = {
model,
stream: false,
messages,
temperature,
user: sessionKey,
};
if (Number.isFinite(maxTokens) && maxTokens > 0) {
payload.max_tokens = maxTokens;
}
const res = await fetch(baseUrl, {
method: "POST",
headers: {
authorization: `Bearer ${apiKey}`,
"content-type": "application/json",
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(timeoutMs),
});
const rawText = await res.text();
let data = null;
try {
data = rawText ? JSON.parse(rawText) : null;
} catch {
data = null;
}
const responseText = extractTextFromOpenAIResponse(data) || rawText || "";
const embeddedFailure = detectProviderFailureInText(responseText);
if (!res.ok) {
const err = new Error(
data?.error?.message || responseText || rawText || `${provider}_http_${res.status}`
);
err.statusCode = res.status;
err.responseText = responseText || rawText;
throw err;
}
if (embeddedFailure) {
const err = new Error(embeddedFailure.message);
err.statusCode = embeddedFailure.statusCode;
err.responseText = responseText;
err.errorKind = embeddedFailure.kind;
throw err;
}
return {
provider,
model,
upstreamStatus: res.status,
text: responseText,
usage: extractUsage(data),
raw: data,
};
}
function selectModelForProvider(runtimeCfg, provider, requestedModel) {
const byProvider = runtimeCfg.providers?.[provider];
if (requestedModel && String(requestedModel).trim()) return String(requestedModel).trim();
if (runtimeCfg.defaults?.model && runtimeCfg.defaults.provider === provider) {
return String(runtimeCfg.defaults.model).trim();
}
const firstModel = byProvider?.models?.[0]?.id;
return firstModel || "";
}
async function executeRequest(body) {
const cfg = readRuntimeConfig();
const provider = String(
body.provider || cfg.defaults?.provider || "openclaw"
).trim();
const sessionKey = String(
body.sessionKey || body.user || cfg.defaults?.sessionKey || "webchat:languageapp"
).trim();
const agentId = String(
body.agentId || body.agent || cfg.defaults?.agentId || "main"
).trim();
const timeoutMs = Number(
body.timeoutMs || cfg.defaults?.timeoutMs || 120000
);
const temperature = Number.isFinite(Number(body.temperature))
? Number(body.temperature)
: Number(cfg.defaults?.temperature ?? 0.2);
const maxTokens =
body.maxTokens === null
? null
: body.maxTokens !== undefined
? Number(body.maxTokens)
: cfg.defaults?.maxTokens;
const message = String(
body.message || body.prompt || body.task || body.input?.message || ""
).trim();
const messages = normalizeMessages(body, message);
if (!messages.length) {
const err = new Error("missing_message");
err.statusCode = 400;
err.errorKind = "bad_request";
throw err;
}
const requestedModel = String(body.model || "").trim();
const effectiveModel = selectModelForProvider(cfg, provider, requestedModel);
if (provider === "openclaw") {
return callOpenClawInternal({
agentId,
model: effectiveModel,
sessionKey,
timeoutMs,
temperature,
maxTokens,
messages,
});
}
if (provider === "groq") {
if (!GROQ_API_KEY) {
const err = new Error("groq_api_key_missing");
err.statusCode = 500;
err.errorKind = "misconfiguration";
throw err;
}
return callOpenAICompatibleProvider({
provider: "groq",
baseUrl:
cfg.providers?.groq?.baseUrl ||
"https://api.groq.com/openai/v1/chat/completions",
apiKey: GROQ_API_KEY,
model: effectiveModel,
sessionKey,
timeoutMs,
temperature,
maxTokens,
messages,
});
}
if (provider === "huggingface") {
if (!HF_PROVIDER_API_KEY) {
const err = new Error("huggingface_api_key_missing");
err.statusCode = 500;
err.errorKind = "misconfiguration";
throw err;
}
return callOpenAICompatibleProvider({
provider: "huggingface",
baseUrl:
cfg.providers?.huggingface?.baseUrl ||
"https://router.huggingface.co/v1/chat/completions",
apiKey: HF_PROVIDER_API_KEY,
model: effectiveModel,
sessionKey,
timeoutMs,
temperature,
maxTokens,
messages,
});
}
const err = new Error(`unsupported_provider:${provider}`);
err.statusCode = 400;
err.errorKind = "bad_request";
throw err;
}
function usageEntryBase({
requestId,
provider,
model,
agentId,
sessionKey,
source,
operation,
startedAt,
}) {
return {
requestId,
timestamp: startedAt,
provider,
model,
agentId,
sessionKey,
source,
operation,
};
}
// Proxy für OpenClaw UI / WS
const proxy = httpProxy.createProxyServer({
target: INTERNAL_BASE,
ws: true,
xfwd: true,
changeOrigin: false,
});
proxy.on("error", (err, req, res) => {
try {
if (res && !res.headersSent) {
res.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
}
res?.end(`Bad gateway: ${err?.message || String(err)}`);
} catch {}
});
const server = http.createServer(async (req, res) => {
ensureState();
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
// --- Worker health --------------------------------------------------------
if (req.method === "GET" && url.pathname === "/api/openclaw/health") {
if (!requireWorkerBearer(req)) {
return json(res, 401, { ok: false, error: "unauthorized" });
}
const probe = await probeInternalGateway();
const runtimeCfg = readRuntimeConfig();
return json(res, probe.reachable ? 200 : 502, {
ok: probe.reachable,
configured: true,
reachable: probe.reachable,
mode: "hf-openclaw-adapter",
detail: probe.reachable
? `internal gateway reachable (HTTP ${probe.statusCode})`
: probe.error || "internal gateway unreachable",
defaults: runtimeCfg.defaults,
providers: Object.fromEntries(
Object.entries(runtimeCfg.providers || {}).map(([key, value]) => [
key,
{
enabled: Boolean(value?.enabled),
modelCount: Array.isArray(value?.models) ? value.models.length : 0,
},
])
),
});
}
// --- Worker execute -------------------------------------------------------
if (req.method === "POST" && url.pathname === "/api/openclaw/tasks/execute") {
if (!requireWorkerBearer(req)) {
return json(res, 401, { ok: false, error: "unauthorized", errorKind: "auth_error" });
}
let body = {};
try {
const raw = await readBody(req);
body = raw ? JSON.parse(raw) : {};
} catch {
return json(res, 400, { ok: false, error: "invalid_json", errorKind: "bad_request" });
}
const runtimeCfg = readRuntimeConfig();
const provider = String(body.provider || runtimeCfg.defaults?.provider || "openclaw").trim();
const model = String(body.model || selectModelForProvider(runtimeCfg, provider, "") || "").trim();
const agentId = String(body.agentId || body.agent || runtimeCfg.defaults?.agentId || "main").trim();
const sessionKey = String(body.sessionKey || body.user || runtimeCfg.defaults?.sessionKey || "webchat:languageapp").trim();
const requestId = crypto.randomUUID();
const startedAt = nowIso();
const startedPerf = Date.now();
try {
const result = await executeRequest(body);
const latencyMs = Date.now() - startedPerf;
const preview = buildPreview(result.text);
const entry = {
...usageEntryBase({
requestId,
provider: result.provider,
model: result.model,
agentId,
sessionKey,
source: "external-worker",
operation: "execute",
startedAt,
}),
finishedAt: nowIso(),
success: true,
statusCode: result.upstreamStatus,
latencyMs,
errorKind: null,
errorMessage: null,
preview,
usage: result.usage,
};
appendUsage(entry);
return json(res, 200, {
ok: true,
mode: "hf-openclaw-adapter",
provider: result.provider,
model: result.model,
agentId,
sessionKey,
output: result.text,
text: result.text,
message: result.text,
usage: result.usage,
requestId,
latencyMs,
raw: result.raw,
});
} catch (err) {
const latencyMs = Date.now() - startedPerf;
const statusCode = Number(err?.statusCode || 502);
const errorMessage = String(err?.message || err || "unknown_error");
const errorKind = String(err?.errorKind || classifyError({
statusCode,
message: errorMessage,
}));
const entry = {
...usageEntryBase({
requestId,
provider,
model,
agentId,
sessionKey,
source: "external-worker",
operation: "execute",
startedAt,
}),
finishedAt: nowIso(),
success: false,
statusCode,
latencyMs,
errorKind,
errorMessage,
preview: buildPreview(err?.responseText || errorMessage),
usage: {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
},
};
appendUsage(entry);
return json(res, statusCode >= 400 && statusCode <= 599 ? statusCode : 502, {
ok: false,
mode: "hf-openclaw-adapter",
provider,
model,
agentId,
sessionKey,
requestId,
latencyMs,
error: errorMessage,
errorKind,
preview: buildPreview(err?.responseText || errorMessage),
});
}
}
// --- Admin: config --------------------------------------------------------
if (req.method === "GET" && url.pathname === "/api/openclaw/admin/config") {
if (!requireAdminBearer(req)) {
return json(res, 401, { ok: false, error: "unauthorized" });
}
return json(res, 200, {
ok: true,
config: readRuntimeConfig(),
});
}
if (req.method === "PUT" && url.pathname === "/api/openclaw/admin/config") {
if (!requireAdminBearer(req)) {
return json(res, 401, { ok: false, error: "unauthorized" });
}
let body = {};
try {
const raw = await readBody(req);
body = raw ? JSON.parse(raw) : {};
} catch {
return json(res, 400, { ok: false, error: "invalid_json" });
}
const cfg = readRuntimeConfig();
const nextDefaults = body.defaults || {};
const allowedProviders = ["openclaw", "groq", "huggingface"];
const nextProvider = String(nextDefaults.provider || cfg.defaults.provider || "openclaw").trim();
if (!allowedProviders.includes(nextProvider)) {
return json(res, 400, { ok: false, error: "invalid_provider" });
}
cfg.defaults.provider = nextProvider;
cfg.defaults.agentId = String(nextDefaults.agentId || cfg.defaults.agentId || "main").trim();
cfg.defaults.model = String(nextDefaults.model || cfg.defaults.model || "").trim();
cfg.defaults.sessionKey = String(nextDefaults.sessionKey || cfg.defaults.sessionKey || "webchat:languageapp").trim();
if (nextDefaults.timeoutMs !== undefined) {
const timeoutMs = Number(nextDefaults.timeoutMs);
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
return json(res, 400, { ok: false, error: "invalid_timeoutMs" });
}
cfg.defaults.timeoutMs = timeoutMs;
}
if (nextDefaults.temperature !== undefined) {
const temperature = Number(nextDefaults.temperature);
if (!Number.isFinite(temperature)) {
return json(res, 400, { ok: false, error: "invalid_temperature" });
}
cfg.defaults.temperature = temperature;
}
if (nextDefaults.maxTokens !== undefined) {
if (nextDefaults.maxTokens === null || nextDefaults.maxTokens === "") {
cfg.defaults.maxTokens = null;
} else {
const maxTokens = Number(nextDefaults.maxTokens);
if (!Number.isFinite(maxTokens) || maxTokens <= 0) {
return json(res, 400, { ok: false, error: "invalid_maxTokens" });
}
cfg.defaults.maxTokens = maxTokens;
}
}
if (body.providers && typeof body.providers === "object") {
for (const [provider, patch] of Object.entries(body.providers)) {
if (!cfg.providers?.[provider]) continue;
if (patch?.enabled !== undefined) {
cfg.providers[provider].enabled = Boolean(patch.enabled);
}
if (typeof patch?.baseUrl === "string" && patch.baseUrl.trim()) {
cfg.providers[provider].baseUrl = patch.baseUrl.trim();
}
}
}
writeRuntimeConfig(cfg);
return json(res, 200, {
ok: true,
config: cfg,
});
}
// --- Admin: models --------------------------------------------------------
if (req.method === "GET" && url.pathname === "/api/openclaw/admin/models") {
if (!requireAdminBearer(req)) {
return json(res, 401, { ok: false, error: "unauthorized" });
}
const cfg = readRuntimeConfig();
const models = Object.entries(cfg.providers || {})
.flatMap(([provider, value]) =>
Array.isArray(value?.models)
? value.models.map((model) => ({
provider,
enabled: Boolean(value?.enabled),
...model,
}))
: []
);
return json(res, 200, {
ok: true,
models,
});
}
// --- Admin: providers -----------------------------------------------------
if (req.method === "GET" && url.pathname === "/api/openclaw/admin/providers") {
if (!requireAdminBearer(req)) {
return json(res, 401, { ok: false, error: "unauthorized" });
}
const cfg = readRuntimeConfig();
return json(res, 200, {
ok: true,
providers: {
openclaw: {
enabled: Boolean(cfg.providers?.openclaw?.enabled),
label: cfg.providers?.openclaw?.label || "OpenClaw (internal)",
authConfigured: Boolean(GATEWAY_TOKEN),
baseUrl: INTERNAL_BASE,
},
groq: {
enabled: Boolean(cfg.providers?.groq?.enabled),
label: cfg.providers?.groq?.label || "Groq",
authConfigured: Boolean(GROQ_API_KEY),
baseUrl: cfg.providers?.groq?.baseUrl || "",
},
huggingface: {
enabled: Boolean(cfg.providers?.huggingface?.enabled),
label: cfg.providers?.huggingface?.label || "Hugging Face",
authConfigured: Boolean(HF_PROVIDER_API_KEY),
baseUrl: cfg.providers?.huggingface?.baseUrl || "",
},
},
});
}
// --- Admin: usage ---------------------------------------------------------
if (req.method === "GET" && url.pathname === "/api/openclaw/admin/usage") {
if (!requireAdminBearer(req)) {
return json(res, 401, { ok: false, error: "unauthorized" });
}
const limit = Math.min(500, Math.max(1, Number(url.searchParams.get("limit") || 100)));
const rows = readUsage(limit);
return json(res, 200, {
ok: true,
rows,
summary: summarizeUsage(rows),
});
}
// --- Admin: probe ---------------------------------------------------------
if (req.method === "POST" && url.pathname === "/api/openclaw/admin/probe") {
if (!requireAdminBearer(req)) {
return json(res, 401, { ok: false, error: "unauthorized" });
}
let body = {};
try {
const raw = await readBody(req);
body = raw ? JSON.parse(raw) : {};
} catch {
body = {};
}
try {
const result = await executeRequest({
provider: body.provider,
model: body.model,
agentId: body.agentId || "main",
sessionKey: body.sessionKey || "webchat:languageapp",
timeoutMs: body.timeoutMs || 120000,
message: 'Antworte exakt mit OPENCLAW_RUNTIME_PROBE_OK und sonst nichts.',
});
const ok = String(result.text || "").trim() === "OPENCLAW_RUNTIME_PROBE_OK";
return json(res, ok ? 200 : 502, {
ok,
provider: result.provider,
model: result.model,
text: result.text,
usage: result.usage,
});
} catch (err) {
return json(res, Number(err?.statusCode || 502), {
ok: false,
error: String(err?.message || err),
errorKind: String(err?.errorKind || classifyError({
statusCode: Number(err?.statusCode || 502),
message: String(err?.message || err),
})),
});
}
}
// --- Alles andere -> OpenClaw UI / WS ------------------------------------
proxy.web(req, res);
});
server.on("upgrade", (req, socket, head) => {
proxy.ws(req, socket, head);
});
server.listen(PUBLIC_PORT, "0.0.0.0", () => {
ensureState();
console.log(
`[hf-worker-adapter] listening on :${PUBLIC_PORT}, proxying UI/WS to ${INTERNAL_BASE}`
);
});