@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
|
@@ -1,77 +1,274 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const next = {
|
|
13
|
-
attempts: 0,
|
|
14
|
-
windowStart: now,
|
|
15
|
-
lockUntil: 0,
|
|
16
|
-
failStreak: 0,
|
|
17
|
-
lastSeenAt: now,
|
|
18
|
-
};
|
|
19
|
-
kLoginAttemptStates.set(clientKey, next);
|
|
20
|
-
return next;
|
|
21
|
-
};
|
|
1
|
+
const {
|
|
2
|
+
kLoginWindowMs,
|
|
3
|
+
kLoginMaxAttempts,
|
|
4
|
+
kLoginBaseLockMs,
|
|
5
|
+
kLoginMaxLockMs,
|
|
6
|
+
kLoginGlobalWindowMs,
|
|
7
|
+
kLoginGlobalMaxAttempts,
|
|
8
|
+
kLoginGlobalBaseLockMs,
|
|
9
|
+
kLoginGlobalMaxLockMs,
|
|
10
|
+
kLoginStateTtlMs,
|
|
11
|
+
} = require("./constants");
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
13
|
+
const kGlobalStateKey = "global:login";
|
|
14
|
+
const kLoginThrottleScope = "login";
|
|
15
|
+
|
|
16
|
+
const createLoginAttemptState = (now) => ({
|
|
17
|
+
attempts: 0,
|
|
18
|
+
windowStart: now,
|
|
19
|
+
lockUntil: 0,
|
|
20
|
+
failStreak: 0,
|
|
21
|
+
lastSeenAt: now,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const normalizeState = (state, now) => ({
|
|
25
|
+
attempts: Number.parseInt(String(state?.attempts ?? 0), 10) || 0,
|
|
26
|
+
windowStart: Number.parseInt(String(state?.windowStart ?? now), 10) || now,
|
|
27
|
+
lockUntil: Number.parseInt(String(state?.lockUntil ?? 0), 10) || 0,
|
|
28
|
+
failStreak: Number.parseInt(String(state?.failStreak ?? 0), 10) || 0,
|
|
29
|
+
lastSeenAt: Number.parseInt(String(state?.lastSeenAt ?? now), 10) || now,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const createMemoryLoginThrottleStore = () => {
|
|
33
|
+
const states = new Map();
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
get: (stateKey) => states.get(stateKey) || null,
|
|
37
|
+
set: (stateKey, state) => {
|
|
38
|
+
states.set(stateKey, { ...state });
|
|
39
|
+
},
|
|
40
|
+
delete: (stateKey) => {
|
|
41
|
+
states.delete(stateKey);
|
|
42
|
+
},
|
|
43
|
+
entries: () =>
|
|
44
|
+
Array.from(states.entries()).map(([stateKey, state]) => [
|
|
45
|
+
stateKey,
|
|
46
|
+
{ ...state },
|
|
47
|
+
]),
|
|
48
|
+
runExclusive: (callback) => callback(),
|
|
36
49
|
};
|
|
50
|
+
};
|
|
37
51
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
const getClientStateKey = (clientKey, scope = kLoginThrottleScope) => {
|
|
53
|
+
const normalizedClientKey = String(clientKey || "unknown");
|
|
54
|
+
return scope === kLoginThrottleScope
|
|
55
|
+
? `client:${normalizedClientKey}`
|
|
56
|
+
: `client:${scope}:${normalizedClientKey}`;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const getGlobalStateKey = (scope = kLoginThrottleScope) =>
|
|
60
|
+
scope === kLoginThrottleScope ? kGlobalStateKey : `global:${scope}`;
|
|
61
|
+
|
|
62
|
+
const getOrCreateState = (store, stateKey, now) => {
|
|
63
|
+
const existing = store.get(stateKey);
|
|
64
|
+
if (existing) {
|
|
65
|
+
const state = normalizeState(existing, now);
|
|
45
66
|
state.lastSeenAt = now;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
store.set(stateKey, state);
|
|
68
|
+
return state;
|
|
69
|
+
}
|
|
70
|
+
const next = createLoginAttemptState(now);
|
|
71
|
+
store.set(stateKey, next);
|
|
72
|
+
return next;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const evaluateState = ({ store, stateKey, now, windowMs }) => {
|
|
76
|
+
const state = getOrCreateState(store, stateKey, now);
|
|
77
|
+
if (state.lockUntil > now) {
|
|
78
|
+
return {
|
|
79
|
+
state,
|
|
80
|
+
blocked: true,
|
|
81
|
+
retryAfterSec: Math.max(1, Math.ceil((state.lockUntil - now) / 1000)),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (now - state.windowStart >= windowMs) {
|
|
85
|
+
state.attempts = 0;
|
|
86
|
+
state.windowStart = now;
|
|
87
|
+
store.set(stateKey, state);
|
|
88
|
+
}
|
|
89
|
+
return { state, blocked: false, retryAfterSec: 0 };
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const recordStateFailure = ({
|
|
93
|
+
store,
|
|
94
|
+
stateKey,
|
|
95
|
+
now,
|
|
96
|
+
windowMs,
|
|
97
|
+
maxAttempts,
|
|
98
|
+
baseLockMs,
|
|
99
|
+
maxLockMs,
|
|
100
|
+
}) => {
|
|
101
|
+
const state = getOrCreateState(store, stateKey, now);
|
|
102
|
+
if (state.lockUntil > now) {
|
|
103
|
+
return {
|
|
104
|
+
state,
|
|
105
|
+
lockMs: state.lockUntil - now,
|
|
106
|
+
locked: true,
|
|
107
|
+
retryAfterSec: Math.max(1, Math.ceil((state.lockUntil - now) / 1000)),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (now - state.windowStart >= windowMs) {
|
|
50
111
|
state.attempts = 0;
|
|
51
112
|
state.windowStart = now;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
113
|
+
}
|
|
114
|
+
state.attempts += 1;
|
|
115
|
+
state.lastSeenAt = now;
|
|
116
|
+
if (state.attempts < maxAttempts) {
|
|
117
|
+
store.set(stateKey, state);
|
|
118
|
+
return { state, lockMs: 0, locked: false, retryAfterSec: 0 };
|
|
119
|
+
}
|
|
120
|
+
state.failStreak += 1;
|
|
121
|
+
state.attempts = 0;
|
|
122
|
+
state.windowStart = now;
|
|
123
|
+
const lockMultiplier = Math.max(1, 2 ** (state.failStreak - 1));
|
|
124
|
+
const lockMs = Math.min(baseLockMs * lockMultiplier, maxLockMs);
|
|
125
|
+
state.lockUntil = now + lockMs;
|
|
126
|
+
store.set(stateKey, state);
|
|
127
|
+
return {
|
|
128
|
+
state,
|
|
129
|
+
lockMs,
|
|
130
|
+
locked: true,
|
|
131
|
+
retryAfterSec: Math.max(1, Math.ceil(lockMs / 1000)),
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const chooseThrottleResult = (...results) => {
|
|
136
|
+
const blockedResults = results.filter((result) => result.blocked);
|
|
137
|
+
if (blockedResults.length === 0) {
|
|
138
|
+
return { blocked: false, retryAfterSec: 0 };
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
blocked: true,
|
|
142
|
+
retryAfterSec: Math.max(
|
|
143
|
+
...blockedResults.map((result) => result.retryAfterSec || 0),
|
|
144
|
+
),
|
|
56
145
|
};
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const chooseFailureResult = (...results) => {
|
|
149
|
+
const lockedResults = results.filter((result) => result.locked);
|
|
150
|
+
if (lockedResults.length === 0) {
|
|
151
|
+
return { lockMs: 0, locked: false, retryAfterSec: 0 };
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
lockMs: Math.max(...lockedResults.map((result) => result.lockMs || 0)),
|
|
155
|
+
locked: true,
|
|
156
|
+
retryAfterSec: Math.max(
|
|
157
|
+
...lockedResults.map((result) => result.retryAfterSec || 0),
|
|
158
|
+
),
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const createLoginThrottle = ({
|
|
163
|
+
store = createMemoryLoginThrottleStore(),
|
|
164
|
+
scope = kLoginThrottleScope,
|
|
165
|
+
windowMs = kLoginWindowMs,
|
|
166
|
+
maxAttempts = kLoginMaxAttempts,
|
|
167
|
+
baseLockMs = kLoginBaseLockMs,
|
|
168
|
+
maxLockMs = kLoginMaxLockMs,
|
|
169
|
+
globalWindowMs = kLoginGlobalWindowMs,
|
|
170
|
+
globalMaxAttempts = kLoginGlobalMaxAttempts,
|
|
171
|
+
globalBaseLockMs = kLoginGlobalBaseLockMs,
|
|
172
|
+
globalMaxLockMs = kLoginGlobalMaxLockMs,
|
|
173
|
+
stateTtlMs = kLoginStateTtlMs,
|
|
174
|
+
} = {}) => {
|
|
175
|
+
const runExclusive =
|
|
176
|
+
typeof store.runExclusive === "function"
|
|
177
|
+
? (callback) => store.runExclusive(callback)
|
|
178
|
+
: (callback) => callback();
|
|
179
|
+
|
|
180
|
+
const getOrCreateLoginAttemptState = (clientKey, now) =>
|
|
181
|
+
runExclusive(() => {
|
|
182
|
+
const clientStateKey = getClientStateKey(clientKey, scope);
|
|
183
|
+
const globalStateKey = getGlobalStateKey(scope);
|
|
184
|
+
return {
|
|
185
|
+
clientKey,
|
|
186
|
+
clientStateKey,
|
|
187
|
+
globalStateKey,
|
|
188
|
+
client: getOrCreateState(store, clientStateKey, now),
|
|
189
|
+
global: getOrCreateState(store, globalStateKey, now),
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const evaluateLoginThrottle = (stateBundle, now) =>
|
|
194
|
+
runExclusive(() => {
|
|
195
|
+
const clientStateKey =
|
|
196
|
+
stateBundle?.clientStateKey ||
|
|
197
|
+
getClientStateKey(stateBundle?.clientKey, scope);
|
|
198
|
+
const globalStateKey = stateBundle?.globalStateKey || getGlobalStateKey(scope);
|
|
199
|
+
const clientResult = evaluateState({
|
|
200
|
+
store,
|
|
201
|
+
stateKey: clientStateKey,
|
|
202
|
+
now,
|
|
203
|
+
windowMs,
|
|
204
|
+
});
|
|
205
|
+
const globalResult = evaluateState({
|
|
206
|
+
store,
|
|
207
|
+
stateKey: globalStateKey,
|
|
208
|
+
now,
|
|
209
|
+
windowMs: globalWindowMs,
|
|
210
|
+
});
|
|
211
|
+
if (stateBundle) {
|
|
212
|
+
stateBundle.client = clientResult.state;
|
|
213
|
+
stateBundle.global = globalResult.state;
|
|
214
|
+
}
|
|
215
|
+
return chooseThrottleResult(clientResult, globalResult);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const recordLoginFailure = (stateBundle, now) =>
|
|
219
|
+
runExclusive(() => {
|
|
220
|
+
const clientStateKey =
|
|
221
|
+
stateBundle?.clientStateKey ||
|
|
222
|
+
getClientStateKey(stateBundle?.clientKey, scope);
|
|
223
|
+
const globalStateKey = stateBundle?.globalStateKey || getGlobalStateKey(scope);
|
|
224
|
+
const clientResult = recordStateFailure({
|
|
225
|
+
store,
|
|
226
|
+
stateKey: clientStateKey,
|
|
227
|
+
now,
|
|
228
|
+
windowMs,
|
|
229
|
+
maxAttempts,
|
|
230
|
+
baseLockMs,
|
|
231
|
+
maxLockMs,
|
|
232
|
+
});
|
|
233
|
+
const globalResult = recordStateFailure({
|
|
234
|
+
store,
|
|
235
|
+
stateKey: globalStateKey,
|
|
236
|
+
now,
|
|
237
|
+
windowMs: globalWindowMs,
|
|
238
|
+
maxAttempts: globalMaxAttempts,
|
|
239
|
+
baseLockMs: globalBaseLockMs,
|
|
240
|
+
maxLockMs: globalMaxLockMs,
|
|
241
|
+
});
|
|
242
|
+
if (stateBundle) {
|
|
243
|
+
stateBundle.client = clientResult.state;
|
|
244
|
+
stateBundle.global = globalResult.state;
|
|
245
|
+
}
|
|
246
|
+
return chooseFailureResult(clientResult, globalResult);
|
|
247
|
+
});
|
|
57
248
|
|
|
58
249
|
const recordLoginSuccess = (clientKey) => {
|
|
59
250
|
if (!clientKey) return;
|
|
60
|
-
|
|
251
|
+
runExclusive(() => {
|
|
252
|
+
store.delete(getClientStateKey(clientKey, scope));
|
|
253
|
+
store.delete(getGlobalStateKey(scope));
|
|
254
|
+
});
|
|
61
255
|
};
|
|
62
256
|
|
|
63
257
|
const cleanupLoginAttemptStates = () => {
|
|
64
258
|
const now = Date.now();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
259
|
+
runExclusive(() => {
|
|
260
|
+
for (const [stateKey, rawState] of store.entries()) {
|
|
261
|
+
if (!rawState) {
|
|
262
|
+
store.delete(stateKey);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const state = normalizeState(rawState, now);
|
|
266
|
+
if (state.lockUntil > now) continue;
|
|
267
|
+
if (now - state.lastSeenAt > stateTtlMs) {
|
|
268
|
+
store.delete(stateKey);
|
|
269
|
+
}
|
|
69
270
|
}
|
|
70
|
-
|
|
71
|
-
if (now - state.lastSeenAt > kLoginStateTtlMs) {
|
|
72
|
-
kLoginAttemptStates.delete(key);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
271
|
+
});
|
|
75
272
|
};
|
|
76
273
|
|
|
77
274
|
return {
|
|
@@ -83,4 +280,8 @@ const createLoginThrottle = () => {
|
|
|
83
280
|
};
|
|
84
281
|
};
|
|
85
282
|
|
|
86
|
-
module.exports = {
|
|
283
|
+
module.exports = {
|
|
284
|
+
createLoginThrottle,
|
|
285
|
+
createMemoryLoginThrottleStore,
|
|
286
|
+
kGlobalStateKey,
|
|
287
|
+
};
|
|
@@ -554,6 +554,11 @@
|
|
|
554
554
|
"provider": "anthropic",
|
|
555
555
|
"label": "Claude Opus 4.6"
|
|
556
556
|
},
|
|
557
|
+
{
|
|
558
|
+
"key": "anthropic/claude-opus-4-8",
|
|
559
|
+
"provider": "anthropic",
|
|
560
|
+
"label": "Claude Opus 4.8"
|
|
561
|
+
},
|
|
557
562
|
{
|
|
558
563
|
"key": "anthropic/claude-opus-4-7",
|
|
559
564
|
"provider": "anthropic",
|
|
@@ -327,7 +327,7 @@ const createOnboardingService = ({
|
|
|
327
327
|
authProfiles,
|
|
328
328
|
ensureGatewayProxyConfig,
|
|
329
329
|
getBaseUrl,
|
|
330
|
-
|
|
330
|
+
runOnboardedBootSequence,
|
|
331
331
|
}) => {
|
|
332
332
|
const { OPENCLAW_DIR, WORKSPACE_DIR, kOnboardingMarkerPath } = constants;
|
|
333
333
|
|
|
@@ -567,7 +567,7 @@ const createOnboardingService = ({
|
|
|
567
567
|
console.error("[onboard] Git push error:", e.message);
|
|
568
568
|
}
|
|
569
569
|
|
|
570
|
-
|
|
570
|
+
runOnboardedBootSequence();
|
|
571
571
|
return { status: 200, body: { ok: true } };
|
|
572
572
|
};
|
|
573
573
|
|
|
@@ -4,6 +4,7 @@ const {
|
|
|
4
4
|
ensurePluginAllowed,
|
|
5
5
|
ensureUsageTrackerPluginEntry,
|
|
6
6
|
} = require("../usage-tracker-config");
|
|
7
|
+
const { isOpenAiCompatApiEnabled } = require("../alphaclaw-config");
|
|
7
8
|
|
|
8
9
|
const kDefaultToolsProfile = "full";
|
|
9
10
|
const kBootstrapExtraFiles = [
|
|
@@ -133,16 +134,29 @@ const buildOnboardArgs = ({
|
|
|
133
134
|
return onboardArgs;
|
|
134
135
|
};
|
|
135
136
|
|
|
136
|
-
const ensureManagedConfigShell = (cfg) => {
|
|
137
|
+
const ensureManagedConfigShell = (cfg, { openAiCompatApiEnabled = false } = {}) => {
|
|
137
138
|
if (!cfg.channels) cfg.channels = {};
|
|
138
139
|
ensurePluginsShell(cfg);
|
|
139
140
|
if (!cfg.commands) cfg.commands = {};
|
|
140
141
|
if (!cfg.tools) cfg.tools = {};
|
|
142
|
+
if (!cfg.gateway) cfg.gateway = {};
|
|
141
143
|
if (!cfg.hooks) cfg.hooks = {};
|
|
142
144
|
if (!cfg.hooks.internal) cfg.hooks.internal = {};
|
|
143
145
|
if (!cfg.hooks.internal.entries) cfg.hooks.internal.entries = {};
|
|
144
146
|
cfg.commands.restart = true;
|
|
145
147
|
cfg.tools.profile = kDefaultToolsProfile;
|
|
148
|
+
if (openAiCompatApiEnabled) {
|
|
149
|
+
if (!cfg.gateway.http) cfg.gateway.http = {};
|
|
150
|
+
if (!cfg.gateway.http.endpoints) cfg.gateway.http.endpoints = {};
|
|
151
|
+
cfg.gateway.http.endpoints.chatCompletions = {
|
|
152
|
+
...(cfg.gateway.http.endpoints.chatCompletions || {}),
|
|
153
|
+
enabled: true,
|
|
154
|
+
};
|
|
155
|
+
cfg.gateway.http.endpoints.responses = {
|
|
156
|
+
...(cfg.gateway.http.endpoints.responses || {}),
|
|
157
|
+
enabled: true,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
146
160
|
cfg.hooks.internal.enabled = true;
|
|
147
161
|
cfg.hooks.internal.entries["bootstrap-extra-files"] = {
|
|
148
162
|
...(cfg.hooks.internal.entries["bootstrap-extra-files"] || {}),
|
|
@@ -224,7 +238,12 @@ const writeSanitizedOpenclawConfig = ({
|
|
|
224
238
|
}) => {
|
|
225
239
|
const configPath = `${openclawDir}/openclaw.json`;
|
|
226
240
|
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
227
|
-
ensureManagedConfigShell(cfg
|
|
241
|
+
ensureManagedConfigShell(cfg, {
|
|
242
|
+
openAiCompatApiEnabled: isOpenAiCompatApiEnabled({
|
|
243
|
+
fsModule: fs,
|
|
244
|
+
openclawDir,
|
|
245
|
+
}),
|
|
246
|
+
});
|
|
228
247
|
applyFreshOnboardingChannels({
|
|
229
248
|
cfg,
|
|
230
249
|
varMap,
|
|
@@ -256,7 +275,12 @@ const writeManagedImportOpenclawConfig = ({
|
|
|
256
275
|
}) => {
|
|
257
276
|
const configPath = `${openclawDir}/openclaw.json`;
|
|
258
277
|
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
259
|
-
ensureManagedConfigShell(cfg
|
|
278
|
+
ensureManagedConfigShell(cfg, {
|
|
279
|
+
openAiCompatApiEnabled: isOpenAiCompatApiEnabled({
|
|
280
|
+
fsModule: fs,
|
|
281
|
+
openclawDir,
|
|
282
|
+
}),
|
|
283
|
+
});
|
|
260
284
|
|
|
261
285
|
ensureUsageTrackerPluginEntry(cfg);
|
|
262
286
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { pathToFileURL } = require("url");
|
|
4
|
+
|
|
5
|
+
const kThinkingModuleSentinel = "listThinkingLevelOptions";
|
|
6
|
+
|
|
7
|
+
let thinkingModulePromise = null;
|
|
8
|
+
|
|
9
|
+
const resolveOpenclawDistDir = () => path.dirname(require.resolve("openclaw"));
|
|
10
|
+
|
|
11
|
+
const resolveThinkingModulePath = (distDir = resolveOpenclawDistDir()) => {
|
|
12
|
+
for (const name of fs.readdirSync(distDir)) {
|
|
13
|
+
if (!/^thinking-.*\.js$/.test(name)) continue;
|
|
14
|
+
if (name.includes("api") || name.includes("policy")) continue;
|
|
15
|
+
const fullPath = path.join(distDir, name);
|
|
16
|
+
const source = fs.readFileSync(fullPath, "utf8");
|
|
17
|
+
if (source.includes(kThinkingModuleSentinel)) return fullPath;
|
|
18
|
+
}
|
|
19
|
+
throw new Error("OpenClaw thinking module not found");
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const loadThinkingModule = async () => {
|
|
23
|
+
if (!thinkingModulePromise) {
|
|
24
|
+
const modulePath = resolveThinkingModulePath();
|
|
25
|
+
thinkingModulePromise = import(pathToFileURL(modulePath).href);
|
|
26
|
+
}
|
|
27
|
+
return thinkingModulePromise;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const splitModelKey = (modelKey = "") => {
|
|
31
|
+
const normalized = String(modelKey || "").trim();
|
|
32
|
+
const slashIndex = normalized.indexOf("/");
|
|
33
|
+
if (slashIndex <= 0) return { provider: "", model: normalized };
|
|
34
|
+
return {
|
|
35
|
+
provider: normalized.slice(0, slashIndex),
|
|
36
|
+
model: normalized.slice(slashIndex + 1),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const buildCatalogEntry = ({ provider, model, reasoning, compat } = {}) => {
|
|
41
|
+
const normalizedProvider = String(provider || "").trim();
|
|
42
|
+
const normalizedModel = String(model || "").trim();
|
|
43
|
+
if (!normalizedProvider || !normalizedModel) return null;
|
|
44
|
+
const entry = {
|
|
45
|
+
provider: normalizedProvider,
|
|
46
|
+
id: normalizedModel,
|
|
47
|
+
};
|
|
48
|
+
if (typeof reasoning === "boolean") entry.reasoning = reasoning;
|
|
49
|
+
if (compat && typeof compat === "object") entry.compat = compat;
|
|
50
|
+
return entry;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const resolveThinkingApi = async () => {
|
|
54
|
+
const mod = await loadThinkingModule();
|
|
55
|
+
return {
|
|
56
|
+
listThinkingLevelOptions: mod.listThinkingLevelOptions || mod.i,
|
|
57
|
+
resolveThinkingDefaultForModel: mod.resolveThinkingDefaultForModel || mod.s,
|
|
58
|
+
normalizeThinkLevel: mod.normalizeThinkLevel || mod.p,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const resolveThinkingOptionsForModel = async ({
|
|
63
|
+
modelKey = "",
|
|
64
|
+
catalog = [],
|
|
65
|
+
} = {}) => {
|
|
66
|
+
const { provider, model } = splitModelKey(modelKey);
|
|
67
|
+
if (!provider || !model) {
|
|
68
|
+
return {
|
|
69
|
+
levels: [],
|
|
70
|
+
modelDefault: "off",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const api = await resolveThinkingApi();
|
|
74
|
+
const levels = api.listThinkingLevelOptions(provider, model, catalog) || [];
|
|
75
|
+
const modelDefault =
|
|
76
|
+
api.resolveThinkingDefaultForModel({
|
|
77
|
+
provider,
|
|
78
|
+
model,
|
|
79
|
+
catalog,
|
|
80
|
+
}) || "off";
|
|
81
|
+
return {
|
|
82
|
+
levels: levels.map((entry) => ({
|
|
83
|
+
id: String(entry?.id || "").trim(),
|
|
84
|
+
label: String(entry?.label || entry?.id || "").trim(),
|
|
85
|
+
})),
|
|
86
|
+
modelDefault: String(modelDefault || "off").trim() || "off",
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const normalizeThinkingDefaultValue = async (raw) => {
|
|
91
|
+
if (raw === null || raw === undefined || raw === "") return null;
|
|
92
|
+
const api = await resolveThinkingApi();
|
|
93
|
+
const normalized = api.normalizeThinkLevel(String(raw || "").trim());
|
|
94
|
+
return normalized || null;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
buildCatalogEntry,
|
|
99
|
+
loadThinkingModule,
|
|
100
|
+
normalizeThinkingDefaultValue,
|
|
101
|
+
resolveThinkingOptionsForModel,
|
|
102
|
+
splitModelKey,
|
|
103
|
+
};
|
|
@@ -178,7 +178,11 @@ const registerAgentRoutes = ({
|
|
|
178
178
|
|
|
179
179
|
app.get("/api/agents", (_req, res) => {
|
|
180
180
|
try {
|
|
181
|
-
res.json({
|
|
181
|
+
res.json({
|
|
182
|
+
ok: true,
|
|
183
|
+
agents: agentsService.listAgents(),
|
|
184
|
+
defaults: agentsService.getAgentDefaults?.() || {},
|
|
185
|
+
});
|
|
182
186
|
} catch (error) {
|
|
183
187
|
res.status(500).json({ ok: false, error: error.message });
|
|
184
188
|
}
|
|
@@ -237,9 +241,12 @@ const registerAgentRoutes = ({
|
|
|
237
241
|
}
|
|
238
242
|
});
|
|
239
243
|
|
|
240
|
-
app.put("/api/agents/:id", (req, res) => {
|
|
244
|
+
app.put("/api/agents/:id", async (req, res) => {
|
|
241
245
|
try {
|
|
242
|
-
const agent = agentsService.updateAgent(
|
|
246
|
+
const agent = await agentsService.updateAgent(
|
|
247
|
+
req.params.id,
|
|
248
|
+
req.body || {},
|
|
249
|
+
);
|
|
243
250
|
return res.json({ ok: true, agent });
|
|
244
251
|
} catch (error) {
|
|
245
252
|
const status = String(error.message || "").includes("not found")
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
const { kFallbackOnboardingModels } = require("../constants");
|
|
1
|
+
const { kFallbackOnboardingModels, OPENCLAW_DIR } = require("../constants");
|
|
2
2
|
const { createModelCatalogCache } = require("../model-catalog-cache");
|
|
3
|
+
const { resolveThinkingOptionsForModel } = require("../openclaw-thinking");
|
|
4
|
+
const { readOpenclawConfig } = require("../openclaw-config");
|
|
3
5
|
const { getCommandOutputCandidates } = require("../utils/command-output");
|
|
4
6
|
|
|
5
7
|
const runModelsGitSync = async (shellCmd) => {
|
|
@@ -182,6 +184,38 @@ const registerModelRoutes = ({
|
|
|
182
184
|
return res.json(response);
|
|
183
185
|
});
|
|
184
186
|
|
|
187
|
+
app.get("/api/models/thinking-options", async (req, res) => {
|
|
188
|
+
const modelKey = String(req.query?.modelKey || "").trim();
|
|
189
|
+
if (!modelKey.includes("/")) {
|
|
190
|
+
return res
|
|
191
|
+
.status(400)
|
|
192
|
+
.json({ ok: false, error: "modelKey is required (provider/model)" });
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const cfg = readOpenclawConfig({ openclawDir: OPENCLAW_DIR });
|
|
196
|
+
const globalThinkingDefault =
|
|
197
|
+
typeof cfg.agents?.defaults?.thinkingDefault === "string"
|
|
198
|
+
? cfg.agents.defaults.thinkingDefault.trim()
|
|
199
|
+
: null;
|
|
200
|
+
const { levels, modelDefault } = await resolveThinkingOptionsForModel({
|
|
201
|
+
modelKey,
|
|
202
|
+
});
|
|
203
|
+
const inheritedDefault = globalThinkingDefault || modelDefault || "off";
|
|
204
|
+
return res.json({
|
|
205
|
+
ok: true,
|
|
206
|
+
modelKey,
|
|
207
|
+
levels,
|
|
208
|
+
modelDefault,
|
|
209
|
+
inheritedDefault,
|
|
210
|
+
});
|
|
211
|
+
} catch (err) {
|
|
212
|
+
return res.status(500).json({
|
|
213
|
+
ok: false,
|
|
214
|
+
error: err.message || "Failed to resolve thinking options",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
185
219
|
app.get("/api/models/status", async (req, res) => {
|
|
186
220
|
try {
|
|
187
221
|
const output = await shellCmd("openclaw models status --json", {
|
|
@@ -93,7 +93,7 @@ const registerOnboardingRoutes = ({
|
|
|
93
93
|
authProfiles,
|
|
94
94
|
ensureGatewayProxyConfig,
|
|
95
95
|
getBaseUrl,
|
|
96
|
-
|
|
96
|
+
runOnboardedBootSequence,
|
|
97
97
|
}) => {
|
|
98
98
|
// Keep mutating onboarding routes marker-gated so in-progress imports
|
|
99
99
|
// can promote files before the final completion marker is written.
|
|
@@ -114,7 +114,7 @@ const registerOnboardingRoutes = ({
|
|
|
114
114
|
authProfiles,
|
|
115
115
|
ensureGatewayProxyConfig,
|
|
116
116
|
getBaseUrl,
|
|
117
|
-
|
|
117
|
+
runOnboardedBootSequence,
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
const kEnvVarNamePattern = /^[A-Z_][A-Z0-9_]*$/;
|