feat(openrouter):add claw-x header (#213)
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Haze <hazeone@users.noreply.github.com>
This commit is contained in:
@@ -6,7 +6,7 @@ import { app } from 'electron';
|
||||
import path from 'path';
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import { EventEmitter } from 'events';
|
||||
import { existsSync } from 'fs';
|
||||
import { existsSync, writeFileSync } from 'fs';
|
||||
import WebSocket from 'ws';
|
||||
import { PORTS } from '../utils/config';
|
||||
import {
|
||||
@@ -107,6 +107,57 @@ function getNodeExecutablePath(): string {
|
||||
return process.execPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the gateway fetch-preload script exists in userData and return
|
||||
* its absolute path. The script patches globalThis.fetch to inject
|
||||
* ClawX app-attribution headers (HTTP-Referer, X-Title) for OpenRouter
|
||||
* API requests, overriding the OpenClaw runner's hardcoded defaults.
|
||||
*
|
||||
* Inlined here so it works in dev, packaged, and asar modes without
|
||||
* extra build config. Loaded by the Gateway child process via
|
||||
* NODE_OPTIONS --require.
|
||||
*/
|
||||
const GATEWAY_FETCH_PRELOAD_SOURCE = `'use strict';
|
||||
(function () {
|
||||
var _f = globalThis.fetch;
|
||||
if (typeof _f !== 'function') return;
|
||||
if (globalThis.__clawxFetchPatched) return;
|
||||
globalThis.__clawxFetchPatched = true;
|
||||
|
||||
globalThis.fetch = function clawxFetch(input, init) {
|
||||
var url =
|
||||
typeof input === 'string' ? input
|
||||
: input && typeof input === 'object' && typeof input.url === 'string'
|
||||
? input.url : '';
|
||||
|
||||
if (url.indexOf('openrouter.ai') !== -1) {
|
||||
init = init ? Object.assign({}, init) : {};
|
||||
var prev = init.headers;
|
||||
var flat = {};
|
||||
if (prev && typeof prev.forEach === 'function') {
|
||||
prev.forEach(function (v, k) { flat[k] = v; });
|
||||
} else if (prev && typeof prev === 'object') {
|
||||
Object.assign(flat, prev);
|
||||
}
|
||||
delete flat['http-referer'];
|
||||
delete flat['HTTP-Referer'];
|
||||
delete flat['x-title'];
|
||||
delete flat['X-Title'];
|
||||
flat['HTTP-Referer'] = 'https://claw-x.com';
|
||||
flat['X-Title'] = 'ClawX';
|
||||
init.headers = flat;
|
||||
}
|
||||
return _f.call(globalThis, input, init);
|
||||
};
|
||||
})();
|
||||
`;
|
||||
|
||||
function ensureGatewayFetchPreload(): string {
|
||||
const dest = path.join(app.getPath('userData'), 'gateway-fetch-preload.cjs');
|
||||
try { writeFileSync(dest, GATEWAY_FETCH_PRELOAD_SOURCE, 'utf-8'); } catch { /* best-effort */ }
|
||||
return dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gateway Manager
|
||||
* Handles starting, stopping, and communicating with the OpenClaw Gateway
|
||||
@@ -814,6 +865,19 @@ export class GatewayManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
// Inject fetch preload so OpenRouter requests carry ClawX headers.
|
||||
// The preload patches globalThis.fetch before any module loads.
|
||||
try {
|
||||
const preloadPath = ensureGatewayFetchPreload();
|
||||
if (existsSync(preloadPath)) {
|
||||
const quoted = `"${preloadPath}"`;
|
||||
const opts = spawnEnv['NODE_OPTIONS'] ?? '';
|
||||
spawnEnv['NODE_OPTIONS'] = `${opts} --require ${quoted}`.trim();
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn('Failed to set up OpenRouter headers preload:', err);
|
||||
}
|
||||
|
||||
const useShell = !app.isPackaged && process.platform === 'win32';
|
||||
const spawnCmd = useShell ? quoteForCmd(command) : command;
|
||||
const spawnArgs = useShell ? args.map(a => quoteForCmd(a)) : args;
|
||||
|
||||
45
electron/utils/openrouter-headers-preload.cjs
Normal file
45
electron/utils/openrouter-headers-preload.cjs
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Gateway fetch preload — loaded via NODE_OPTIONS --require before
|
||||
* the OpenClaw Gateway starts.
|
||||
*
|
||||
* Patches globalThis.fetch so that every request whose URL contains
|
||||
* "openrouter.ai" carries the ClawX app-attribution headers.
|
||||
*
|
||||
* The OpenAI SDK (used by OpenClaw) captures globalThis.fetch in its
|
||||
* constructor, so patching here guarantees all SDK requests go through
|
||||
* the interceptor.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
var _f = globalThis.fetch;
|
||||
if (typeof _f !== 'function') return;
|
||||
if (globalThis.__clawxFetchPatched) return;
|
||||
globalThis.__clawxFetchPatched = true;
|
||||
|
||||
globalThis.fetch = function clawxFetch(input, init) {
|
||||
var url =
|
||||
typeof input === 'string' ? input
|
||||
: input && typeof input === 'object' && typeof input.url === 'string'
|
||||
? input.url : '';
|
||||
|
||||
if (url.indexOf('openrouter.ai') !== -1) {
|
||||
init = init ? Object.assign({}, init) : {};
|
||||
var prev = init.headers;
|
||||
var flat = {};
|
||||
if (prev && typeof prev.forEach === 'function') {
|
||||
prev.forEach(function (v, k) { flat[k] = v; });
|
||||
} else if (prev && typeof prev === 'object') {
|
||||
Object.assign(flat, prev);
|
||||
}
|
||||
delete flat['http-referer'];
|
||||
delete flat['HTTP-Referer'];
|
||||
delete flat['x-title'];
|
||||
delete flat['X-Title'];
|
||||
flat['HTTP-Referer'] = 'https://claw-x.com';
|
||||
flat['X-Title'] = 'ClawX';
|
||||
init.headers = flat;
|
||||
}
|
||||
return _f.call(globalThis, input, init);
|
||||
};
|
||||
})();
|
||||
54
pnpm-lock.yaml
generated
54
pnpm-lock.yaml
generated
@@ -1415,6 +1415,20 @@ packages:
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@node-llama-cpp/linux-x64-cuda-ext@3.15.1':
|
||||
resolution: {integrity: sha512-toepvLcXjgaQE/QGIThHBD58jbHGBWT1jhblJkCjYBRHfVOO+6n/PmVsJt+yMfu5Z93A2gF8YOvVyZXNXmGo5g==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@node-llama-cpp/linux-x64-cuda@3.15.1':
|
||||
resolution: {integrity: sha512-kngwoq1KdrqSr/b6+tn5jbtGHI0tZnW5wofKssZy+Il2ge3eN9FowKbXG4FH452g6qSSVoDccAoTvYOxyLyX+w==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@node-llama-cpp/linux-x64-vulkan@3.15.1':
|
||||
resolution: {integrity: sha512-CMsyQkGKpHKeOH9+ZPxo0hO0usg8jabq5/aM3JwdX9CiuXhXUa3nu3NH4RObiNi596Zwn/zWzlps0HRwcpL8rw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -1447,6 +1461,24 @@ packages:
|
||||
cpu: [arm64, x64]
|
||||
os: [win32]
|
||||
|
||||
'@node-llama-cpp/win-x64-cuda-ext@3.15.1':
|
||||
resolution: {integrity: sha512-mO3Tf6D3UlFkjQF5J96ynTkjdF7dac/f5f61cEh6oU4D3hdx+cwnmBWT1gVhDSLboJYzCHtx7U2EKPP6n8HoWA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@node-llama-cpp/win-x64-cuda@3.15.1':
|
||||
resolution: {integrity: sha512-swoyx0/dY4ixiu3mEWrIAinx0ffHn9IncELDNREKG+iIXfx6w0OujOMQ6+X+lGj+sjE01yMUP/9fv6GEp2pmBw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@node-llama-cpp/win-x64-vulkan@3.15.1':
|
||||
resolution: {integrity: sha512-BPBjUEIkFTdcHSsQyblP0v/aPPypi6uqQIq27mo4A49CYjX22JDmk4ncdBLk6cru+UkvwEEe+F2RomjoMt32aQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@node-llama-cpp/win-x64@3.15.1':
|
||||
resolution: {integrity: sha512-jtoXBa6h+VPsQgefrO7HDjYv4WvxfHtUO30ABwCUDuEgM0e05YYhxMZj1z2Ns47UrquNvd/LUPCyjHKqHUN+5Q==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -4173,7 +4205,7 @@ packages:
|
||||
glob@8.1.0:
|
||||
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
|
||||
engines: {node: '>=12'}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
|
||||
global-agent@3.0.0:
|
||||
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
|
||||
@@ -8479,6 +8511,12 @@ snapshots:
|
||||
'@node-llama-cpp/linux-armv7l@3.15.1':
|
||||
optional: true
|
||||
|
||||
'@node-llama-cpp/linux-x64-cuda-ext@3.15.1':
|
||||
optional: true
|
||||
|
||||
'@node-llama-cpp/linux-x64-cuda@3.15.1':
|
||||
optional: true
|
||||
|
||||
'@node-llama-cpp/linux-x64-vulkan@3.15.1':
|
||||
optional: true
|
||||
|
||||
@@ -8494,6 +8532,15 @@ snapshots:
|
||||
'@node-llama-cpp/win-arm64@3.15.1':
|
||||
optional: true
|
||||
|
||||
'@node-llama-cpp/win-x64-cuda-ext@3.15.1':
|
||||
optional: true
|
||||
|
||||
'@node-llama-cpp/win-x64-cuda@3.15.1':
|
||||
optional: true
|
||||
|
||||
'@node-llama-cpp/win-x64-vulkan@3.15.1':
|
||||
optional: true
|
||||
|
||||
'@node-llama-cpp/win-x64@3.15.1':
|
||||
optional: true
|
||||
|
||||
@@ -12963,11 +13010,16 @@ snapshots:
|
||||
'@node-llama-cpp/linux-arm64': 3.15.1
|
||||
'@node-llama-cpp/linux-armv7l': 3.15.1
|
||||
'@node-llama-cpp/linux-x64': 3.15.1
|
||||
'@node-llama-cpp/linux-x64-cuda': 3.15.1
|
||||
'@node-llama-cpp/linux-x64-cuda-ext': 3.15.1
|
||||
'@node-llama-cpp/linux-x64-vulkan': 3.15.1
|
||||
'@node-llama-cpp/mac-arm64-metal': 3.15.1
|
||||
'@node-llama-cpp/mac-x64': 3.15.1
|
||||
'@node-llama-cpp/win-arm64': 3.15.1
|
||||
'@node-llama-cpp/win-x64': 3.15.1
|
||||
'@node-llama-cpp/win-x64-cuda': 3.15.1
|
||||
'@node-llama-cpp/win-x64-cuda-ext': 3.15.1
|
||||
'@node-llama-cpp/win-x64-vulkan': 3.15.1
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
Reference in New Issue
Block a user