@autotask/atools-tool 0.1.4 → 0.1.6
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 +5 -1
- package/bin/cli.mjs +3 -0
- package/lib/config-openclaw.mjs +4 -1
- package/lib/install.mjs +15 -4
- package/lib/proxy-server.mjs +22 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,10 +68,14 @@ 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 enables proxy model rewrite:
|
|
72
|
+
|
|
73
|
+
- force outbound model to `gpt-5.3-codex` (for codex-only upstream compatibility)
|
|
74
|
+
|
|
71
75
|
## Proxy Command
|
|
72
76
|
|
|
73
77
|
```bash
|
|
74
|
-
atools-tool serve --port 18888 --upstream https://sub2api.atools.live
|
|
78
|
+
atools-tool serve --port 18888 --upstream https://sub2api.atools.live --force-model gpt-5.3-codex
|
|
75
79
|
```
|
|
76
80
|
|
|
77
81
|
## Repo Install
|
package/bin/cli.mjs
CHANGED
|
@@ -8,6 +8,7 @@ function parseArgs(argv) {
|
|
|
8
8
|
openclawHome: undefined,
|
|
9
9
|
port: undefined,
|
|
10
10
|
upstream: undefined,
|
|
11
|
+
forceModel: undefined,
|
|
11
12
|
maxReqBytes: undefined,
|
|
12
13
|
logFile: undefined,
|
|
13
14
|
retryMax: undefined,
|
|
@@ -25,6 +26,7 @@ function parseArgs(argv) {
|
|
|
25
26
|
if (a === '--port') out.port = Number(next());
|
|
26
27
|
else if (a === '--openclaw-home') out.openclawHome = next();
|
|
27
28
|
else if (a === '--upstream') out.upstream = next();
|
|
29
|
+
else if (a === '--force-model') out.forceModel = next();
|
|
28
30
|
else if (a === '--max-req-bytes') out.maxReqBytes = Number(next());
|
|
29
31
|
else if (a === '--log-file') out.logFile = next();
|
|
30
32
|
else if (a === '--retry-max') out.retryMax = Number(next());
|
|
@@ -50,6 +52,7 @@ Options:
|
|
|
50
52
|
--openclaw-home <path> OpenClaw home dir (default: ~/.openclaw)
|
|
51
53
|
--port <number> Listen port (default: 18888)
|
|
52
54
|
--upstream <url> Upstream base URL (default: https://sub2api.atools.live)
|
|
55
|
+
--force-model <id> Force outbound model id (e.g. gpt-5.3-codex)
|
|
53
56
|
--max-req-bytes <number> Compact requests larger than this threshold (default: 68000)
|
|
54
57
|
--log-file <path> Log file (default: /tmp/openclaw/atools-compat-proxy.log)
|
|
55
58
|
--retry-max <number> Retry attempts on 5xx for /responses (default: 6)
|
package/lib/config-openclaw.mjs
CHANGED
|
@@ -16,6 +16,7 @@ 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';
|
|
19
20
|
const PROXY_LOG_FILE = path.join(os.tmpdir(), 'openclaw', 'atools-compat-proxy.log');
|
|
20
21
|
const PROXY_SERVICE = 'openclaw-atools-proxy.service';
|
|
21
22
|
|
|
@@ -266,6 +267,7 @@ function configureLinuxProxyService() {
|
|
|
266
267
|
const original = fs.readFileSync(servicePath, 'utf8');
|
|
267
268
|
let next = original;
|
|
268
269
|
next = upsertEnvLine(next, 'SUB2API_UPSTREAM', PROXY_UPSTREAM);
|
|
270
|
+
next = upsertEnvLine(next, 'SUB2API_COMPAT_FORCE_MODEL', PROXY_FORCE_MODEL);
|
|
269
271
|
next = upsertEnvLine(next, 'SUB2API_COMPAT_DROP_TOOLS_ON_COMPACT', '0');
|
|
270
272
|
next = upsertEnvLine(next, 'SUB2API_COMPAT_PORT', String(PROXY_PORT));
|
|
271
273
|
next = upsertEnvLine(next, 'SUB2API_COMPAT_LOG', PROXY_LOG_FILE);
|
|
@@ -315,7 +317,8 @@ function startDetachedProxy() {
|
|
|
315
317
|
stdio: 'ignore',
|
|
316
318
|
env: {
|
|
317
319
|
...process.env,
|
|
318
|
-
SUB2API_UPSTREAM: PROXY_UPSTREAM
|
|
320
|
+
SUB2API_UPSTREAM: PROXY_UPSTREAM,
|
|
321
|
+
SUB2API_COMPAT_FORCE_MODEL: PROXY_FORCE_MODEL
|
|
319
322
|
}
|
|
320
323
|
}
|
|
321
324
|
);
|
package/lib/install.mjs
CHANGED
|
@@ -6,6 +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 = 'gpt-5.3-codex';
|
|
9
10
|
const DEFAULT_PORT = 18888;
|
|
10
11
|
const DEFAULT_UPSTREAM = 'https://sub2api.atools.live';
|
|
11
12
|
const DEFAULT_LOG_FILE = path.join(os.tmpdir(), 'openclaw', 'atools-compat-proxy.log');
|
|
@@ -50,7 +51,7 @@ function defaultServiceId(name = DEFAULT_SERVICE_NAME) {
|
|
|
50
51
|
return String(name || DEFAULT_SERVICE_NAME).replace(/\.service$/i, '');
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
function buildProxyServeArgs({ port, upstream, maxReqBytes, logFile }) {
|
|
54
|
+
function buildProxyServeArgs({ port, upstream, forceModel, maxReqBytes, logFile }) {
|
|
54
55
|
const args = [
|
|
55
56
|
'serve',
|
|
56
57
|
'--port',
|
|
@@ -62,6 +63,9 @@ function buildProxyServeArgs({ port, upstream, maxReqBytes, logFile }) {
|
|
|
62
63
|
'--max-req-bytes',
|
|
63
64
|
String(maxReqBytes)
|
|
64
65
|
];
|
|
66
|
+
if (forceModel) {
|
|
67
|
+
args.push('--force-model', String(forceModel));
|
|
68
|
+
}
|
|
65
69
|
return args;
|
|
66
70
|
}
|
|
67
71
|
|
|
@@ -85,6 +89,7 @@ function parseArgs(argv) {
|
|
|
85
89
|
openclawHome: process.env.OPENCLAW_HOME || path.join(os.homedir(), '.openclaw'),
|
|
86
90
|
provider: DEFAULT_PROVIDER,
|
|
87
91
|
model: DEFAULT_MODEL,
|
|
92
|
+
forceModel: DEFAULT_FORCE_MODEL,
|
|
88
93
|
port: DEFAULT_PORT,
|
|
89
94
|
upstream: DEFAULT_UPSTREAM,
|
|
90
95
|
maxReqBytes: 68000,
|
|
@@ -102,6 +107,7 @@ function parseArgs(argv) {
|
|
|
102
107
|
if (a === '--openclaw-home') out.openclawHome = next();
|
|
103
108
|
else if (a === '--provider') out.provider = next();
|
|
104
109
|
else if (a === '--model') out.model = next();
|
|
110
|
+
else if (a === '--force-model') out.forceModel = next();
|
|
105
111
|
else if (a === '--port') out.port = Number(next());
|
|
106
112
|
else if (a === '--upstream') out.upstream = next();
|
|
107
113
|
else if (a === '--max-req-bytes') out.maxReqBytes = Number(next());
|
|
@@ -249,9 +255,10 @@ function patchOpenclawConfig(openclawHome, provider, model, port, dryRun) {
|
|
|
249
255
|
|
|
250
256
|
function patchAgentModels(openclawHome, provider, model, port, apiKey, dryRun) {
|
|
251
257
|
const modelsPath = path.join(openclawHome, 'agents/main/agent/models.json');
|
|
252
|
-
const models = readJsonIfExists(modelsPath);
|
|
253
|
-
|
|
254
|
-
|
|
258
|
+
const models = readJsonIfExists(modelsPath) || { providers: {} };
|
|
259
|
+
const creatingNewModelsFile = !fs.existsSync(modelsPath);
|
|
260
|
+
if (creatingNewModelsFile) {
|
|
261
|
+
info(`agent models config not found, will create: ${modelsPath}`);
|
|
255
262
|
}
|
|
256
263
|
|
|
257
264
|
const baseUrl = `http://127.0.0.1:${port}/v1`;
|
|
@@ -293,6 +300,7 @@ function patchAgentModels(openclawHome, provider, model, port, apiKey, dryRun) {
|
|
|
293
300
|
info(`dry-run: would patch ${modelsPath}`);
|
|
294
301
|
return;
|
|
295
302
|
}
|
|
303
|
+
ensureDir(path.dirname(modelsPath));
|
|
296
304
|
const bak = backup(modelsPath);
|
|
297
305
|
if (bak) info(`backup created: ${bak}`);
|
|
298
306
|
writeJson(modelsPath, models);
|
|
@@ -478,6 +486,7 @@ Options:
|
|
|
478
486
|
--openclaw-home <path> Default: ~/.openclaw
|
|
479
487
|
--provider <name> Default: sub2api
|
|
480
488
|
--model <id> Default: gpt-5.3-codex
|
|
489
|
+
--force-model <id> Default: gpt-5.3-codex (proxy rewrites outbound model)
|
|
481
490
|
--port <number> Default: 18888
|
|
482
491
|
--upstream <url> Default: https://sub2api.atools.live
|
|
483
492
|
--max-req-bytes <number> Default: 68000
|
|
@@ -504,6 +513,7 @@ export async function runInstall(rawArgs = process.argv.slice(2)) {
|
|
|
504
513
|
const serveArgs = buildProxyServeArgs({
|
|
505
514
|
port: args.port,
|
|
506
515
|
upstream: args.upstream,
|
|
516
|
+
forceModel: args.forceModel,
|
|
507
517
|
maxReqBytes: args.maxReqBytes,
|
|
508
518
|
logFile: args.logFile
|
|
509
519
|
});
|
|
@@ -511,6 +521,7 @@ export async function runInstall(rawArgs = process.argv.slice(2)) {
|
|
|
511
521
|
info(`openclawHome=${args.openclawHome}`);
|
|
512
522
|
info(`platform=${process.platform}`);
|
|
513
523
|
info(`provider/model=${args.provider}/${args.model}`);
|
|
524
|
+
info(`proxy forceModel=${args.forceModel || '(disabled)'}`);
|
|
514
525
|
info(`proxy=http://127.0.0.1:${args.port}/v1 -> ${args.upstream}`);
|
|
515
526
|
|
|
516
527
|
patchOpenclawConfig(args.openclawHome, args.provider, args.model, args.port, args.dryRun);
|
package/lib/proxy-server.mjs
CHANGED
|
@@ -9,6 +9,7 @@ const DEFAULT_USER_AGENT = 'codex_cli_rs/0.101.0 (Ubuntu 24.4.0; x86_64) Windows
|
|
|
9
9
|
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
|
+
const DEFAULT_FORCE_MODEL = '';
|
|
12
13
|
|
|
13
14
|
function ensureParentDir(filePath) {
|
|
14
15
|
const dir = path.dirname(filePath);
|
|
@@ -54,13 +55,19 @@ function normalizeContent(content) {
|
|
|
54
55
|
return [toInputText('')];
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
function normalizeResponsesPayload(payload, {
|
|
58
|
+
function normalizeResponsesPayload(payload, {
|
|
59
|
+
stripPreviousResponseId = DEFAULT_STRIP_PREVIOUS_RESPONSE_ID,
|
|
60
|
+
forceModel = DEFAULT_FORCE_MODEL
|
|
61
|
+
} = {}) {
|
|
58
62
|
if (!payload || typeof payload !== 'object') return payload;
|
|
59
63
|
const out = { ...payload };
|
|
60
64
|
|
|
61
65
|
if (stripPreviousResponseId && 'previous_response_id' in out) {
|
|
62
66
|
delete out.previous_response_id;
|
|
63
67
|
}
|
|
68
|
+
if (forceModel) {
|
|
69
|
+
out.model = forceModel;
|
|
70
|
+
}
|
|
64
71
|
|
|
65
72
|
if (typeof out.input === 'string') {
|
|
66
73
|
out.input = [{ role: 'user', content: normalizeContent(out.input) }];
|
|
@@ -166,6 +173,7 @@ export async function createProxyServer(options = {}) {
|
|
|
166
173
|
?? ['1', 'true', 'yes', 'on'].includes(String(process.env.SUB2API_COMPAT_DROP_TOOLS_ON_COMPACT || '').toLowerCase());
|
|
167
174
|
const stripPreviousResponseId = options.stripPreviousResponseId
|
|
168
175
|
?? ['1', 'true', 'yes', 'on'].includes(String(process.env.SUB2API_COMPAT_STRIP_PREVIOUS_RESPONSE_ID || '').toLowerCase());
|
|
176
|
+
const forceModel = String(options.forceModel ?? process.env.SUB2API_COMPAT_FORCE_MODEL ?? '').trim();
|
|
169
177
|
|
|
170
178
|
const server = http.createServer(async (req, res) => {
|
|
171
179
|
const startAt = Date.now();
|
|
@@ -189,7 +197,12 @@ export async function createProxyServer(options = {}) {
|
|
|
189
197
|
if (isResponsesPath && rawBody.length > 0) {
|
|
190
198
|
try {
|
|
191
199
|
const parsed = JSON.parse(rawBody.toString('utf8'));
|
|
192
|
-
const
|
|
200
|
+
const inputModel = typeof parsed.model === 'string' ? parsed.model : '';
|
|
201
|
+
const normalized = normalizeResponsesPayload(parsed, {
|
|
202
|
+
stripPreviousResponseId,
|
|
203
|
+
forceModel
|
|
204
|
+
});
|
|
205
|
+
const outputModel = typeof normalized.model === 'string' ? normalized.model : '';
|
|
193
206
|
body = Buffer.from(JSON.stringify(normalized));
|
|
194
207
|
if (body.length > maxReqBytes) {
|
|
195
208
|
const compacted = compactResponsesPayload(normalized, { dropToolsOnCompact });
|
|
@@ -214,6 +227,13 @@ export async function createProxyServer(options = {}) {
|
|
|
214
227
|
minimalBody = minimalBuf;
|
|
215
228
|
}
|
|
216
229
|
}
|
|
230
|
+
if (forceModel && inputModel && inputModel !== outputModel) {
|
|
231
|
+
appendLog(logFile, {
|
|
232
|
+
method: req.method,
|
|
233
|
+
path: url.pathname,
|
|
234
|
+
modelRewrite: { from: inputModel, to: outputModel }
|
|
235
|
+
});
|
|
236
|
+
}
|
|
217
237
|
} catch {
|
|
218
238
|
// Keep original body if parse fails.
|
|
219
239
|
}
|