scalemax 0.1.49 → 0.1.50
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 -3
- package/dist/commands/configure.d.ts +2 -0
- package/dist/commands/configure.js +58 -38
- package/dist/commands/doctor.js +24 -3
- package/dist/commands/helper-bot.js +36 -19
- package/dist/configure/claude.js +76 -45
- package/dist/configure/cline.js +3 -1
- package/dist/configure/codex.js +21 -6
- package/dist/configure/continue.js +2 -2
- package/dist/configure/cursor.js +5 -2
- package/dist/configure/hermes.js +3 -3
- package/dist/configure/models.d.ts +83 -21
- package/dist/configure/models.js +313 -55
- package/dist/configure/openclaw.js +3 -3
- package/dist/configure/opencode.js +3 -3
- package/dist/configure/roo-code.d.ts +1 -0
- package/dist/configure/roo-code.js +15 -6
- package/dist/types/client.d.ts +17 -0
- package/dist/utils/api.d.ts +53 -27
- package/dist/utils/api.js +129 -92
- package/dist/utils/modelCheck.d.ts +8 -0
- package/dist/utils/modelCheck.js +32 -0
- package/dist/utils/modelId.d.ts +8 -0
- package/dist/utils/modelId.js +35 -0
- package/package.json +1 -1
package/dist/configure/codex.js
CHANGED
|
@@ -6,7 +6,7 @@ import { codexAuthPath, codexConfigPath } from './paths.js';
|
|
|
6
6
|
import { atomicWrite, backupFile, captureFileSnapshots, listBackups, restoreFileSnapshots, snapshotRollback } from '../utils/fs.js';
|
|
7
7
|
import { GENERATED_HEADER } from '../constants.js';
|
|
8
8
|
import { commandExists } from '../utils/exec.js';
|
|
9
|
-
import { AUTO_MODEL_ID,
|
|
9
|
+
import { AUTO_MODEL_ID, codexModel, codexModelIds, sameModelId } from './models.js';
|
|
10
10
|
const id = 'codex';
|
|
11
11
|
const name = 'Codex';
|
|
12
12
|
const provider = 'scalemax';
|
|
@@ -40,7 +40,17 @@ function selectedModel(ctx) {
|
|
|
40
40
|
// Auto key cannot call and would 403 every request.
|
|
41
41
|
return ctx.providerScope === 'auto-api' ? AUTO_MODEL_ID : fallbackModel;
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
// GPT only (or Auto on an Auto key); throws a plain explanation otherwise.
|
|
44
|
+
return codexModel(ctx);
|
|
45
|
+
}
|
|
46
|
+
/** A model Codex can run on with this key: any of its GPT models, or Auto. */
|
|
47
|
+
function codexCanUse(ctx, configured) {
|
|
48
|
+
if (typeof configured !== 'string' || !configured.trim())
|
|
49
|
+
return false;
|
|
50
|
+
if (!ctx.models)
|
|
51
|
+
return configured === selectedModel(ctx);
|
|
52
|
+
return codexModelIds(ctx).some(id => sameModelId(id, configured))
|
|
53
|
+
|| (sameModelId(configured, AUTO_MODEL_ID) && sameModelId(selectedModel(ctx), AUTO_MODEL_ID));
|
|
44
54
|
}
|
|
45
55
|
function scalemaxCodexBlock(baseUrl, model = fallbackModel, authHash, keyHash) {
|
|
46
56
|
const authMarker = authHash ? `${managedAuthHashPrefix}${authHash}\n` : '';
|
|
@@ -329,8 +339,11 @@ export async function verifyCodexConfiguration(ctx) {
|
|
|
329
339
|
errors.push(`${authPath} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
330
340
|
}
|
|
331
341
|
const toml = parseSimpleTomlForCodex(configText);
|
|
332
|
-
|
|
333
|
-
|
|
342
|
+
// Any GPT model this key has is a working choice (the user may pick another
|
|
343
|
+
// one); a Claude id, the claude-sonnet-4-6 alias or a model the key lacks is
|
|
344
|
+
// not. 0.1.49 wrote the alias, and every Codex request on it failed.
|
|
345
|
+
if (!codexCanUse(ctx, toml.model))
|
|
346
|
+
errors.push(`Codex is set to ${toml.model ? `"${toml.model}"` : 'no model'}, which is not a GPT model on this API key. Run \`npx scalemax@latest update\` to switch it to ${model}.`);
|
|
334
347
|
if (toml.model_provider !== provider)
|
|
335
348
|
errors.push(`Codex model_provider mismatch: expected ${provider}`);
|
|
336
349
|
if (toml.model_reasoning_effort !== reasoningEffort)
|
|
@@ -382,15 +395,17 @@ export async function verifyCodexConfiguration(ctx) {
|
|
|
382
395
|
else {
|
|
383
396
|
warnings.push('Codex executable was not found on PATH, so launch verification was skipped.');
|
|
384
397
|
}
|
|
398
|
+
// Check the model Codex will actually call: the configured one when it is valid.
|
|
399
|
+
const liveModel = codexCanUse(ctx, toml.model) ? toml.model : model;
|
|
385
400
|
try {
|
|
386
401
|
const res = await fetch(`${ctx.baseUrl}/responses`, {
|
|
387
402
|
method: 'POST',
|
|
388
403
|
headers: { Authorization: `Bearer ${ctx.apiKey}`, 'Content-Type': 'application/json' },
|
|
389
|
-
body: JSON.stringify({ model, input: 'Reply K', stream: false, max_output_tokens: 8 }),
|
|
404
|
+
body: JSON.stringify({ model: liveModel, input: 'Reply K', stream: false, max_output_tokens: 8 }),
|
|
390
405
|
signal: AbortSignal.timeout(15000)
|
|
391
406
|
});
|
|
392
407
|
if (res.status === 404)
|
|
393
|
-
warnings.push(`ScaleMax /v1/responses returned HTTP 404 for model "${
|
|
408
|
+
warnings.push(`ScaleMax /v1/responses returned HTTP 404 for model "${liveModel}". Codex requires the Responses API to complete requests; verify the base URL is correct and that this API key is entitled to that model.`);
|
|
394
409
|
else if (res.status === 401 || res.status === 403)
|
|
395
410
|
errors.push(`ScaleMax rejected the API key on /v1/responses with HTTP ${res.status}`);
|
|
396
411
|
else if (!res.ok)
|
|
@@ -6,7 +6,7 @@ import { atomicWrite, backupFile, captureFileSnapshots, restoreFileSnapshots, sn
|
|
|
6
6
|
import { commandExists } from '../utils/exec.js';
|
|
7
7
|
import { clientConfigPath } from './paths.js';
|
|
8
8
|
import { isScaleMaxApiUrl } from './common.js';
|
|
9
|
-
import { isExactChatCompletionsModelId, modelName as displayModelName, AUTO_MODEL_ID } from './models.js';
|
|
9
|
+
import { isExactChatCompletionsModelId, modelIdKey, modelName as displayModelName, AUTO_MODEL_ID } from './models.js';
|
|
10
10
|
/**
|
|
11
11
|
* Which single-model keys Continue's automatic setup can configure.
|
|
12
12
|
*
|
|
@@ -24,7 +24,7 @@ import { isExactChatCompletionsModelId, modelName as displayModelName, AUTO_MODE
|
|
|
24
24
|
* -- live-verified, 2026-08-08).
|
|
25
25
|
*/
|
|
26
26
|
export function isContinueEligibleModelId(id) {
|
|
27
|
-
return isExactChatCompletionsModelId(id) || id
|
|
27
|
+
return isExactChatCompletionsModelId(id) || modelIdKey(id) === AUTO_MODEL_ID;
|
|
28
28
|
}
|
|
29
29
|
const id = 'continue';
|
|
30
30
|
const name = 'Continue (VS Code)';
|
package/dist/configure/cursor.js
CHANGED
|
@@ -5,8 +5,11 @@ import { scaleMaxDir, timestamp } from '../utils/paths.js';
|
|
|
5
5
|
import { captureFileSnapshots, readJson, restoreFileSnapshots, writeJsonAtomic } from '../utils/fs.js';
|
|
6
6
|
import { resolveCursorAppPath, inspectCursorBundles, patchCursorBundles, restoreCursorBundles } from './cursor-bundle.js';
|
|
7
7
|
import { configureCursorState, restoreCursorState } from './cursor-state.js';
|
|
8
|
+
import { isChatModel } from '../utils/api.js';
|
|
8
9
|
const id = 'cursor', name = 'Cursor';
|
|
9
10
|
function manifestPath(ctx) { return path.join(scaleMaxDir(ctx.home, ctx.platform, ctx.env), 'cursor', 'manifest.json'); }
|
|
11
|
+
// Cursor's picker is chat-only: image, video, embedding and voice rows stay out.
|
|
12
|
+
function chatModels(ctx) { return (ctx.models || []).filter(model => isChatModel(model)); }
|
|
10
13
|
async function cursorVersion(appPath) { try {
|
|
11
14
|
return (await fs.readJson(path.join(appPath, 'Contents', 'Resources', 'app', 'package.json')))?.version;
|
|
12
15
|
}
|
|
@@ -48,7 +51,7 @@ export async function configure(ctx) {
|
|
|
48
51
|
patchResult = await patchCursorBundles(appPath, ctx.home, ctx.platform, ctx.env);
|
|
49
52
|
const backupRoot = patchResult.backupRoot || stateBackupRoot;
|
|
50
53
|
await fs.ensureDir(backupRoot, { mode: 0o700 });
|
|
51
|
-
state = await configureCursorState(ctx.home, ctx.platform, ctx.env, ctx.apiKey, ctx.baseUrl, ctx
|
|
54
|
+
state = await configureCursorState(ctx.home, ctx.platform, ctx.env, ctx.apiKey, ctx.baseUrl, chatModels(ctx), backupRoot);
|
|
52
55
|
const manifestState = prior?.state ? { ...state, originalState: prior.state.originalState, originalSecret: prior.state.originalSecret } : state;
|
|
53
56
|
const bundles = patchResult.managed ? patchResult.records : (prior?.bundleManaged ? prior.bundles : patchResult.records);
|
|
54
57
|
const manifest = { schemaVersion: 1, createdAt: new Date().toISOString(), appPath, version: await cursorVersion(appPath), bundleManaged: Boolean(patchResult.managed || prior?.bundleManaged), bundles, state: manifestState };
|
|
@@ -104,7 +107,7 @@ export async function verify(ctx) {
|
|
|
104
107
|
const manifest = await readJson(manifestPath(ctx), undefined), appPath = await resolveCursorAppPath(ctx.platform, ctx.env, ctx.home, manifest?.appPath), inspection = await inspectCursorBundles(appPath);
|
|
105
108
|
if (inspection.status !== 'patched' || !manifest?.state?.modelIds?.length)
|
|
106
109
|
throw new Error('Cursor local BYOK patch or protected model state is incomplete.');
|
|
107
|
-
const live = new Set((ctx
|
|
110
|
+
const live = new Set(chatModels(ctx).map(m => m.id)), configured = new Set(manifest.state.modelIds), missing = [...live].filter(x => !configured.has(x));
|
|
108
111
|
if (missing.length)
|
|
109
112
|
throw new Error(`Cursor is missing ${missing.length} live ScaleMax model(s). Rerun update.`);
|
|
110
113
|
return { id, name, changed: false, path: manifestPath(ctx), message: `verified local BYOK patch and ${configured.size} configured model IDs` };
|
package/dist/configure/hermes.js
CHANGED
|
@@ -4,7 +4,7 @@ import { parseDocument } from 'yaml';
|
|
|
4
4
|
import { atomicWrite, backupFile, captureFileSnapshots, restoreFileSnapshots, snapshotRollback } from '../utils/fs.js';
|
|
5
5
|
import { clientConfigPath } from './paths.js';
|
|
6
6
|
import { isScaleMaxApiUrl, isScaleMaxManagedText } from './common.js';
|
|
7
|
-
import { anthropicModelIds, anthropicOrigin, deepSeekChatModelIds, modelSupportsTools, openAiBaseUrl, openAiResponsesModelIds, preferredAnthropicModel, preferredOpenAiResponsesModel } from './models.js';
|
|
7
|
+
import { anthropicModelIds, anthropicOrigin, deepSeekChatModelIds, modelIdKey, modelSupportsTools, openAiBaseUrl, openAiResponsesModelIds, preferredAnthropicModel, preferredOpenAiResponsesModel } from './models.js';
|
|
8
8
|
const id = 'hermes';
|
|
9
9
|
const name = 'Hermes';
|
|
10
10
|
const envKey = 'SCALEMAX_HERMES_API_KEY';
|
|
@@ -15,12 +15,12 @@ const deepSeekProviderName = 'scalemax-deepseek';
|
|
|
15
15
|
const glmProviderName = 'scalemax-glm-5-2';
|
|
16
16
|
const claudeProviderName = 'scalemax-claude';
|
|
17
17
|
function exactChatProviderName(ids) {
|
|
18
|
-
return ids.length > 0 && ids.every(id => id
|
|
18
|
+
return ids.length > 0 && ids.every(id => modelIdKey(id) === 'glm-5.2')
|
|
19
19
|
? glmProviderName
|
|
20
20
|
: deepSeekProviderName;
|
|
21
21
|
}
|
|
22
22
|
function exactChatLabel(ids) {
|
|
23
|
-
return ids.length > 0 && ids.every(id => id
|
|
23
|
+
return ids.length > 0 && ids.every(id => modelIdKey(id) === 'glm-5.2')
|
|
24
24
|
? 'GLM 5.2'
|
|
25
25
|
: 'DeepSeek';
|
|
26
26
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import { ConfigureContext } from '../types/client.js';
|
|
1
|
+
import type { ClientId, ConfigureContext } from '../types/client.js';
|
|
2
|
+
import { type ModelInfo } from '../utils/api.js';
|
|
3
|
+
export { canonicalModelId, modelIdKey, sameModelId } from '../utils/modelId.js';
|
|
4
|
+
/**
|
|
5
|
+
* Model selection reads only the authenticated catalog, plus the ids the
|
|
6
|
+
* setup probe saw fail (`avoidModelIds`), which defaults skip.
|
|
7
|
+
*/
|
|
8
|
+
type CatalogContext = Pick<ConfigureContext, 'models' | 'avoidModelIds'>;
|
|
2
9
|
export declare const DEEPSEEK_V4_FLASH_MODEL_ID = "deepseek-v4-flash";
|
|
3
10
|
export declare const GLM_5_2_MODEL_ID = "glm-5.2";
|
|
4
11
|
export declare const DEEPSEEK_V4_PRO_MODEL_ID = "deepseek-v4-pro";
|
|
@@ -21,35 +28,90 @@ export interface AuthenticatedChatCapabilities {
|
|
|
21
28
|
streamingUsage: boolean;
|
|
22
29
|
reasoningReplay: boolean;
|
|
23
30
|
}
|
|
24
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The catalog row for `id`: the exact spelling first, then the same model
|
|
33
|
+
* under another context suffix (`claude-opus-5` finds `claude-opus-5[1m]`).
|
|
34
|
+
*/
|
|
35
|
+
export declare function catalogModel(ctx: CatalogContext, id: string): ModelInfo | undefined;
|
|
36
|
+
export declare function authenticatedChatCapabilities(ctx: CatalogContext, id: string): AuthenticatedChatCapabilities;
|
|
25
37
|
/** Exact model aliases that are Chat-Completions-only, never Responses API. */
|
|
26
38
|
export declare function isExactChatCompletionsModelId(id: string): boolean;
|
|
27
39
|
/** Backward-compatible internal name for legacy client configuration code. */
|
|
28
40
|
export declare const isDeepSeekChatModelId: typeof isExactChatCompletionsModelId;
|
|
29
41
|
/** Trust only the authenticated gateway catalog for endpoint capabilities. */
|
|
30
|
-
export declare function modelSupportsTools(ctx:
|
|
31
|
-
export declare function modelIds(ctx:
|
|
32
|
-
export declare function anthropicModelIds(ctx:
|
|
33
|
-
export declare function openAiModelIds(ctx:
|
|
42
|
+
export declare function modelSupportsTools(ctx: CatalogContext, id: string): boolean;
|
|
43
|
+
export declare function modelIds(ctx: CatalogContext): string[];
|
|
44
|
+
export declare function anthropicModelIds(ctx: CatalogContext): string[];
|
|
45
|
+
export declare function openAiModelIds(ctx: CatalogContext): string[];
|
|
34
46
|
/** Models that must stay on the OpenAI-compatible Chat Completions route. */
|
|
35
|
-
export declare function deepSeekChatModelIds(ctx:
|
|
47
|
+
export declare function deepSeekChatModelIds(ctx: CatalogContext): string[];
|
|
36
48
|
/** Non-Anthropic models that may remain on existing Responses-based clients. */
|
|
37
|
-
export declare function openAiResponsesModelIds(ctx:
|
|
38
|
-
|
|
39
|
-
export declare function
|
|
40
|
-
|
|
49
|
+
export declare function openAiResponsesModelIds(ctx: CatalogContext): string[];
|
|
50
|
+
/** GPT and Codex-family ids (not gpt-image-* or gpt-oss-*). */
|
|
51
|
+
export declare function isGptModelId(id: string): boolean;
|
|
52
|
+
/** The catalog says the model accepts image input. Missing metadata fails closed. */
|
|
53
|
+
export declare function modelAcceptsImages(ctx: CatalogContext, id: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* The best Claude this key has, for Claude Code and every client's Claude
|
|
56
|
+
* profile: Opus 5.5, Opus 5, Sonnet 5.5, Sonnet 5, then older Claude, then
|
|
57
|
+
* any other `claude-*` row, then an image-capable Anthropic row (Fable 5)
|
|
58
|
+
* before a text-only one. Never a model from another provider.
|
|
59
|
+
*/
|
|
60
|
+
export declare function preferredAnthropicModel(ctx: CatalogContext): string;
|
|
61
|
+
/** Every catalog id Claude Code can be pointed at: Claude rows, or Auto. */
|
|
62
|
+
export declare function claudeCodeModelIds(ctx: CatalogContext): string[];
|
|
63
|
+
export declare const CLAUDE_CODE_NEEDS_CLAUDE_MESSAGE = "This API key has no Claude model, so Claude Code was not set up. Claude Code needs a key on the Claude or OpenAI + Claude plan: https://scalemax.pro/pricing";
|
|
64
|
+
/** Claude Code's ANTHROPIC_MODEL. */
|
|
65
|
+
export declare function claudeCodeMainModel(ctx: CatalogContext): string;
|
|
66
|
+
/**
|
|
67
|
+
* Claude Code's background model (ANTHROPIC_DEFAULT_HAIKU_MODEL): Haiku when
|
|
68
|
+
* the key lists it, else the cheapest Claude the key lists, else nothing.
|
|
69
|
+
* Leaving it unset on a key without Haiku made Claude Code call its built-in
|
|
70
|
+
* Haiku id, which the key then refused on every background request.
|
|
71
|
+
*/
|
|
72
|
+
export declare function claudeCodeBackgroundModel(ctx: CatalogContext): string | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Claude Code's picker hides gateway ids that do not start with `claude` or
|
|
75
|
+
* `anthropic`, so ONE custom option exposes Fable or Auto. Fable 5 keeps the
|
|
76
|
+
* slot whenever the key has it (re-running setup never moves a user onto a
|
|
77
|
+
* different model and price); among the rest an image-capable model wins, so
|
|
78
|
+
* a pasted screenshot does not fail on a text-only default.
|
|
79
|
+
*/
|
|
80
|
+
export declare function claudeCodeCustomModelId(ctx: CatalogContext): string | undefined;
|
|
81
|
+
/** GPT models Codex can run on (Responses API). */
|
|
82
|
+
export declare function codexModelIds(ctx: CatalogContext): string[];
|
|
83
|
+
export declare const CODEX_NEEDS_GPT_MESSAGE = "This API key has no GPT model, so Codex was not set up. Codex runs on GPT models only: use a key on the OpenAI or OpenAI + Claude plan: https://scalemax.pro/pricing";
|
|
84
|
+
/**
|
|
85
|
+
* Codex's model: a GPT model the key has (gpt-5.6-terra first), or `auto` on
|
|
86
|
+
* an Auto key. Never a Claude id, the `claude-sonnet-4-6` alias, DeepSeek or
|
|
87
|
+
* another family's model: Codex's Responses requests fail on those.
|
|
88
|
+
*/
|
|
89
|
+
export declare function codexModel(ctx: CatalogContext): string;
|
|
90
|
+
/** Non-Claude default for OpenAI-compatible clients: GPT, then Local, then other families. */
|
|
91
|
+
export declare function preferredOpenAiModel(ctx: CatalogContext): string;
|
|
92
|
+
export declare function preferredOpenAiResponsesModel(ctx: CatalogContext): string;
|
|
41
93
|
/**
|
|
42
94
|
* OpenAI-compatible chat clients can use the gateway's normalized chat route
|
|
43
|
-
* for either family.
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
export declare function
|
|
49
|
-
export declare function fastModel(ctx:
|
|
50
|
-
export declare function fastOpenAiModel(ctx:
|
|
95
|
+
* for either family. A GPT model stays the default when the key has one, so
|
|
96
|
+
* mixed-catalog configurations do not switch model families; a Claude key
|
|
97
|
+
* gets its best Claude (its free and alias rows are not a reason to leave
|
|
98
|
+
* Claude); Local, Gemini, Grok and Qwen keys get their own flagship.
|
|
99
|
+
*/
|
|
100
|
+
export declare function preferredChatCompletionsModel(ctx: CatalogContext): string;
|
|
101
|
+
export declare function fastModel(ctx: CatalogContext): string | undefined;
|
|
102
|
+
export declare function fastOpenAiModel(ctx: CatalogContext): string | undefined;
|
|
51
103
|
/** The lightweight model for an OpenAI-compatible chat client, if one exists. */
|
|
52
|
-
export declare function fastChatCompletionsModel(ctx:
|
|
53
|
-
|
|
104
|
+
export declare function fastChatCompletionsModel(ctx: CatalogContext): string | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* The model ids setup would write as defaults for these clients, most
|
|
107
|
+
* important first: Claude Code's main, background and picker models, Codex's
|
|
108
|
+
* model, then the chat and agent clients' defaults. Lists (Cursor, OpenCode's
|
|
109
|
+
* model table) are not defaults. The setup probe checks exactly these and
|
|
110
|
+
* asks again after a failure, instead of the first rows of the catalog.
|
|
111
|
+
*/
|
|
112
|
+
export declare function defaultModelIdsFor(ctx: CatalogContext & Pick<ConfigureContext, 'providerScope'>, clientIds: readonly ClientId[]): string[];
|
|
113
|
+
export declare function modelName(ctx: CatalogContext, id: string): string;
|
|
114
|
+
/** Opus 5 / 5.5 publish a 128k output cap that OpenCode and OpenClaw honour. */
|
|
115
|
+
export declare function hasLargeOutputCap(id: string): boolean;
|
|
54
116
|
export declare function anthropicOrigin(baseUrl: string): string;
|
|
55
117
|
export declare function openAiBaseUrl(baseUrl: string): string;
|