@autotask/atools-tool 0.1.3 → 0.1.4
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 +6 -0
- package/bin/cli.mjs +2 -9
- package/lib/config-openclaw.mjs +2 -6
- package/lib/install.mjs +4 -38
- package/lib/proxy-server.mjs +3 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,12 @@ Recommended selections:
|
|
|
50
50
|
npm i -g @autotask/atools-tool
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
Default full install (recommended):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm i -g @autotask/atools-tool && atools-tool-install && atools-tool openclaw
|
|
57
|
+
```
|
|
58
|
+
|
|
53
59
|
Optional installer command:
|
|
54
60
|
|
|
55
61
|
```bash
|
package/bin/cli.mjs
CHANGED
|
@@ -8,8 +8,6 @@ function parseArgs(argv) {
|
|
|
8
8
|
openclawHome: undefined,
|
|
9
9
|
port: undefined,
|
|
10
10
|
upstream: undefined,
|
|
11
|
-
fallbackUpstream: undefined,
|
|
12
|
-
fallbackApiKey: undefined,
|
|
13
11
|
maxReqBytes: undefined,
|
|
14
12
|
logFile: undefined,
|
|
15
13
|
retryMax: undefined,
|
|
@@ -27,8 +25,6 @@ function parseArgs(argv) {
|
|
|
27
25
|
if (a === '--port') out.port = Number(next());
|
|
28
26
|
else if (a === '--openclaw-home') out.openclawHome = next();
|
|
29
27
|
else if (a === '--upstream') out.upstream = next();
|
|
30
|
-
else if (a === '--fallback-upstream') out.fallbackUpstream = next();
|
|
31
|
-
else if (a === '--fallback-api-key') out.fallbackApiKey = next();
|
|
32
28
|
else if (a === '--max-req-bytes') out.maxReqBytes = Number(next());
|
|
33
29
|
else if (a === '--log-file') out.logFile = next();
|
|
34
30
|
else if (a === '--retry-max') out.retryMax = Number(next());
|
|
@@ -54,8 +50,6 @@ Options:
|
|
|
54
50
|
--openclaw-home <path> OpenClaw home dir (default: ~/.openclaw)
|
|
55
51
|
--port <number> Listen port (default: 18888)
|
|
56
52
|
--upstream <url> Upstream base URL (default: https://sub2api.atools.live)
|
|
57
|
-
--fallback-upstream <url> Fallback base URL on primary 5xx
|
|
58
|
-
--fallback-api-key <key> Override Bearer key for fallback upstream
|
|
59
53
|
--max-req-bytes <number> Compact requests larger than this threshold (default: 68000)
|
|
60
54
|
--log-file <path> Log file (default: /tmp/openclaw/atools-compat-proxy.log)
|
|
61
55
|
--retry-max <number> Retry attempts on 5xx for /responses (default: 6)
|
|
@@ -81,9 +75,8 @@ async function main() {
|
|
|
81
75
|
throw new Error(`unsupported command: ${args.cmd}`);
|
|
82
76
|
}
|
|
83
77
|
|
|
84
|
-
const { port, upstream,
|
|
85
|
-
|
|
86
|
-
process.stdout.write(`atools-tool listening on http://127.0.0.1:${port}, upstream=${upstream}${fallbackNote}, log=${logFile}\n`);
|
|
78
|
+
const { port, upstream, logFile } = await createProxyServer(args);
|
|
79
|
+
process.stdout.write(`atools-tool listening on http://127.0.0.1:${port}, upstream=${upstream}, log=${logFile}\n`);
|
|
87
80
|
}
|
|
88
81
|
|
|
89
82
|
main().catch((err) => {
|
package/lib/config-openclaw.mjs
CHANGED
|
@@ -266,8 +266,6 @@ function configureLinuxProxyService() {
|
|
|
266
266
|
const original = fs.readFileSync(servicePath, 'utf8');
|
|
267
267
|
let next = original;
|
|
268
268
|
next = upsertEnvLine(next, 'SUB2API_UPSTREAM', PROXY_UPSTREAM);
|
|
269
|
-
next = upsertEnvLine(next, 'SUB2API_FALLBACK_UPSTREAM', '');
|
|
270
|
-
next = upsertEnvLine(next, 'SUB2API_FALLBACK_API_KEY', '');
|
|
271
269
|
next = upsertEnvLine(next, 'SUB2API_COMPAT_DROP_TOOLS_ON_COMPACT', '0');
|
|
272
270
|
next = upsertEnvLine(next, 'SUB2API_COMPAT_PORT', String(PROXY_PORT));
|
|
273
271
|
next = upsertEnvLine(next, 'SUB2API_COMPAT_LOG', PROXY_LOG_FILE);
|
|
@@ -317,9 +315,7 @@ function startDetachedProxy() {
|
|
|
317
315
|
stdio: 'ignore',
|
|
318
316
|
env: {
|
|
319
317
|
...process.env,
|
|
320
|
-
SUB2API_UPSTREAM: PROXY_UPSTREAM
|
|
321
|
-
SUB2API_FALLBACK_UPSTREAM: '',
|
|
322
|
-
SUB2API_FALLBACK_API_KEY: ''
|
|
318
|
+
SUB2API_UPSTREAM: PROXY_UPSTREAM
|
|
323
319
|
}
|
|
324
320
|
}
|
|
325
321
|
);
|
|
@@ -360,7 +356,7 @@ async function ensureProxyReady() {
|
|
|
360
356
|
|
|
361
357
|
if (await isPortOpen(PROXY_HOST, PROXY_PORT)) return true;
|
|
362
358
|
|
|
363
|
-
// Linux
|
|
359
|
+
// Linux retry: service may exist but not running.
|
|
364
360
|
if (process.platform === 'linux') {
|
|
365
361
|
const service = configureLinuxProxyService();
|
|
366
362
|
if (service.exists && isLinuxProxyServiceActive()) {
|
package/lib/install.mjs
CHANGED
|
@@ -8,7 +8,6 @@ const DEFAULT_PROVIDER = 'sub2api';
|
|
|
8
8
|
const DEFAULT_MODEL = 'gpt-5.3-codex';
|
|
9
9
|
const DEFAULT_PORT = 18888;
|
|
10
10
|
const DEFAULT_UPSTREAM = 'https://sub2api.atools.live';
|
|
11
|
-
const DEFAULT_FALLBACK_UPSTREAM = '';
|
|
12
11
|
const DEFAULT_LOG_FILE = path.join(os.tmpdir(), 'openclaw', 'atools-compat-proxy.log');
|
|
13
12
|
const DEFAULT_SERVICE_NAME = 'openclaw-atools-proxy.service';
|
|
14
13
|
const OPENCLAW_GATEWAY_SERVICE = 'openclaw-gateway.service';
|
|
@@ -51,7 +50,7 @@ function defaultServiceId(name = DEFAULT_SERVICE_NAME) {
|
|
|
51
50
|
return String(name || DEFAULT_SERVICE_NAME).replace(/\.service$/i, '');
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
function buildProxyServeArgs({ port, upstream,
|
|
53
|
+
function buildProxyServeArgs({ port, upstream, maxReqBytes, logFile }) {
|
|
55
54
|
const args = [
|
|
56
55
|
'serve',
|
|
57
56
|
'--port',
|
|
@@ -63,12 +62,6 @@ function buildProxyServeArgs({ port, upstream, fallbackUpstream, fallbackApiKey,
|
|
|
63
62
|
'--max-req-bytes',
|
|
64
63
|
String(maxReqBytes)
|
|
65
64
|
];
|
|
66
|
-
if (fallbackUpstream) {
|
|
67
|
-
args.push('--fallback-upstream', String(fallbackUpstream));
|
|
68
|
-
}
|
|
69
|
-
if (fallbackApiKey) {
|
|
70
|
-
args.push('--fallback-api-key', String(fallbackApiKey));
|
|
71
|
-
}
|
|
72
65
|
return args;
|
|
73
66
|
}
|
|
74
67
|
|
|
@@ -94,12 +87,10 @@ function parseArgs(argv) {
|
|
|
94
87
|
model: DEFAULT_MODEL,
|
|
95
88
|
port: DEFAULT_PORT,
|
|
96
89
|
upstream: DEFAULT_UPSTREAM,
|
|
97
|
-
fallbackUpstream: DEFAULT_FALLBACK_UPSTREAM,
|
|
98
90
|
maxReqBytes: 68000,
|
|
99
91
|
logFile: DEFAULT_LOG_FILE,
|
|
100
92
|
serviceName: DEFAULT_SERVICE_NAME,
|
|
101
93
|
apiKey: process.env.SUB_OAI_KEY || '',
|
|
102
|
-
fallbackApiKey: process.env.FALLBACK_OAI_KEY || '',
|
|
103
94
|
dryRun: false,
|
|
104
95
|
noRestart: false
|
|
105
96
|
};
|
|
@@ -113,12 +104,10 @@ function parseArgs(argv) {
|
|
|
113
104
|
else if (a === '--model') out.model = next();
|
|
114
105
|
else if (a === '--port') out.port = Number(next());
|
|
115
106
|
else if (a === '--upstream') out.upstream = next();
|
|
116
|
-
else if (a === '--fallback-upstream') out.fallbackUpstream = next();
|
|
117
107
|
else if (a === '--max-req-bytes') out.maxReqBytes = Number(next());
|
|
118
108
|
else if (a === '--log-file') out.logFile = next();
|
|
119
109
|
else if (a === '--service-name') out.serviceName = next();
|
|
120
110
|
else if (a === '--api-key') out.apiKey = next();
|
|
121
|
-
else if (a === '--fallback-api-key') out.fallbackApiKey = next();
|
|
122
111
|
else if (a === '--dry-run') out.dryRun = true;
|
|
123
112
|
else if (a === '--no-restart') out.noRestart = true;
|
|
124
113
|
else if (a === '--help' || a === '-h') {
|
|
@@ -137,13 +126,8 @@ function parseArgs(argv) {
|
|
|
137
126
|
function renderSystemdService({
|
|
138
127
|
nodeBin,
|
|
139
128
|
cliPath,
|
|
140
|
-
serveArgs
|
|
141
|
-
fallbackUpstream,
|
|
142
|
-
fallbackApiKey
|
|
129
|
+
serveArgs
|
|
143
130
|
}) {
|
|
144
|
-
const fallbackEnv = fallbackUpstream
|
|
145
|
-
? `Environment=SUB2API_FALLBACK_UPSTREAM=${fallbackUpstream}\nEnvironment=SUB2API_FALLBACK_API_KEY=${fallbackApiKey || ''}\n`
|
|
146
|
-
: '';
|
|
147
131
|
const execArgs = [nodeBin, cliPath, ...serveArgs].join(' ');
|
|
148
132
|
return `[Unit]
|
|
149
133
|
Description=OpenClaw ATools Compatibility Proxy
|
|
@@ -157,7 +141,6 @@ RestartSec=2
|
|
|
157
141
|
Environment=HOME=${os.homedir()}
|
|
158
142
|
Environment=TMPDIR=/tmp
|
|
159
143
|
Environment="SUB2API_COMPAT_USER_AGENT=codex_cli_rs/0.101.0 (Ubuntu 24.4.0; x86_64) WindowsTerminal"
|
|
160
|
-
${fallbackEnv}
|
|
161
144
|
|
|
162
145
|
[Install]
|
|
163
146
|
WantedBy=default.target
|
|
@@ -207,13 +190,6 @@ function detectApiKey(openclawHome, explicitKey) {
|
|
|
207
190
|
return auth?.profiles?.['sub2api:default']?.key || '';
|
|
208
191
|
}
|
|
209
192
|
|
|
210
|
-
function detectFallbackApiKey(openclawHome, explicitKey) {
|
|
211
|
-
if (explicitKey) return explicitKey;
|
|
212
|
-
const cfgPath = path.join(openclawHome, 'openclaw.json');
|
|
213
|
-
const cfg = readJsonIfExists(cfgPath);
|
|
214
|
-
return cfg?.models?.providers?.gmn?.apiKey || '';
|
|
215
|
-
}
|
|
216
|
-
|
|
217
193
|
function patchOpenclawConfig(openclawHome, provider, model, port, dryRun) {
|
|
218
194
|
const cfgPath = path.join(openclawHome, 'openclaw.json');
|
|
219
195
|
const cfg = readJsonIfExists(cfgPath);
|
|
@@ -504,12 +480,10 @@ Options:
|
|
|
504
480
|
--model <id> Default: gpt-5.3-codex
|
|
505
481
|
--port <number> Default: 18888
|
|
506
482
|
--upstream <url> Default: https://sub2api.atools.live
|
|
507
|
-
--fallback-upstream <url> Default: empty (disabled)
|
|
508
483
|
--max-req-bytes <number> Default: 68000
|
|
509
484
|
--log-file <path> Default: <tmp>/openclaw/atools-compat-proxy.log
|
|
510
485
|
--service-name <name> Linux: *.service; macOS/Windows: service id/task name
|
|
511
|
-
--api-key <key> Prefer explicit key;
|
|
512
|
-
--fallback-api-key <key> Prefer explicit key; fallback env FALLBACK_OAI_KEY then openclaw gmn key
|
|
486
|
+
--api-key <key> Prefer explicit key; otherwise use SUB_OAI_KEY or auth-profiles
|
|
513
487
|
--no-restart Do not restart openclaw-gateway.service
|
|
514
488
|
--dry-run Print actions without changing files
|
|
515
489
|
-h, --help Show help
|
|
@@ -527,12 +501,9 @@ export async function runInstall(rawArgs = process.argv.slice(2)) {
|
|
|
527
501
|
const nodeBin = process.execPath;
|
|
528
502
|
const cliPath = fileURLToPath(new URL('../bin/cli.mjs', import.meta.url));
|
|
529
503
|
const apiKey = detectApiKey(args.openclawHome, args.apiKey);
|
|
530
|
-
const fallbackApiKey = detectFallbackApiKey(args.openclawHome, args.fallbackApiKey);
|
|
531
504
|
const serveArgs = buildProxyServeArgs({
|
|
532
505
|
port: args.port,
|
|
533
506
|
upstream: args.upstream,
|
|
534
|
-
fallbackUpstream: args.fallbackUpstream,
|
|
535
|
-
fallbackApiKey,
|
|
536
507
|
maxReqBytes: args.maxReqBytes,
|
|
537
508
|
logFile: args.logFile
|
|
538
509
|
});
|
|
@@ -541,9 +512,6 @@ export async function runInstall(rawArgs = process.argv.slice(2)) {
|
|
|
541
512
|
info(`platform=${process.platform}`);
|
|
542
513
|
info(`provider/model=${args.provider}/${args.model}`);
|
|
543
514
|
info(`proxy=http://127.0.0.1:${args.port}/v1 -> ${args.upstream}`);
|
|
544
|
-
if (args.fallbackUpstream) {
|
|
545
|
-
info(`fallback=${args.fallbackUpstream}`);
|
|
546
|
-
}
|
|
547
515
|
|
|
548
516
|
patchOpenclawConfig(args.openclawHome, args.provider, args.model, args.port, args.dryRun);
|
|
549
517
|
patchAgentModels(args.openclawHome, args.provider, args.model, args.port, apiKey, args.dryRun);
|
|
@@ -552,9 +520,7 @@ export async function runInstall(rawArgs = process.argv.slice(2)) {
|
|
|
552
520
|
const linuxServiceContent = renderSystemdService({
|
|
553
521
|
nodeBin,
|
|
554
522
|
cliPath,
|
|
555
|
-
serveArgs
|
|
556
|
-
fallbackUpstream: args.fallbackUpstream,
|
|
557
|
-
fallbackApiKey
|
|
523
|
+
serveArgs
|
|
558
524
|
});
|
|
559
525
|
const macPlistContent = renderLaunchdPlist({
|
|
560
526
|
label: defaultServiceId(args.serviceName),
|
package/lib/proxy-server.mjs
CHANGED
|
@@ -6,8 +6,6 @@ const DEFAULT_PORT = 18888;
|
|
|
6
6
|
const DEFAULT_UPSTREAM = 'https://sub2api.atools.live';
|
|
7
7
|
const DEFAULT_LOG = '/tmp/openclaw/atools-compat-proxy.log';
|
|
8
8
|
const DEFAULT_USER_AGENT = 'codex_cli_rs/0.101.0 (Ubuntu 24.4.0; x86_64) WindowsTerminal';
|
|
9
|
-
const DEFAULT_FALLBACK_UPSTREAM = '';
|
|
10
|
-
const DEFAULT_FALLBACK_API_KEY = '';
|
|
11
9
|
const DEFAULT_MAX_REQ_BYTES = 68000;
|
|
12
10
|
const DEFAULT_DROP_TOOLS_ON_COMPACT = false;
|
|
13
11
|
const DEFAULT_STRIP_PREVIOUS_RESPONSE_ID = false;
|
|
@@ -149,13 +147,10 @@ async function forward(url, req, headers, body) {
|
|
|
149
147
|
});
|
|
150
148
|
}
|
|
151
149
|
|
|
152
|
-
function buildHeaders(baseHeaders, bodyLen
|
|
150
|
+
function buildHeaders(baseHeaders, bodyLen) {
|
|
153
151
|
const headers = { ...baseHeaders };
|
|
154
152
|
headers['content-type'] = 'application/json';
|
|
155
153
|
headers['content-length'] = String(bodyLen);
|
|
156
|
-
if (apiKeyOverride) {
|
|
157
|
-
headers.authorization = `Bearer ${apiKeyOverride}`;
|
|
158
|
-
}
|
|
159
154
|
return headers;
|
|
160
155
|
}
|
|
161
156
|
|
|
@@ -166,8 +161,6 @@ export async function createProxyServer(options = {}) {
|
|
|
166
161
|
const retryMax = Number(options.retryMax ?? process.env.SUB2API_COMPAT_RETRY_MAX ?? 6);
|
|
167
162
|
const retryBaseMs = Number(options.retryBaseMs ?? process.env.SUB2API_COMPAT_RETRY_BASE_MS ?? 300);
|
|
168
163
|
const userAgent = options.userAgent ?? process.env.SUB2API_COMPAT_USER_AGENT ?? DEFAULT_USER_AGENT;
|
|
169
|
-
const fallbackUpstream = options.fallbackUpstream ?? process.env.SUB2API_FALLBACK_UPSTREAM ?? DEFAULT_FALLBACK_UPSTREAM;
|
|
170
|
-
const fallbackApiKey = options.fallbackApiKey ?? process.env.SUB2API_FALLBACK_API_KEY ?? DEFAULT_FALLBACK_API_KEY;
|
|
171
164
|
const maxReqBytes = Number(options.maxReqBytes ?? process.env.SUB2API_COMPAT_MAX_REQ_BYTES ?? DEFAULT_MAX_REQ_BYTES);
|
|
172
165
|
const dropToolsOnCompact = options.dropToolsOnCompact
|
|
173
166
|
?? ['1', 'true', 'yes', 'on'].includes(String(process.env.SUB2API_COMPAT_DROP_TOOLS_ON_COMPACT || '').toLowerCase());
|
|
@@ -178,7 +171,6 @@ export async function createProxyServer(options = {}) {
|
|
|
178
171
|
const startAt = Date.now();
|
|
179
172
|
try {
|
|
180
173
|
const url = new URL(req.url || '/', upstream);
|
|
181
|
-
const fallbackUrl = fallbackUpstream ? new URL(req.url || '/', fallbackUpstream) : null;
|
|
182
174
|
const chunks = [];
|
|
183
175
|
for await (const c of req) chunks.push(c);
|
|
184
176
|
const rawBody = Buffer.concat(chunks);
|
|
@@ -245,9 +237,7 @@ export async function createProxyServer(options = {}) {
|
|
|
245
237
|
|
|
246
238
|
let resp;
|
|
247
239
|
let attempts = 0;
|
|
248
|
-
let via = 'primary';
|
|
249
240
|
let primaryStatus = null;
|
|
250
|
-
let fallbackStatus = null;
|
|
251
241
|
let compacted = false;
|
|
252
242
|
let strategy = 'full';
|
|
253
243
|
const maxAttempts = isResponsesPath ? retryMax : 1;
|
|
@@ -260,32 +250,11 @@ export async function createProxyServer(options = {}) {
|
|
|
260
250
|
? 'full'
|
|
261
251
|
: (strategyIndex === 1 ? 'compact' : (strategyIndex === 2 ? 'aggressive' : 'minimal'));
|
|
262
252
|
const primaryHeaders = isResponsesPath ? buildHeaders(baseHeaders, requestBody.length) : baseHeaders;
|
|
263
|
-
const fallbackHeaders = isResponsesPath
|
|
264
|
-
? buildHeaders(baseHeaders, requestBody.length, fallbackApiKey)
|
|
265
|
-
: (fallbackApiKey ? { ...baseHeaders, authorization: `Bearer ${fallbackApiKey}` } : baseHeaders);
|
|
266
253
|
|
|
267
254
|
const primaryResp = await forward(url, req, primaryHeaders, requestBody);
|
|
268
255
|
primaryStatus = primaryResp.status;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
via = 'primary';
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (fallbackUrl) {
|
|
276
|
-
const fbResp = await forward(fallbackUrl, req, fallbackHeaders, requestBody);
|
|
277
|
-
fallbackStatus = fbResp.status;
|
|
278
|
-
if (fbResp.status < 500) {
|
|
279
|
-
resp = fbResp;
|
|
280
|
-
via = 'fallback';
|
|
281
|
-
break;
|
|
282
|
-
}
|
|
283
|
-
resp = fbResp;
|
|
284
|
-
via = 'fallback';
|
|
285
|
-
} else {
|
|
286
|
-
resp = primaryResp;
|
|
287
|
-
via = 'primary';
|
|
288
|
-
}
|
|
256
|
+
resp = primaryResp;
|
|
257
|
+
if (primaryResp.status < 500) break;
|
|
289
258
|
|
|
290
259
|
if (attempts < maxAttempts) {
|
|
291
260
|
await sleep(retryBaseMs * attempts);
|
|
@@ -304,9 +273,7 @@ export async function createProxyServer(options = {}) {
|
|
|
304
273
|
path: url.pathname,
|
|
305
274
|
status: resp.status,
|
|
306
275
|
attempts,
|
|
307
|
-
via,
|
|
308
276
|
primaryStatus,
|
|
309
|
-
fallbackStatus,
|
|
310
277
|
compacted,
|
|
311
278
|
strategy,
|
|
312
279
|
reqBytes: rawBody.length,
|
|
@@ -339,7 +306,6 @@ export async function createProxyServer(options = {}) {
|
|
|
339
306
|
server,
|
|
340
307
|
port,
|
|
341
308
|
upstream,
|
|
342
|
-
fallbackUpstream,
|
|
343
309
|
logFile,
|
|
344
310
|
close: () => new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve())))
|
|
345
311
|
};
|