@autotask/atools-tool 0.1.6 → 0.1.7
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 +3 -4
- package/lib/config-openclaw.mjs +7 -4
- package/lib/install.mjs +2 -2
- package/lib/proxy-server.mjs +86 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,14 +68,13 @@ atools-tool-install --help
|
|
|
68
68
|
- macOS: launchd user agent (`~/Library/LaunchAgents`)
|
|
69
69
|
- Windows: Task Scheduler user task (ONLOGON + start now)
|
|
70
70
|
|
|
71
|
-
By default, installer
|
|
72
|
-
|
|
73
|
-
- force outbound model to `gpt-5.3-codex` (for codex-only upstream compatibility)
|
|
71
|
+
By default, installer does not hard-force a single outbound model.
|
|
72
|
+
Proxy normalizes `sub2api/<model>` to plain model id for codex-compatible upstreams.
|
|
74
73
|
|
|
75
74
|
## Proxy Command
|
|
76
75
|
|
|
77
76
|
```bash
|
|
78
|
-
atools-tool serve --port 18888 --upstream https://sub2api.atools.live
|
|
77
|
+
atools-tool serve --port 18888 --upstream https://sub2api.atools.live
|
|
79
78
|
```
|
|
80
79
|
|
|
81
80
|
## Repo Install
|
package/lib/config-openclaw.mjs
CHANGED
|
@@ -16,7 +16,6 @@ const PROXY_HOST = '127.0.0.1';
|
|
|
16
16
|
const PROXY_PORT = 18888;
|
|
17
17
|
const PROXY_BASE_URL = `http://${PROXY_HOST}:${PROXY_PORT}/v1`;
|
|
18
18
|
const PROXY_UPSTREAM = 'https://sub2api.atools.live';
|
|
19
|
-
const PROXY_FORCE_MODEL = 'gpt-5.3-codex';
|
|
20
19
|
const PROXY_LOG_FILE = path.join(os.tmpdir(), 'openclaw', 'atools-compat-proxy.log');
|
|
21
20
|
const PROXY_SERVICE = 'openclaw-atools-proxy.service';
|
|
22
21
|
|
|
@@ -259,6 +258,11 @@ function upsertEnvLine(content, key, value) {
|
|
|
259
258
|
return `${content.slice(0, afterServiceHeader + 1)}${line}\n${content.slice(afterServiceHeader + 1)}`;
|
|
260
259
|
}
|
|
261
260
|
|
|
261
|
+
function removeEnvLine(content, key) {
|
|
262
|
+
const re = new RegExp(`^Environment=${escapeRegExp(key)}=.*\\n?`, 'gm');
|
|
263
|
+
return content.replace(re, '');
|
|
264
|
+
}
|
|
265
|
+
|
|
262
266
|
function configureLinuxProxyService() {
|
|
263
267
|
const servicePath = path.join(os.homedir(), '.config/systemd/user', PROXY_SERVICE);
|
|
264
268
|
if (!fs.existsSync(servicePath)) {
|
|
@@ -267,7 +271,7 @@ function configureLinuxProxyService() {
|
|
|
267
271
|
const original = fs.readFileSync(servicePath, 'utf8');
|
|
268
272
|
let next = original;
|
|
269
273
|
next = upsertEnvLine(next, 'SUB2API_UPSTREAM', PROXY_UPSTREAM);
|
|
270
|
-
next =
|
|
274
|
+
next = removeEnvLine(next, 'SUB2API_COMPAT_FORCE_MODEL');
|
|
271
275
|
next = upsertEnvLine(next, 'SUB2API_COMPAT_DROP_TOOLS_ON_COMPACT', '0');
|
|
272
276
|
next = upsertEnvLine(next, 'SUB2API_COMPAT_PORT', String(PROXY_PORT));
|
|
273
277
|
next = upsertEnvLine(next, 'SUB2API_COMPAT_LOG', PROXY_LOG_FILE);
|
|
@@ -317,8 +321,7 @@ function startDetachedProxy() {
|
|
|
317
321
|
stdio: 'ignore',
|
|
318
322
|
env: {
|
|
319
323
|
...process.env,
|
|
320
|
-
SUB2API_UPSTREAM: PROXY_UPSTREAM
|
|
321
|
-
SUB2API_COMPAT_FORCE_MODEL: PROXY_FORCE_MODEL
|
|
324
|
+
SUB2API_UPSTREAM: PROXY_UPSTREAM
|
|
322
325
|
}
|
|
323
326
|
}
|
|
324
327
|
);
|
package/lib/install.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
|
|
7
7
|
const DEFAULT_PROVIDER = 'sub2api';
|
|
8
8
|
const DEFAULT_MODEL = 'gpt-5.3-codex';
|
|
9
|
-
const DEFAULT_FORCE_MODEL = '
|
|
9
|
+
const DEFAULT_FORCE_MODEL = '';
|
|
10
10
|
const DEFAULT_PORT = 18888;
|
|
11
11
|
const DEFAULT_UPSTREAM = 'https://sub2api.atools.live';
|
|
12
12
|
const DEFAULT_LOG_FILE = path.join(os.tmpdir(), 'openclaw', 'atools-compat-proxy.log');
|
|
@@ -486,7 +486,7 @@ Options:
|
|
|
486
486
|
--openclaw-home <path> Default: ~/.openclaw
|
|
487
487
|
--provider <name> Default: sub2api
|
|
488
488
|
--model <id> Default: gpt-5.3-codex
|
|
489
|
-
--force-model <id> Default:
|
|
489
|
+
--force-model <id> Default: disabled (optional hard rewrite)
|
|
490
490
|
--port <number> Default: 18888
|
|
491
491
|
--upstream <url> Default: https://sub2api.atools.live
|
|
492
492
|
--max-req-bytes <number> Default: 68000
|
package/lib/proxy-server.mjs
CHANGED
|
@@ -10,6 +10,34 @@ const DEFAULT_MAX_REQ_BYTES = 68000;
|
|
|
10
10
|
const DEFAULT_DROP_TOOLS_ON_COMPACT = false;
|
|
11
11
|
const DEFAULT_STRIP_PREVIOUS_RESPONSE_ID = false;
|
|
12
12
|
const DEFAULT_FORCE_MODEL = '';
|
|
13
|
+
const SUPPORTED_MODEL_IDS = new Set(['gpt-5.2', 'gpt-5.3-codex', 'gpt-5.4']);
|
|
14
|
+
const FORWARD_HEADER_ALLOWLIST = new Set([
|
|
15
|
+
'authorization',
|
|
16
|
+
'api-key',
|
|
17
|
+
'x-api-key',
|
|
18
|
+
'openai-organization',
|
|
19
|
+
'openai-project'
|
|
20
|
+
]);
|
|
21
|
+
const RESPONSES_KEY_ALLOWLIST = new Set([
|
|
22
|
+
'model',
|
|
23
|
+
'input',
|
|
24
|
+
'instructions',
|
|
25
|
+
'reasoning',
|
|
26
|
+
'metadata',
|
|
27
|
+
'temperature',
|
|
28
|
+
'max_output_tokens',
|
|
29
|
+
'top_p',
|
|
30
|
+
'stream',
|
|
31
|
+
'store',
|
|
32
|
+
'previous_response_id',
|
|
33
|
+
'tools',
|
|
34
|
+
'tool_choice',
|
|
35
|
+
'parallel_tool_calls',
|
|
36
|
+
'text',
|
|
37
|
+
'truncation',
|
|
38
|
+
'max_tool_calls',
|
|
39
|
+
'prompt_cache_key'
|
|
40
|
+
]);
|
|
13
41
|
|
|
14
42
|
function ensureParentDir(filePath) {
|
|
15
43
|
const dir = path.dirname(filePath);
|
|
@@ -65,8 +93,9 @@ function normalizeResponsesPayload(payload, {
|
|
|
65
93
|
if (stripPreviousResponseId && 'previous_response_id' in out) {
|
|
66
94
|
delete out.previous_response_id;
|
|
67
95
|
}
|
|
68
|
-
|
|
69
|
-
|
|
96
|
+
const normalizedModel = normalizeModelId(out.model, forceModel);
|
|
97
|
+
if (normalizedModel) {
|
|
98
|
+
out.model = normalizedModel;
|
|
70
99
|
}
|
|
71
100
|
|
|
72
101
|
if (typeof out.input === 'string') {
|
|
@@ -82,6 +111,32 @@ function normalizeResponsesPayload(payload, {
|
|
|
82
111
|
});
|
|
83
112
|
}
|
|
84
113
|
|
|
114
|
+
return pruneResponsesPayload(out);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function normalizeModelId(modelId, forceModel = DEFAULT_FORCE_MODEL) {
|
|
118
|
+
const forced = String(forceModel || '').trim();
|
|
119
|
+
if (forced) return forced;
|
|
120
|
+
if (typeof modelId !== 'string') return '';
|
|
121
|
+
|
|
122
|
+
const raw = modelId.trim();
|
|
123
|
+
if (!raw) return raw;
|
|
124
|
+
if (SUPPORTED_MODEL_IDS.has(raw)) return raw;
|
|
125
|
+
|
|
126
|
+
const slashIndex = raw.lastIndexOf('/');
|
|
127
|
+
if (slashIndex < 0) return raw;
|
|
128
|
+
const tail = raw.slice(slashIndex + 1).trim();
|
|
129
|
+
if (SUPPORTED_MODEL_IDS.has(tail)) return tail;
|
|
130
|
+
return raw;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function pruneResponsesPayload(payload) {
|
|
134
|
+
const out = {};
|
|
135
|
+
for (const [key, value] of Object.entries(payload || {})) {
|
|
136
|
+
if (RESPONSES_KEY_ALLOWLIST.has(key)) {
|
|
137
|
+
out[key] = value;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
85
140
|
return out;
|
|
86
141
|
}
|
|
87
142
|
|
|
@@ -161,6 +216,29 @@ function buildHeaders(baseHeaders, bodyLen) {
|
|
|
161
216
|
return headers;
|
|
162
217
|
}
|
|
163
218
|
|
|
219
|
+
function normalizeHeaderValue(value) {
|
|
220
|
+
if (Array.isArray(value)) return value.join(', ');
|
|
221
|
+
if (value == null) return '';
|
|
222
|
+
return String(value);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function buildCodexLikeHeaders(incomingHeaders, userAgent) {
|
|
226
|
+
const headers = {};
|
|
227
|
+
for (const [rawKey, rawValue] of Object.entries(incomingHeaders || {})) {
|
|
228
|
+
const key = String(rawKey || '').toLowerCase();
|
|
229
|
+
if (!FORWARD_HEADER_ALLOWLIST.has(key)) continue;
|
|
230
|
+
const value = normalizeHeaderValue(rawValue);
|
|
231
|
+
if (value) headers[key] = value;
|
|
232
|
+
}
|
|
233
|
+
headers.accept = 'application/json';
|
|
234
|
+
headers['content-type'] = 'application/json';
|
|
235
|
+
headers['user-agent'] = userAgent;
|
|
236
|
+
headers['x-stainless-lang'] = 'rust';
|
|
237
|
+
headers['x-stainless-package-name'] = 'codex-cli';
|
|
238
|
+
headers['x-stainless-package-version'] = '0.101.0';
|
|
239
|
+
return headers;
|
|
240
|
+
}
|
|
241
|
+
|
|
164
242
|
export async function createProxyServer(options = {}) {
|
|
165
243
|
const port = Number(options.port ?? process.env.SUB2API_COMPAT_PORT ?? DEFAULT_PORT);
|
|
166
244
|
const upstream = options.upstream ?? process.env.SUB2API_UPSTREAM ?? DEFAULT_UPSTREAM;
|
|
@@ -183,10 +261,7 @@ export async function createProxyServer(options = {}) {
|
|
|
183
261
|
for await (const c of req) chunks.push(c);
|
|
184
262
|
const rawBody = Buffer.concat(chunks);
|
|
185
263
|
|
|
186
|
-
const baseHeaders =
|
|
187
|
-
delete baseHeaders.host;
|
|
188
|
-
delete baseHeaders.connection;
|
|
189
|
-
baseHeaders['user-agent'] = userAgent;
|
|
264
|
+
const baseHeaders = buildCodexLikeHeaders(req.headers, userAgent);
|
|
190
265
|
|
|
191
266
|
const isResponsesPath = req.method === 'POST' && url.pathname.endsWith('/responses');
|
|
192
267
|
let body = rawBody;
|
|
@@ -227,7 +302,7 @@ export async function createProxyServer(options = {}) {
|
|
|
227
302
|
minimalBody = minimalBuf;
|
|
228
303
|
}
|
|
229
304
|
}
|
|
230
|
-
if (
|
|
305
|
+
if (inputModel && outputModel && inputModel !== outputModel) {
|
|
231
306
|
appendLog(logFile, {
|
|
232
307
|
method: req.method,
|
|
233
308
|
path: url.pathname,
|
|
@@ -288,6 +363,9 @@ export async function createProxyServer(options = {}) {
|
|
|
288
363
|
});
|
|
289
364
|
|
|
290
365
|
const outBuffer = Buffer.from(await resp.arrayBuffer());
|
|
366
|
+
const upstreamError = resp.status >= 500
|
|
367
|
+
? outBuffer.toString('utf8').replace(/\s+/g, ' ').slice(0, 240)
|
|
368
|
+
: '';
|
|
291
369
|
appendLog(logFile, {
|
|
292
370
|
method: req.method,
|
|
293
371
|
path: url.pathname,
|
|
@@ -299,6 +377,7 @@ export async function createProxyServer(options = {}) {
|
|
|
299
377
|
reqBytes: rawBody.length,
|
|
300
378
|
upstreamReqBytes: requestBodies[Math.min(requestBodies.length - 1, attempts - 1)].length,
|
|
301
379
|
respBytes: outBuffer.length,
|
|
380
|
+
...(upstreamError ? { upstreamError } : {}),
|
|
302
381
|
durationMs: Date.now() - startAt
|
|
303
382
|
});
|
|
304
383
|
res.end(outBuffer);
|