@chrysb/alphaclaw 0.9.16 → 0.9.18
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 +25 -0
- package/lib/public/css/tailwind.generated.css +1 -1
- package/lib/public/dist/app.bundle.js +1858 -1758
- package/lib/public/js/components/agents-tab/agent-overview/model-card.js +59 -7
- package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +124 -0
- package/lib/public/js/components/api-feature-panel.js +76 -0
- package/lib/public/js/components/envars.js +1 -1
- package/lib/public/js/components/general/index.js +6 -0
- package/lib/public/js/components/general/use-general-tab.js +69 -0
- package/lib/public/js/components/row-accessory-select.js +52 -0
- package/lib/public/js/lib/api.js +26 -0
- package/lib/public/js/lib/model-catalog.js +6 -0
- package/lib/public/js/lib/model-config.js +12 -7
- package/lib/public/js/lib/storage-keys.js +4 -0
- package/lib/public/js/lib/thinking-levels.js +37 -0
- package/lib/server/agents/agents.js +33 -7
- package/lib/server/agents/channels.js +4 -2
- package/lib/server/alphaclaw-config.js +99 -0
- package/lib/server/chat-ws.js +4 -1
- package/lib/server/constants.js +73 -0
- package/lib/server/cost-utils.js +2 -0
- package/lib/server/db/auth/index.js +147 -0
- package/lib/server/db/auth/schema.js +17 -0
- package/lib/server/gateway.js +321 -20
- package/lib/server/helpers.js +1 -3
- package/lib/server/init/register-server-routes.js +45 -18
- package/lib/server/init/runtime-init.js +4 -0
- package/lib/server/init/server-lifecycle.js +1 -24
- package/lib/server/login-throttle.js +261 -60
- package/lib/server/model-catalog-bootstrap.json +5 -0
- package/lib/server/onboarding/index.js +2 -2
- package/lib/server/onboarding/openclaw.js +27 -3
- package/lib/server/openclaw-thinking.js +103 -0
- package/lib/server/openclaw-version.js +1 -1
- package/lib/server/routes/agents.js +10 -3
- package/lib/server/routes/models.js +35 -1
- package/lib/server/routes/onboarding.js +2 -2
- package/lib/server/routes/proxy.js +219 -1
- package/lib/server/routes/system.js +63 -2
- package/lib/server/usage-tracker-config.js +52 -1
- package/lib/server.js +60 -22
- package/package.json +2 -2
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const kThinkingLevelLabelOverrides = {
|
|
2
|
+
off: "Off",
|
|
3
|
+
on: "On",
|
|
4
|
+
minimal: "Minimal",
|
|
5
|
+
low: "Low",
|
|
6
|
+
medium: "Medium",
|
|
7
|
+
high: "High",
|
|
8
|
+
adaptive: "Adaptive",
|
|
9
|
+
xhigh: "Extra high",
|
|
10
|
+
max: "Maximum",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const formatThinkingLevelLabel = (levelId = "") => {
|
|
14
|
+
const normalized = String(levelId || "").trim().toLowerCase();
|
|
15
|
+
if (!normalized) return "";
|
|
16
|
+
if (kThinkingLevelLabelOverrides[normalized]) {
|
|
17
|
+
return kThinkingLevelLabelOverrides[normalized];
|
|
18
|
+
}
|
|
19
|
+
return normalized
|
|
20
|
+
.split(/[-_]/g)
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
23
|
+
.join(" ");
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const formatInheritedThinkingLabel = (levelId = "") => {
|
|
27
|
+
const label = formatThinkingLevelLabel(levelId);
|
|
28
|
+
return label ? `Inherited: ${label}` : "Inherited";
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const shouldShowThinkingLevelSelect = (levels = []) => {
|
|
32
|
+
const normalized = (Array.isArray(levels) ? levels : [])
|
|
33
|
+
.map((entry) => String(entry?.id || entry || "").trim().toLowerCase())
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
if (normalized.length === 0) return false;
|
|
36
|
+
return !(normalized.length === 1 && normalized[0] === "off");
|
|
37
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
+
const { normalizeThinkingDefaultValue } = require("../openclaw-thinking");
|
|
2
3
|
|
|
3
4
|
const {
|
|
4
5
|
kDefaultAgentId,
|
|
@@ -42,11 +43,25 @@ const toReadableAgent = (agent = {}) => ({
|
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
|
|
45
|
-
const
|
|
46
|
-
|
|
46
|
+
const readAgentsConfig = () =>
|
|
47
|
+
withNormalizedAgentsConfig({
|
|
47
48
|
OPENCLAW_DIR,
|
|
48
49
|
cfg: loadConfig({ fsImpl, OPENCLAW_DIR }),
|
|
49
50
|
});
|
|
51
|
+
|
|
52
|
+
const getAgentDefaults = () => {
|
|
53
|
+
const cfg = readAgentsConfig();
|
|
54
|
+
const thinkingDefault = cfg.agents?.defaults?.thinkingDefault;
|
|
55
|
+
return {
|
|
56
|
+
thinkingDefault:
|
|
57
|
+
typeof thinkingDefault === "string" && thinkingDefault.trim()
|
|
58
|
+
? thinkingDefault.trim()
|
|
59
|
+
: null,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const listAgents = () => {
|
|
64
|
+
const cfg = readAgentsConfig();
|
|
50
65
|
return (cfg.agents?.list || []).map((entry) => toReadableAgent(entry));
|
|
51
66
|
};
|
|
52
67
|
|
|
@@ -131,12 +146,9 @@ const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
|
|
|
131
146
|
return toReadableAgent(nextAgent);
|
|
132
147
|
};
|
|
133
148
|
|
|
134
|
-
const updateAgent = (agentId, patch = {}) => {
|
|
149
|
+
const updateAgent = async (agentId, patch = {}) => {
|
|
135
150
|
const normalized = String(agentId || "").trim();
|
|
136
|
-
const cfg =
|
|
137
|
-
OPENCLAW_DIR,
|
|
138
|
-
cfg: loadConfig({ fsImpl, OPENCLAW_DIR }),
|
|
139
|
-
});
|
|
151
|
+
const cfg = readAgentsConfig();
|
|
140
152
|
const index = cfg.agents.list.findIndex((entry) => entry.id === normalized);
|
|
141
153
|
if (index < 0) throw new Error(`Agent "${normalized}" not found`);
|
|
142
154
|
const current = cfg.agents.list[index];
|
|
@@ -188,6 +200,19 @@ const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
|
|
|
188
200
|
delete next.tools;
|
|
189
201
|
}
|
|
190
202
|
}
|
|
203
|
+
if (patch.thinkingDefault !== undefined) {
|
|
204
|
+
if (patch.thinkingDefault === null) {
|
|
205
|
+
delete next.thinkingDefault;
|
|
206
|
+
} else {
|
|
207
|
+
const normalizedThinking = await normalizeThinkingDefaultValue(
|
|
208
|
+
patch.thinkingDefault,
|
|
209
|
+
);
|
|
210
|
+
if (!normalizedThinking) {
|
|
211
|
+
throw new Error("Invalid thinkingDefault value");
|
|
212
|
+
}
|
|
213
|
+
next.thinkingDefault = normalizedThinking;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
191
216
|
cfg.agents.list[index] = next;
|
|
192
217
|
saveConfig({ fsImpl, OPENCLAW_DIR, config: cfg });
|
|
193
218
|
return toReadableAgent(next);
|
|
@@ -253,6 +278,7 @@ const createAgentsDomain = ({ fsImpl, OPENCLAW_DIR }) => {
|
|
|
253
278
|
return {
|
|
254
279
|
listAgents,
|
|
255
280
|
getAgent,
|
|
281
|
+
getAgentDefaults,
|
|
256
282
|
getAgentWorkspaceSize,
|
|
257
283
|
createAgent,
|
|
258
284
|
updateAgent,
|
|
@@ -270,7 +270,7 @@ const createChannelsDomain = ({
|
|
|
270
270
|
|
|
271
271
|
const previousConfig = cloneJson(cfg);
|
|
272
272
|
try {
|
|
273
|
-
onProgress({ phase: "
|
|
273
|
+
onProgress({ phase: "configuring", label: "Configuring..." });
|
|
274
274
|
writeEnvFile(nextEnvVars);
|
|
275
275
|
reloadEnv();
|
|
276
276
|
assertActiveChannelTokenEnvVars({
|
|
@@ -280,7 +280,6 @@ const createChannelsDomain = ({
|
|
|
280
280
|
}),
|
|
281
281
|
envVars: nextEnvVars,
|
|
282
282
|
});
|
|
283
|
-
await restartGateway();
|
|
284
283
|
const pluginEnabledCfg = withNormalizedAgentsConfig({
|
|
285
284
|
OPENCLAW_DIR,
|
|
286
285
|
cfg: loadConfig({ fsImpl, OPENCLAW_DIR }),
|
|
@@ -374,6 +373,8 @@ const createChannelsDomain = ({
|
|
|
374
373
|
"Could not bind channel account",
|
|
375
374
|
);
|
|
376
375
|
}
|
|
376
|
+
onProgress({ phase: "restarting", label: "Rebooting..." });
|
|
377
|
+
await restartGateway();
|
|
377
378
|
} catch (error) {
|
|
378
379
|
try {
|
|
379
380
|
await clawCmd(
|
|
@@ -760,6 +761,7 @@ const createChannelsDomain = ({
|
|
|
760
761
|
}
|
|
761
762
|
|
|
762
763
|
cleanupChannelAccountPairingFiles({ provider, accountId });
|
|
764
|
+
await restartGateway();
|
|
763
765
|
return { ok: true };
|
|
764
766
|
}
|
|
765
767
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const kConfigFileName = "alphaclaw.json";
|
|
5
|
+
const kDefaultAlphaclawConfig = Object.freeze({
|
|
6
|
+
features: Object.freeze({
|
|
7
|
+
openaiCompatApi: Object.freeze({
|
|
8
|
+
enabled: false,
|
|
9
|
+
}),
|
|
10
|
+
}),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const resolveAlphaclawConfigPath = ({ openclawDir } = {}) =>
|
|
14
|
+
path.join(openclawDir || process.cwd(), kConfigFileName);
|
|
15
|
+
|
|
16
|
+
const normalizeOpenAiCompatApiFeature = (feature = {}) => ({
|
|
17
|
+
...(feature && typeof feature === "object" ? feature : {}),
|
|
18
|
+
enabled: feature?.enabled === true,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const normalizeAlphaclawConfig = (raw = {}) => {
|
|
22
|
+
const base = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
23
|
+
const features =
|
|
24
|
+
base.features && typeof base.features === "object" && !Array.isArray(base.features)
|
|
25
|
+
? base.features
|
|
26
|
+
: {};
|
|
27
|
+
return {
|
|
28
|
+
...base,
|
|
29
|
+
features: {
|
|
30
|
+
...features,
|
|
31
|
+
openaiCompatApi: normalizeOpenAiCompatApiFeature(features.openaiCompatApi),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const readAlphaclawConfig = ({
|
|
37
|
+
fsModule = fs,
|
|
38
|
+
openclawDir,
|
|
39
|
+
fallback = kDefaultAlphaclawConfig,
|
|
40
|
+
} = {}) => {
|
|
41
|
+
try {
|
|
42
|
+
const configPath = resolveAlphaclawConfigPath({ openclawDir });
|
|
43
|
+
const raw = fsModule.readFileSync(configPath, "utf8");
|
|
44
|
+
return normalizeAlphaclawConfig(JSON.parse(raw));
|
|
45
|
+
} catch {
|
|
46
|
+
return normalizeAlphaclawConfig(fallback);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const writeAlphaclawConfig = ({
|
|
51
|
+
fsModule = fs,
|
|
52
|
+
openclawDir,
|
|
53
|
+
config,
|
|
54
|
+
spacing = 2,
|
|
55
|
+
} = {}) => {
|
|
56
|
+
const configPath = resolveAlphaclawConfigPath({ openclawDir });
|
|
57
|
+
fsModule.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
58
|
+
const normalized = normalizeAlphaclawConfig(config);
|
|
59
|
+
fsModule.writeFileSync(configPath, `${JSON.stringify(normalized, null, spacing)}\n`);
|
|
60
|
+
return normalized;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const isOpenAiCompatApiEnabled = (options = {}) =>
|
|
64
|
+
readAlphaclawConfig(options).features.openaiCompatApi.enabled === true;
|
|
65
|
+
|
|
66
|
+
const updateOpenAiCompatApiFeature = ({
|
|
67
|
+
fsModule = fs,
|
|
68
|
+
openclawDir,
|
|
69
|
+
enabled,
|
|
70
|
+
} = {}) => {
|
|
71
|
+
const current = readAlphaclawConfig({ fsModule, openclawDir });
|
|
72
|
+
const next = normalizeAlphaclawConfig({
|
|
73
|
+
...current,
|
|
74
|
+
features: {
|
|
75
|
+
...current.features,
|
|
76
|
+
openaiCompatApi: {
|
|
77
|
+
...current.features.openaiCompatApi,
|
|
78
|
+
enabled: enabled === true,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
const changed =
|
|
83
|
+
current.features.openaiCompatApi.enabled !== next.features.openaiCompatApi.enabled;
|
|
84
|
+
return {
|
|
85
|
+
config: writeAlphaclawConfig({ fsModule, openclawDir, config: next }),
|
|
86
|
+
changed,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
kConfigFileName,
|
|
92
|
+
kDefaultAlphaclawConfig,
|
|
93
|
+
isOpenAiCompatApiEnabled,
|
|
94
|
+
normalizeAlphaclawConfig,
|
|
95
|
+
readAlphaclawConfig,
|
|
96
|
+
resolveAlphaclawConfigPath,
|
|
97
|
+
updateOpenAiCompatApiFeature,
|
|
98
|
+
writeAlphaclawConfig,
|
|
99
|
+
};
|
package/lib/server/chat-ws.js
CHANGED
|
@@ -6,7 +6,7 @@ const kEnvRefPattern = /^\$\{([A-Z0-9_]+)\}$/i;
|
|
|
6
6
|
const kConnectTimeoutMs = 8000;
|
|
7
7
|
const kHistoryTimeoutMs = 12000;
|
|
8
8
|
const kGatewayReqTimeoutMs = 15000;
|
|
9
|
-
const kGatewayProtocolVersion =
|
|
9
|
+
const kGatewayProtocolVersion = 4;
|
|
10
10
|
// Gateway method auth (see OpenClaw method-scopes): chat.history needs operator.read;
|
|
11
11
|
// chat.send / chat.abort need operator.write. Align with CLI_DEFAULT_OPERATOR_SCOPES plus admin.
|
|
12
12
|
const kGatewayChatBridgeScopes = [
|
|
@@ -283,6 +283,9 @@ const sanitizeError = (error) => {
|
|
|
283
283
|
) {
|
|
284
284
|
return "Gateway authentication failed. Verify OPENCLAW_GATEWAY_TOKEN matches the gateway.";
|
|
285
285
|
}
|
|
286
|
+
if (lower.includes("protocol mismatch")) {
|
|
287
|
+
return "Chat cannot connect to the gateway (protocol version mismatch). Update AlphaClaw to match your OpenClaw version.";
|
|
288
|
+
}
|
|
286
289
|
if (lower.includes("method not found") || lower.includes("unknown method")) {
|
|
287
290
|
return "This gateway build does not support chat APIs. Update OpenClaw.";
|
|
288
291
|
}
|
package/lib/server/constants.js
CHANGED
|
@@ -54,6 +54,22 @@ const kLoginMaxLockMs = parsePositiveInt(
|
|
|
54
54
|
process.env.LOGIN_RATE_MAX_LOCK_MS,
|
|
55
55
|
15 * 60 * 1000,
|
|
56
56
|
);
|
|
57
|
+
const kLoginGlobalWindowMs = parsePositiveInt(
|
|
58
|
+
process.env.LOGIN_RATE_GLOBAL_WINDOW_MS,
|
|
59
|
+
kLoginWindowMs,
|
|
60
|
+
);
|
|
61
|
+
const kLoginGlobalMaxAttempts = parsePositiveInt(
|
|
62
|
+
process.env.LOGIN_RATE_GLOBAL_MAX_ATTEMPTS,
|
|
63
|
+
Math.max(kLoginMaxAttempts * 5, 25),
|
|
64
|
+
);
|
|
65
|
+
const kLoginGlobalBaseLockMs = parsePositiveInt(
|
|
66
|
+
process.env.LOGIN_RATE_GLOBAL_BASE_LOCK_MS,
|
|
67
|
+
kLoginBaseLockMs,
|
|
68
|
+
);
|
|
69
|
+
const kLoginGlobalMaxLockMs = parsePositiveInt(
|
|
70
|
+
process.env.LOGIN_RATE_GLOBAL_MAX_LOCK_MS,
|
|
71
|
+
kLoginMaxLockMs,
|
|
72
|
+
);
|
|
57
73
|
const kLoginCleanupIntervalMs = parsePositiveInt(
|
|
58
74
|
process.env.LOGIN_RATE_CLEANUP_INTERVAL_MS,
|
|
59
75
|
60 * 1000,
|
|
@@ -65,6 +81,45 @@ const kLoginStateTtlMs = Math.max(
|
|
|
65
81
|
),
|
|
66
82
|
kLoginMaxLockMs,
|
|
67
83
|
);
|
|
84
|
+
const kOpenAiCompatApiRateWindowMs = parsePositiveInt(
|
|
85
|
+
process.env.OPENAI_COMPAT_API_RATE_WINDOW_MS,
|
|
86
|
+
kLoginWindowMs,
|
|
87
|
+
);
|
|
88
|
+
const kOpenAiCompatApiRateMaxAttempts = parsePositiveInt(
|
|
89
|
+
process.env.OPENAI_COMPAT_API_RATE_MAX_ATTEMPTS,
|
|
90
|
+
10,
|
|
91
|
+
);
|
|
92
|
+
const kOpenAiCompatApiRateBaseLockMs = parsePositiveInt(
|
|
93
|
+
process.env.OPENAI_COMPAT_API_RATE_BASE_LOCK_MS,
|
|
94
|
+
kLoginBaseLockMs,
|
|
95
|
+
);
|
|
96
|
+
const kOpenAiCompatApiRateMaxLockMs = parsePositiveInt(
|
|
97
|
+
process.env.OPENAI_COMPAT_API_RATE_MAX_LOCK_MS,
|
|
98
|
+
kLoginMaxLockMs,
|
|
99
|
+
);
|
|
100
|
+
const kOpenAiCompatApiRateGlobalWindowMs = parsePositiveInt(
|
|
101
|
+
process.env.OPENAI_COMPAT_API_RATE_GLOBAL_WINDOW_MS,
|
|
102
|
+
kOpenAiCompatApiRateWindowMs,
|
|
103
|
+
);
|
|
104
|
+
const kOpenAiCompatApiRateGlobalMaxAttempts = parsePositiveInt(
|
|
105
|
+
process.env.OPENAI_COMPAT_API_RATE_GLOBAL_MAX_ATTEMPTS,
|
|
106
|
+
Math.max(kOpenAiCompatApiRateMaxAttempts * 10, 100),
|
|
107
|
+
);
|
|
108
|
+
const kOpenAiCompatApiRateGlobalBaseLockMs = parsePositiveInt(
|
|
109
|
+
process.env.OPENAI_COMPAT_API_RATE_GLOBAL_BASE_LOCK_MS,
|
|
110
|
+
kOpenAiCompatApiRateBaseLockMs,
|
|
111
|
+
);
|
|
112
|
+
const kOpenAiCompatApiRateGlobalMaxLockMs = parsePositiveInt(
|
|
113
|
+
process.env.OPENAI_COMPAT_API_RATE_GLOBAL_MAX_LOCK_MS,
|
|
114
|
+
kOpenAiCompatApiRateMaxLockMs,
|
|
115
|
+
);
|
|
116
|
+
const kOpenAiCompatApiRateStateTtlMs = Math.max(
|
|
117
|
+
parsePositiveInt(
|
|
118
|
+
process.env.OPENAI_COMPAT_API_RATE_STATE_TTL_MS,
|
|
119
|
+
Math.max(kOpenAiCompatApiRateWindowMs, kOpenAiCompatApiRateMaxLockMs) * 3,
|
|
120
|
+
),
|
|
121
|
+
kOpenAiCompatApiRateMaxLockMs,
|
|
122
|
+
);
|
|
68
123
|
|
|
69
124
|
const kOnboardingModelProviders = new Set([
|
|
70
125
|
"anthropic",
|
|
@@ -92,6 +147,11 @@ const kOnboardingModelProviders = new Set([
|
|
|
92
147
|
"vllm",
|
|
93
148
|
]);
|
|
94
149
|
const kMinimalFallbackOnboardingModels = [
|
|
150
|
+
{
|
|
151
|
+
key: "anthropic/claude-opus-4-8",
|
|
152
|
+
provider: "anthropic",
|
|
153
|
+
label: "Claude Opus 4.8",
|
|
154
|
+
},
|
|
95
155
|
{
|
|
96
156
|
key: "anthropic/claude-opus-4-7",
|
|
97
157
|
provider: "anthropic",
|
|
@@ -445,8 +505,21 @@ module.exports = {
|
|
|
445
505
|
kLoginMaxAttempts,
|
|
446
506
|
kLoginBaseLockMs,
|
|
447
507
|
kLoginMaxLockMs,
|
|
508
|
+
kLoginGlobalWindowMs,
|
|
509
|
+
kLoginGlobalMaxAttempts,
|
|
510
|
+
kLoginGlobalBaseLockMs,
|
|
511
|
+
kLoginGlobalMaxLockMs,
|
|
448
512
|
kLoginCleanupIntervalMs,
|
|
449
513
|
kLoginStateTtlMs,
|
|
514
|
+
kOpenAiCompatApiRateWindowMs,
|
|
515
|
+
kOpenAiCompatApiRateMaxAttempts,
|
|
516
|
+
kOpenAiCompatApiRateBaseLockMs,
|
|
517
|
+
kOpenAiCompatApiRateMaxLockMs,
|
|
518
|
+
kOpenAiCompatApiRateGlobalWindowMs,
|
|
519
|
+
kOpenAiCompatApiRateGlobalMaxAttempts,
|
|
520
|
+
kOpenAiCompatApiRateGlobalBaseLockMs,
|
|
521
|
+
kOpenAiCompatApiRateGlobalMaxLockMs,
|
|
522
|
+
kOpenAiCompatApiRateStateTtlMs,
|
|
450
523
|
kOnboardingModelProviders,
|
|
451
524
|
kFallbackOnboardingModels,
|
|
452
525
|
kVersionCacheTtlMs,
|
package/lib/server/cost-utils.js
CHANGED
|
@@ -13,6 +13,8 @@ const kClaudeOpus47Pricing = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const kGlobalModelPricing = {
|
|
16
|
+
"claude-opus-4-8": kClaudeOpus47Pricing,
|
|
17
|
+
"claude-opus-4.8": kClaudeOpus47Pricing,
|
|
16
18
|
"claude-opus-4-7": kClaudeOpus47Pricing,
|
|
17
19
|
"claude-opus-4.7": kClaudeOpus47Pricing,
|
|
18
20
|
"claude-opus-4-6": {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { DatabaseSync } = require("node:sqlite");
|
|
4
|
+
const { createSchema } = require("./schema");
|
|
5
|
+
|
|
6
|
+
let db = null;
|
|
7
|
+
|
|
8
|
+
const ensureDb = () => {
|
|
9
|
+
if (!db) throw new Error("Auth DB not initialized");
|
|
10
|
+
return db;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const closeAuthDb = () => {
|
|
14
|
+
if (!db) return;
|
|
15
|
+
const database = db;
|
|
16
|
+
db = null;
|
|
17
|
+
database.close();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const initAuthDb = ({ rootDir }) => {
|
|
21
|
+
closeAuthDb();
|
|
22
|
+
const dbDir = path.join(rootDir, "db");
|
|
23
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
24
|
+
const dbPath = path.join(dbDir, "auth.db");
|
|
25
|
+
db = new DatabaseSync(dbPath);
|
|
26
|
+
db.exec("PRAGMA busy_timeout = 5000");
|
|
27
|
+
createSchema(db);
|
|
28
|
+
return { path: dbPath };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const toStateModel = (row) => {
|
|
32
|
+
if (!row) return null;
|
|
33
|
+
return {
|
|
34
|
+
attempts: Number(row.attempts || 0),
|
|
35
|
+
windowStart: Number(row.window_start || 0),
|
|
36
|
+
lockUntil: Number(row.lock_until || 0),
|
|
37
|
+
failStreak: Number(row.fail_streak || 0),
|
|
38
|
+
lastSeenAt: Number(row.last_seen_at || 0),
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const createLoginThrottleStore = () => ({
|
|
43
|
+
get: (stateKey) => {
|
|
44
|
+
const row = ensureDb()
|
|
45
|
+
.prepare(
|
|
46
|
+
`
|
|
47
|
+
SELECT
|
|
48
|
+
attempts,
|
|
49
|
+
window_start,
|
|
50
|
+
lock_until,
|
|
51
|
+
fail_streak,
|
|
52
|
+
last_seen_at
|
|
53
|
+
FROM login_throttle_states
|
|
54
|
+
WHERE state_key = $state_key
|
|
55
|
+
LIMIT 1
|
|
56
|
+
`,
|
|
57
|
+
)
|
|
58
|
+
.get({ $state_key: String(stateKey || "") });
|
|
59
|
+
return toStateModel(row);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
set: (stateKey, state) => {
|
|
63
|
+
ensureDb()
|
|
64
|
+
.prepare(
|
|
65
|
+
`
|
|
66
|
+
INSERT INTO login_throttle_states (
|
|
67
|
+
state_key,
|
|
68
|
+
attempts,
|
|
69
|
+
window_start,
|
|
70
|
+
lock_until,
|
|
71
|
+
fail_streak,
|
|
72
|
+
last_seen_at
|
|
73
|
+
) VALUES (
|
|
74
|
+
$state_key,
|
|
75
|
+
$attempts,
|
|
76
|
+
$window_start,
|
|
77
|
+
$lock_until,
|
|
78
|
+
$fail_streak,
|
|
79
|
+
$last_seen_at
|
|
80
|
+
)
|
|
81
|
+
ON CONFLICT(state_key) DO UPDATE SET
|
|
82
|
+
attempts = excluded.attempts,
|
|
83
|
+
window_start = excluded.window_start,
|
|
84
|
+
lock_until = excluded.lock_until,
|
|
85
|
+
fail_streak = excluded.fail_streak,
|
|
86
|
+
last_seen_at = excluded.last_seen_at
|
|
87
|
+
`,
|
|
88
|
+
)
|
|
89
|
+
.run({
|
|
90
|
+
$state_key: String(stateKey || ""),
|
|
91
|
+
$attempts: Number(state?.attempts || 0),
|
|
92
|
+
$window_start: Number(state?.windowStart || 0),
|
|
93
|
+
$lock_until: Number(state?.lockUntil || 0),
|
|
94
|
+
$fail_streak: Number(state?.failStreak || 0),
|
|
95
|
+
$last_seen_at: Number(state?.lastSeenAt || 0),
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
delete: (stateKey) => {
|
|
100
|
+
ensureDb()
|
|
101
|
+
.prepare(
|
|
102
|
+
`
|
|
103
|
+
DELETE FROM login_throttle_states
|
|
104
|
+
WHERE state_key = $state_key
|
|
105
|
+
`,
|
|
106
|
+
)
|
|
107
|
+
.run({ $state_key: String(stateKey || "") });
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
entries: () =>
|
|
111
|
+
ensureDb()
|
|
112
|
+
.prepare(
|
|
113
|
+
`
|
|
114
|
+
SELECT
|
|
115
|
+
state_key,
|
|
116
|
+
attempts,
|
|
117
|
+
window_start,
|
|
118
|
+
lock_until,
|
|
119
|
+
fail_streak,
|
|
120
|
+
last_seen_at
|
|
121
|
+
FROM login_throttle_states
|
|
122
|
+
`,
|
|
123
|
+
)
|
|
124
|
+
.all()
|
|
125
|
+
.map((row) => [String(row.state_key || ""), toStateModel(row)]),
|
|
126
|
+
|
|
127
|
+
runExclusive: (callback) => {
|
|
128
|
+
const database = ensureDb();
|
|
129
|
+
database.exec("BEGIN IMMEDIATE");
|
|
130
|
+
try {
|
|
131
|
+
const result = callback();
|
|
132
|
+
database.exec("COMMIT");
|
|
133
|
+
return result;
|
|
134
|
+
} catch (err) {
|
|
135
|
+
try {
|
|
136
|
+
database.exec("ROLLBACK");
|
|
137
|
+
} catch {}
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
initAuthDb,
|
|
145
|
+
closeAuthDb,
|
|
146
|
+
createLoginThrottleStore,
|
|
147
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const createSchema = (db) => {
|
|
2
|
+
db.exec(`
|
|
3
|
+
CREATE TABLE IF NOT EXISTS login_throttle_states (
|
|
4
|
+
state_key TEXT PRIMARY KEY,
|
|
5
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
6
|
+
window_start INTEGER NOT NULL,
|
|
7
|
+
lock_until INTEGER NOT NULL DEFAULT 0,
|
|
8
|
+
fail_streak INTEGER NOT NULL DEFAULT 0,
|
|
9
|
+
last_seen_at INTEGER NOT NULL
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
CREATE INDEX IF NOT EXISTS idx_login_throttle_states_last_seen
|
|
13
|
+
ON login_throttle_states(last_seen_at);
|
|
14
|
+
`);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
module.exports = { createSchema };
|