From 4188a4d74a2e8cae4e184260fdc84ee3e8a35f73 Mon Sep 17 00:00:00 2001 From: OpenClaw Assistant Date: Mon, 23 Feb 2026 08:30:07 +0000 Subject: [PATCH] Add local OpenClaw endpoint server for vk-bridge --- skills/dist/vk-bridge.skill | Bin 4494 -> 7133 bytes skills/vk-bridge/SKILL.md | 10 +- skills/vk-bridge/references/.env.example | 13 +- .../scripts/vk-openclaw-endpoint.mjs | 166 ++++++++++++++++++ 4 files changed, 185 insertions(+), 4 deletions(-) create mode 100755 skills/vk-bridge/scripts/vk-openclaw-endpoint.mjs diff --git a/skills/dist/vk-bridge.skill b/skills/dist/vk-bridge.skill index 3148af159fab5970aa1cadb9b7245e0360e4ffa2..55bf7c794b9a209b565757a9533fc8bb55acf4c6 100644 GIT binary patch delta 4500 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 delta 1890 zcmZvddpOgJ8^AZ$QSNiwPM5`yY#7t5FbTOUml0Ym8`V@SYpC-@msKL2RjwU#Tkf|> zxvUa8lo1V?TOPM0w?ny1Ir{zcJAa(_ectDN-uM0U^E{t-Xn&Fp;OQbM1qFdX5KxRA zG4Lh^S+GkQ1oGMj0;z6o8Rt;sh%><<6k{(JCs$W=Snv(sAP=}B^y&!7)cFLV+@t=~`RG!2-$c_X(~lcB`d_{$ zb<^kD*KFd8?H{CtX!vezl$#`80O1${$6{^LaGv%o8mXpSMEJ73%aP z)Ob5SwnwQSepz6Ghkn`rjrC=TMTnf{rQQ*JoGxaE5+idgzPtMX#}6OydwTK5q4wGr z`k3?lJsLyD-P+OUJSlXL{XkcFMT7cm0(Lqo|Bg}b#0;RFeJB#FLwr7n9nn6LQlj63 z-Od)%H=<*!p5UL;B}1;_B{G-ig@e}%3BX;^p~v#6vuyw8BUA5E$P7f|stOn1Sc^KT z(*DU0Tkpt=4s>e#cvs zD=ukO_>|*W`BkU0dPl^?FoOJSjITs>O=DEq$~2#e{ts1a0`}7YUTG zOnx4S(!nb~lw$QE|8^}pO`xrx-)mM$HLNIgth9~ySiU{lGgQ@}u&{(wy)6~1*cDM` zjgfOVzkn4NioT3Zg?nYxv~Y63@@n$>=ZUN@a^Ys?Ny9B~F@=44Q_|htV?PJTKWc+# zqxT)LKFWhLhjsK%;|)>+BvT~D`9NJ0SaM_O5#y?tfswnnajA*7Ak`=Rp4DXVN#VJ} zet5O`7L7vL^%Q8@nfh~cit16-W)gOvPiq9aAx&n`w|(L$XAs%%vk7lj$!OB2OY-{9 zQah{#?Jo>EP^g0$9-6k+EW@=h>lC+Xi_3i(`?{UvSh)B^=MO;_pv9CEB|uD-_U47w zQGoPf@f-CFZ}QPHwQa>^pG`?y-TY#mf3!kx;Y&^jq7yFR_@`P^D)ttaCbZaLIV%}|hZk}aDY$`nh6gs>okOmU zv@GgzXovJOF74)ijwjd!E`da*s4FI+_R$i!4>gU+o7Up$)HceLCOys7Gz$&Ys9IXn zS8_o5UQCeUSXw!@+r_@VaO%$>7@oqk;x>mhW)6kiGn8A&N4nnr}$8y~FUG`%Ss7~ z4d8(cHN5OG+UC3LyOoP(R{39(OQUK!-*WZ(zj6d8)u$# zpy2MY11C|)JVoJ<^c8LH*@ZK8;oRhV+E{DzKnuEA1zo(jeC z$u%YCyq_jrn!k{+cfnyiQ^++rEwBVs^1nVGI}e80{a?(su>CKp<2 { + let size = 0; + const chunks = []; + req.on('data', (c) => { + size += c.length; + if (size > limit) { + reject(new Error('Payload too large')); + req.destroy(); + return; + } + chunks.push(c); + }); + req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); + req.on('error', reject); + }); +} + +function extractText(responseJson) { + if (typeof responseJson?.output_text === 'string' && responseJson.output_text.trim()) { + return responseJson.output_text.trim(); + } + const out = responseJson?.output; + if (!Array.isArray(out)) return ''; + for (const item of out) { + if (item?.type !== 'message' || !Array.isArray(item?.content)) continue; + for (const c of item.content) { + if (c?.type === 'output_text' && typeof c.text === 'string' && c.text.trim()) { + return c.text.trim(); + } + } + } + return ''; +} + +async function askOpenClaw(payload) { + const userText = String(payload?.text || '').trim(); + if (!userText) return { silent: true }; + + const reqBody = { + model: CFG.agentModel, + user: `vk:${payload.group_id}:${payload.user_id}`, + max_output_tokens: 350, + instructions: SYSTEM_INSTRUCTIONS, + input: [ + { + type: 'message', + role: 'user', + content: [ + { + type: 'input_text', + text: `VK message from user ${payload.user_id}: ${userText}`, + }, + ], + }, + ], + }; + + const ctrl = new AbortController(); + const t = setTimeout(() => ctrl.abort(), CFG.timeoutMs); + try { + const res = await fetch(CFG.openclawUrl, { + method: 'POST', + headers: { + 'content-type': 'application/json', + authorization: `Bearer ${CFG.openclawToken}`, + }, + body: JSON.stringify(reqBody), + signal: ctrl.signal, + }); + + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new Error(`OpenClaw HTTP ${res.status} ${text}`.trim()); + } + + const json = await res.json(); + const reply = extractText(json); + if (!reply) return { silent: true }; + return { reply }; + } finally { + clearTimeout(t); + } +} + +const server = http.createServer(async (req, res) => { + if (req.method === 'GET' && req.url === '/healthz') { + res.writeHead(200, { 'content-type': 'application/json' }); + res.end(JSON.stringify({ ok: true })); + return; + } + + if (req.method !== 'POST' || req.url !== CFG.path) { + res.writeHead(404, { 'content-type': 'application/json' }); + res.end(JSON.stringify({ error: 'not_found' })); + return; + } + + try { + const raw = await readBody(req, CFG.maxBodyBytes); + const body = JSON.parse(raw || '{}'); + const out = await askOpenClaw(body); + res.writeHead(200, { 'content-type': 'application/json' }); + res.end(JSON.stringify(out)); + } catch (e) { + res.writeHead(500, { 'content-type': 'application/json' }); + res.end(JSON.stringify({ error: 'bridge_failed', detail: e.message })); + } +}); + +server.listen(CFG.bindPort, CFG.bindHost, () => { + console.log(`[vk-endpoint] listening on http://${CFG.bindHost}:${CFG.bindPort}${CFG.path}`); +});