scalemax 0.1.33 → 0.1.35
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 +15 -1
- package/dist/configure/roo-code.js +15 -2
- package/dist/utils/api.js +16 -1
- package/dist/utils/network.d.ts +5 -0
- package/dist/utils/network.js +91 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -205,7 +205,7 @@ npx scalemax models
|
|
|
205
205
|
```
|
|
206
206
|
|
|
207
207
|
The model list is fetched live from `https://api.scalemax.pro/v1/models` every time, so disabled or unsupported models disappear immediately without manual cache cleanup.
|
|
208
|
-
|
|
208
|
+
The CLI authenticates the key before loading its model catalog. Safe GET checks receive one short, jittered retry on transient network, rate-limit, or server failures; model-generation POST requests are never retried by the installer. CLI requests honor `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` and automatically try the usable IPv4 or IPv6 route.
|
|
209
209
|
|
|
210
210
|
Run diagnostics:
|
|
211
211
|
|
|
@@ -289,6 +289,20 @@ Or clear the npm cache:
|
|
|
289
289
|
npm cache verify
|
|
290
290
|
```
|
|
291
291
|
|
|
292
|
+
### `Could not reach the ScaleMax API`
|
|
293
|
+
|
|
294
|
+
Use the latest CLI first:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
npx scalemax@latest
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
ScaleMax automatically honors `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`
|
|
301
|
+
when a workplace or ISP requires an outbound proxy. It also falls back between
|
|
302
|
+
IPv4 and IPv6. If the error specifically reports DNS or TLS, correct the
|
|
303
|
+
device DNS/system clock or disable HTTPS inspection for `api.scalemax.pro`,
|
|
304
|
+
then rerun the command. Never disable TLS verification.
|
|
305
|
+
|
|
292
306
|
### Client config looks wrong
|
|
293
307
|
|
|
294
308
|
Run:
|
|
@@ -17,6 +17,7 @@ const codexProfileId = 'scalemax-codex';
|
|
|
17
17
|
const claudeProfileId = 'scalemax-claude';
|
|
18
18
|
const deepSeekProfileId = 'scalemax-deepseek';
|
|
19
19
|
const glmProfileId = 'scalemax-glm-5-2';
|
|
20
|
+
const rooMaximumOutputTokens = 384_000;
|
|
20
21
|
function exactChatProfile(ctx, model) {
|
|
21
22
|
if (model.trim().toLowerCase() === 'glm-5.2')
|
|
22
23
|
return { name: glmProfileName, id: glmProfileId, model };
|
|
@@ -30,7 +31,10 @@ function positiveInteger(value, fallback) {
|
|
|
30
31
|
function rooModelInfo(ctx, modelId) {
|
|
31
32
|
const model = (ctx.models || []).find(candidate => candidate.id.trim() === modelId.trim());
|
|
32
33
|
const contextWindow = positiveInteger(model?.context_window, 128_000);
|
|
33
|
-
|
|
34
|
+
// Roo sends this value as max_completion_tokens. Keep it within the
|
|
35
|
+
// ScaleMax/OpenAI-compatible contract even if an upstream catalog
|
|
36
|
+
// accidentally reports an output window larger than the public API accepts.
|
|
37
|
+
const maxTokens = Math.min(rooMaximumOutputTokens, contextWindow, positiveInteger(model?.max_output_tokens, 4_096));
|
|
34
38
|
const capabilities = model?.capabilities;
|
|
35
39
|
return {
|
|
36
40
|
maxTokens,
|
|
@@ -128,7 +132,13 @@ function handoff(ctx) {
|
|
|
128
132
|
apiConfigs,
|
|
129
133
|
modeApiConfigs: {}
|
|
130
134
|
},
|
|
131
|
-
globalSettings: {
|
|
135
|
+
globalSettings: {
|
|
136
|
+
// Roo 3.54 still probes an obsolete bundled-ripgrep path while building
|
|
137
|
+
// automatic workspace snapshots on newer VS Code releases. Disabling
|
|
138
|
+
// only that snapshot prevents its recursive request loop; Roo's explicit
|
|
139
|
+
// read/search/edit tools remain available to the selected model.
|
|
140
|
+
maxWorkspaceFiles: 0
|
|
141
|
+
}
|
|
132
142
|
}, null, 2) + '\n';
|
|
133
143
|
}
|
|
134
144
|
export async function configure(ctx) {
|
|
@@ -171,6 +181,9 @@ export async function verify(ctx) {
|
|
|
171
181
|
if (settings[importSetting] !== importSettingValue || profile?.providerProfiles?.currentApiConfigName !== current.name) {
|
|
172
182
|
throw new Error('Roo Code auto-import handoff is incomplete.');
|
|
173
183
|
}
|
|
184
|
+
if (profile?.globalSettings?.maxWorkspaceFiles !== 0) {
|
|
185
|
+
throw new Error('Roo Code reliability settings are incomplete.');
|
|
186
|
+
}
|
|
174
187
|
for (const expected of expectedProfiles) {
|
|
175
188
|
const config = profile?.providerProfiles?.apiConfigs?.[expected.name];
|
|
176
189
|
const expectedModelInfo = rooModelInfo(ctx, expected.model);
|
package/dist/utils/api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SCALEMAX_BASE_URL, SCALEMAX_TOKEN_BASE_URL } from '../constants.js';
|
|
2
2
|
import { redactApiKey, sanitizeApiKey } from './apiKey.js';
|
|
3
|
+
import { scaleMaxFetch } from './network.js';
|
|
3
4
|
export const DEEPSEEK_V4_FLASH_SCOPE = 'deepseek-v4-flash';
|
|
4
5
|
export const GLM_5_2_SCOPE = 'glm-5.2';
|
|
5
6
|
// Keep only aliases that are no longer offered. Direct GPT-5.6 Sol is an
|
|
@@ -77,9 +78,21 @@ function retryableStatus(status) {
|
|
|
77
78
|
}
|
|
78
79
|
function safeTransportError(error, timeoutSeconds) {
|
|
79
80
|
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
+
const cause = error && typeof error === 'object' && 'cause' in error
|
|
82
|
+
? error.cause
|
|
83
|
+
: undefined;
|
|
84
|
+
const code = cause && typeof cause === 'object' && 'code' in cause
|
|
85
|
+
? String(cause.code || '').toUpperCase()
|
|
86
|
+
: '';
|
|
80
87
|
if (/abort|timeout|timed?\s*out/i.test(message)) {
|
|
81
88
|
return `ScaleMax request timed out after ${timeoutSeconds} seconds. Please try again.`;
|
|
82
89
|
}
|
|
90
|
+
if (code === 'ENOTFOUND' || code === 'EAI_AGAIN') {
|
|
91
|
+
return 'Could not resolve the ScaleMax API hostname. Check this device’s DNS settings, then retry.';
|
|
92
|
+
}
|
|
93
|
+
if (/CERT|TLS|SSL|SELF_SIGNED|UNABLE_TO_VERIFY/.test(code)) {
|
|
94
|
+
return 'The ScaleMax TLS certificate could not be verified. Check the system clock and any HTTPS-inspecting proxy or antivirus, then retry.';
|
|
95
|
+
}
|
|
83
96
|
// Native fetch intentionally hides DNS/TLS/proxy details behind the generic
|
|
84
97
|
// "fetch failed" message. It is not an authentication verdict and may
|
|
85
98
|
// contain environment-specific detail that is not useful in an installer.
|
|
@@ -90,6 +103,8 @@ async function request(path, apiKey, init = {}, retryGet = false, baseUrl = SCAL
|
|
|
90
103
|
if (apiKey)
|
|
91
104
|
headers.set('Authorization', `Bearer ${sanitizeApiKey(apiKey)}`);
|
|
92
105
|
headers.set('Cache-Control', 'no-cache');
|
|
106
|
+
headers.set('Accept', 'application/json');
|
|
107
|
+
headers.set('User-Agent', 'ScaleMax-CLI');
|
|
93
108
|
if (!headers.has('Content-Type') && init.body)
|
|
94
109
|
headers.set('Content-Type', 'application/json');
|
|
95
110
|
const attempts = retryGet && (!init.method || init.method === 'GET') ? 2 : 1;
|
|
@@ -98,7 +113,7 @@ async function request(path, apiKey, init = {}, retryGet = false, baseUrl = SCAL
|
|
|
98
113
|
const timeout = AbortSignal.timeout(timeoutMs);
|
|
99
114
|
const signal = init.signal && typeof AbortSignal.any === 'function' ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
100
115
|
try {
|
|
101
|
-
const response = await
|
|
116
|
+
const response = await scaleMaxFetch(`${baseUrl}${path}`, { ...init, headers, signal }, attempt > 0);
|
|
102
117
|
if (attempt + 1 === attempts || !retryableStatus(response.status))
|
|
103
118
|
return response;
|
|
104
119
|
await response.body?.cancel().catch(() => undefined);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Agent, ProxyAgent } from 'undici';
|
|
2
|
+
/*
|
|
3
|
+
* Node's built-in fetch does not consistently honor HTTP(S)_PROXY/NO_PROXY
|
|
4
|
+
* across the supported Node 18-22 range. It can also wait on an unusable
|
|
5
|
+
* IPv6 route instead of trying the working IPv4 address. The CLI is a
|
|
6
|
+
* network installer, so use an explicit dispatcher that handles both cases.
|
|
7
|
+
*
|
|
8
|
+
* RFC 8305-style family selection races the available A/AAAA routes instead
|
|
9
|
+
* of assuming the first DNS result is usable. ProxyAgent is used explicitly
|
|
10
|
+
* rather than Undici's experimental EnvHttpProxyAgent so customers do not see
|
|
11
|
+
* runtime stability warnings.
|
|
12
|
+
*/
|
|
13
|
+
const directDispatcher = new Agent({
|
|
14
|
+
autoSelectFamily: true,
|
|
15
|
+
autoSelectFamilyAttemptTimeout: 250
|
|
16
|
+
});
|
|
17
|
+
const proxyDispatchers = new Map();
|
|
18
|
+
const proxyPreferredOrigins = new Set();
|
|
19
|
+
function environmentValue(lowercase, uppercase) {
|
|
20
|
+
return String(process.env[lowercase] || process.env[uppercase] || '').trim();
|
|
21
|
+
}
|
|
22
|
+
function defaultPort(url) {
|
|
23
|
+
return url.port || (url.protocol === 'https:' ? '443' : '80');
|
|
24
|
+
}
|
|
25
|
+
function splitNoProxyEntry(entry) {
|
|
26
|
+
const value = entry.trim().toLowerCase();
|
|
27
|
+
if (value.startsWith('[')) {
|
|
28
|
+
const end = value.indexOf(']');
|
|
29
|
+
if (end >= 0) {
|
|
30
|
+
const port = value.slice(end + 1).replace(/^:/, '') || undefined;
|
|
31
|
+
return { hostname: value.slice(1, end), port };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const colon = value.lastIndexOf(':');
|
|
35
|
+
if (colon > -1 && value.indexOf(':') === colon && /^\d+$/.test(value.slice(colon + 1))) {
|
|
36
|
+
return { hostname: value.slice(0, colon), port: value.slice(colon + 1) };
|
|
37
|
+
}
|
|
38
|
+
return { hostname: value };
|
|
39
|
+
}
|
|
40
|
+
function bypassesProxy(url) {
|
|
41
|
+
const raw = environmentValue('no_proxy', 'NO_PROXY');
|
|
42
|
+
if (!raw)
|
|
43
|
+
return false;
|
|
44
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, '').toLowerCase();
|
|
45
|
+
const port = defaultPort(url);
|
|
46
|
+
return raw.split(/[,\s]+/).filter(Boolean).some(candidate => {
|
|
47
|
+
if (candidate === '*')
|
|
48
|
+
return true;
|
|
49
|
+
const parsed = splitNoProxyEntry(candidate);
|
|
50
|
+
if (parsed.port && parsed.port !== port)
|
|
51
|
+
return false;
|
|
52
|
+
const rule = parsed.hostname.replace(/^\*\./, '').replace(/^\./, '');
|
|
53
|
+
return hostname === rule || hostname.endsWith(`.${rule}`);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function proxyFor(url) {
|
|
57
|
+
if (bypassesProxy(url))
|
|
58
|
+
return undefined;
|
|
59
|
+
if (url.protocol === 'https:') {
|
|
60
|
+
return environmentValue('https_proxy', 'HTTPS_PROXY')
|
|
61
|
+
|| environmentValue('http_proxy', 'HTTP_PROXY')
|
|
62
|
+
|| undefined;
|
|
63
|
+
}
|
|
64
|
+
return environmentValue('http_proxy', 'HTTP_PROXY') || undefined;
|
|
65
|
+
}
|
|
66
|
+
function dispatcherFor(url) {
|
|
67
|
+
const proxy = proxyFor(url);
|
|
68
|
+
if (!proxy)
|
|
69
|
+
return directDispatcher;
|
|
70
|
+
let dispatcher = proxyDispatchers.get(proxy);
|
|
71
|
+
if (!dispatcher) {
|
|
72
|
+
dispatcher = new ProxyAgent({
|
|
73
|
+
uri: proxy,
|
|
74
|
+
autoSelectFamily: true,
|
|
75
|
+
autoSelectFamilyAttemptTimeout: 250
|
|
76
|
+
});
|
|
77
|
+
proxyDispatchers.set(proxy, dispatcher);
|
|
78
|
+
}
|
|
79
|
+
return dispatcher;
|
|
80
|
+
}
|
|
81
|
+
export async function scaleMaxFetch(input, init = {}, allowProxyFallback = false) {
|
|
82
|
+
const url = input instanceof URL ? input : new URL(input);
|
|
83
|
+
const proxy = proxyFor(url);
|
|
84
|
+
const useProxy = Boolean(proxy)
|
|
85
|
+
&& (allowProxyFallback || proxyPreferredOrigins.has(url.origin));
|
|
86
|
+
const dispatcher = useProxy ? dispatcherFor(url) : directDispatcher;
|
|
87
|
+
const response = await fetch(input, { ...init, dispatcher });
|
|
88
|
+
if (useProxy)
|
|
89
|
+
proxyPreferredOrigins.add(url.origin);
|
|
90
|
+
return response;
|
|
91
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scalemax",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.35",
|
|
4
4
|
"description": "Universal ScaleMax installer for AI coding clients",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"fs-extra": "^11.2.0",
|
|
34
34
|
"json5": "^2.2.3",
|
|
35
35
|
"ora": "^8.1.1",
|
|
36
|
+
"undici": "^6.28.0",
|
|
36
37
|
"yaml": "^2.9.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|