@cloudcli-ai/cloudcli 1.33.0 → 1.33.2
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.de.md +1 -1
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.ru.md +1 -1
- package/README.tr.md +1 -1
- package/README.zh-CN.md +1 -1
- package/README.zh-TW.md +242 -0
- package/dist/assets/{index-skXSjBje.js → index-B0mnqBF-.js} +256 -256
- package/dist/assets/index-FSP9Hz7V.css +32 -0
- package/dist/index.html +2 -2
- package/dist/modelConstants.js +8 -1
- package/dist-server/server/claude-sdk.js +8 -1
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/index.js +118 -50
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/projects/projects.routes.js +10 -2
- package/dist-server/server/modules/projects/projects.routes.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +3 -0
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +9 -4
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js +27 -0
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js +28 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +9 -2
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/websocket-auth.service.js +6 -2
- package/dist-server/server/modules/websocket/services/websocket-auth.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/websocket-server.service.js +18 -0
- package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
- package/dist-server/server/routes/agent.js +3 -2
- package/dist-server/server/routes/agent.js.map +1 -1
- package/dist-server/server/routes/commands.js +19 -6
- package/dist-server/server/routes/commands.js.map +1 -1
- package/package.json +3 -2
- package/public/modelConstants.js +8 -1
- package/server/claude-sdk.js +8 -1
- package/server/index.js +131 -58
- package/server/modules/projects/projects.routes.ts +11 -2
- package/server/modules/providers/list/claude/claude-auth.provider.ts +4 -0
- package/server/modules/providers/list/claude/claude-models.provider.ts +9 -4
- package/server/modules/providers/services/provider-models.service.ts +33 -0
- package/server/modules/providers/tests/provider-models.service.test.ts +31 -0
- package/server/modules/websocket/services/shell-websocket.service.ts +13 -2
- package/server/modules/websocket/services/websocket-auth.service.ts +7 -2
- package/server/modules/websocket/services/websocket-server.service.ts +18 -0
- package/server/routes/agent.js +4 -2
- package/server/routes/commands.js +29 -9
- package/dist/assets/index-MOIvpy53.css +0 -32
|
@@ -17,6 +17,7 @@ import { readProviderSessionActiveModelChange } from '@/shared/utils.js';
|
|
|
17
17
|
|
|
18
18
|
export const PROVIDER_MODELS_CACHE_TTL_MS = 3 * 24 * 60 * 60 * 1000;
|
|
19
19
|
const PROVIDER_MODELS_CACHE_VERSION = 1;
|
|
20
|
+
const UNCACHED_PROVIDERS = new Set<LLMProvider>(['claude']);
|
|
20
21
|
|
|
21
22
|
type ProviderModelsServiceDependencies = {
|
|
22
23
|
resolveProvider?: (provider: LLMProvider) => Pick<IProvider, 'models'>;
|
|
@@ -232,10 +233,42 @@ export const createProviderModelsService = (dependencies: ProviderModelsServiceD
|
|
|
232
233
|
return request;
|
|
233
234
|
};
|
|
234
235
|
|
|
236
|
+
const loadDirectModels = (
|
|
237
|
+
provider: LLMProvider,
|
|
238
|
+
): Promise<ProviderModelsResult> => {
|
|
239
|
+
const request = resolveProvider(provider).models.getSupportedModels()
|
|
240
|
+
.then((models) => {
|
|
241
|
+
const currentTime = now();
|
|
242
|
+
return {
|
|
243
|
+
models,
|
|
244
|
+
cache: {
|
|
245
|
+
updatedAt: new Date(currentTime).toISOString(),
|
|
246
|
+
expiresAt: new Date(currentTime).toISOString(),
|
|
247
|
+
source: 'fresh' as const,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
})
|
|
251
|
+
.finally(() => {
|
|
252
|
+
pendingRequests.delete(provider);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
pendingRequests.set(provider, request);
|
|
256
|
+
return request;
|
|
257
|
+
};
|
|
258
|
+
|
|
235
259
|
const getProviderModels = async (
|
|
236
260
|
provider: LLMProvider,
|
|
237
261
|
options: ProviderModelsOptions = {},
|
|
238
262
|
): Promise<ProviderModelsResult> => {
|
|
263
|
+
if (UNCACHED_PROVIDERS.has(provider)) {
|
|
264
|
+
const pendingRequest = pendingRequests.get(provider);
|
|
265
|
+
if (pendingRequest) {
|
|
266
|
+
return pendingRequest;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return loadDirectModels(provider);
|
|
270
|
+
}
|
|
271
|
+
|
|
239
272
|
if (options.bypassCache) {
|
|
240
273
|
const pendingRequest = pendingRequests.get(provider);
|
|
241
274
|
if (pendingRequest) {
|
|
@@ -130,6 +130,37 @@ test('provider models are cached for the three-day ttl', async () => {
|
|
|
130
130
|
}
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
test('claude provider models are always loaded directly from the provider', async () => {
|
|
134
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'provider-model-cache-claude-direct-'));
|
|
135
|
+
let loadCount = 0;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const service = createProviderModelsService({
|
|
139
|
+
cachePath: path.join(tempRoot, 'models-cache.json'),
|
|
140
|
+
resolveProvider: (provider) => ({
|
|
141
|
+
models: {
|
|
142
|
+
getSupportedModels: async () => {
|
|
143
|
+
loadCount += 1;
|
|
144
|
+
return createModels(`${provider}-${loadCount}`);
|
|
145
|
+
},
|
|
146
|
+
getCurrentActiveModel: async () => createCurrentActiveModel(`${provider}-active`),
|
|
147
|
+
changeActiveModel: async (input) => createSessionActiveModelChange(provider, input),
|
|
148
|
+
},
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const first = await service.getProviderModels('claude');
|
|
153
|
+
const second = await service.getProviderModels('claude');
|
|
154
|
+
|
|
155
|
+
assert.equal(loadCount, 2);
|
|
156
|
+
assert.equal(first.models.DEFAULT, 'claude-1');
|
|
157
|
+
assert.equal(second.models.DEFAULT, 'claude-2');
|
|
158
|
+
assert.equal(second.cache.source, 'fresh');
|
|
159
|
+
} finally {
|
|
160
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
133
164
|
test('provider model cache is persisted across service instances', async () => {
|
|
134
165
|
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'provider-model-cache-file-'));
|
|
135
166
|
const cachePath = path.join(tempRoot, 'models-cache.json');
|
|
@@ -18,6 +18,7 @@ type ShellIncomingMessage = {
|
|
|
18
18
|
provider?: string;
|
|
19
19
|
initialCommand?: string;
|
|
20
20
|
isPlainShell?: boolean;
|
|
21
|
+
forceRestart?: boolean;
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
type PtySessionEntry = {
|
|
@@ -180,6 +181,7 @@ export function handleShellConnection(
|
|
|
180
181
|
const hasSession = readBoolean(data.hasSession);
|
|
181
182
|
const provider = readString(data.provider, 'claude');
|
|
182
183
|
const initialCommand = readString(data.initialCommand);
|
|
184
|
+
const forceRestart = readBoolean(data.forceRestart);
|
|
183
185
|
const isPlainShell =
|
|
184
186
|
readBoolean(data.isPlainShell) ||
|
|
185
187
|
(!!initialCommand && !hasSession) ||
|
|
@@ -200,7 +202,7 @@ export function handleShellConnection(
|
|
|
200
202
|
: '';
|
|
201
203
|
ptySessionKey = `${projectPath}_${sessionId ?? 'default'}${commandSuffix}`;
|
|
202
204
|
|
|
203
|
-
if (isLoginCommand) {
|
|
205
|
+
if (isLoginCommand || forceRestart) {
|
|
204
206
|
const oldSession = ptySessionsMap.get(ptySessionKey);
|
|
205
207
|
if (oldSession) {
|
|
206
208
|
if (oldSession.timeoutId) {
|
|
@@ -211,7 +213,8 @@ export function handleShellConnection(
|
|
|
211
213
|
}
|
|
212
214
|
}
|
|
213
215
|
|
|
214
|
-
const existingSession =
|
|
216
|
+
const existingSession =
|
|
217
|
+
isLoginCommand || forceRestart ? null : ptySessionsMap.get(ptySessionKey);
|
|
215
218
|
if (existingSession) {
|
|
216
219
|
shellProcess = existingSession.pty;
|
|
217
220
|
if (existingSession.timeoutId) {
|
|
@@ -368,6 +371,10 @@ export function handleShellConnection(
|
|
|
368
371
|
}
|
|
369
372
|
|
|
370
373
|
const session = ptySessionsMap.get(ptySessionKey);
|
|
374
|
+
if (session && session.pty !== shellProcess) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
371
378
|
if (session && session.ws && session.ws.readyState === WebSocket.OPEN) {
|
|
372
379
|
session.ws.send(
|
|
373
380
|
JSON.stringify({
|
|
@@ -451,6 +458,10 @@ export function handleShellConnection(
|
|
|
451
458
|
|
|
452
459
|
session.ws = null;
|
|
453
460
|
session.timeoutId = setTimeout(() => {
|
|
461
|
+
if (ptySessionsMap.get(ptySessionKey as string) !== session) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
454
465
|
session.pty.kill();
|
|
455
466
|
ptySessionsMap.delete(ptySessionKey as string);
|
|
456
467
|
}, PTY_SESSION_TIMEOUT);
|
|
@@ -20,7 +20,13 @@ export function verifyWebSocketClient(
|
|
|
20
20
|
dependencies: WebSocketAuthDependencies
|
|
21
21
|
): boolean {
|
|
22
22
|
const request = info.req as AuthenticatedWebSocketRequest;
|
|
23
|
-
|
|
23
|
+
const upgradeUrl = new URL(request.url ?? '/', 'http://localhost');
|
|
24
|
+
const loggedUrl = new URL(upgradeUrl);
|
|
25
|
+
if (loggedUrl.searchParams.has('token')) {
|
|
26
|
+
loggedUrl.searchParams.set('token', 'REDACTED');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log('WebSocket connection attempt to:', `${loggedUrl.pathname}${loggedUrl.search}`);
|
|
24
30
|
|
|
25
31
|
// Platform mode: use the first DB user and skip token checks.
|
|
26
32
|
if (dependencies.isPlatform) {
|
|
@@ -36,7 +42,6 @@ export function verifyWebSocketClient(
|
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
// OSS mode: read JWT from query string first, then Authorization header.
|
|
39
|
-
const upgradeUrl = new URL(request.url ?? '/', 'http://localhost');
|
|
40
45
|
const token =
|
|
41
46
|
upgradeUrl.searchParams.get('token') ??
|
|
42
47
|
request.headers.authorization?.split(' ')[1] ??
|
|
@@ -31,6 +31,24 @@ export function createWebSocketServer(
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
wss.on('connection', (ws, request) => {
|
|
34
|
+
// Keep WebSocket alive across reverse-proxy idle timeouts (Cloudflare ~100s,
|
|
35
|
+
// AWS ALB 60s, nginx 60s, etc.). Without app-level pings these connections
|
|
36
|
+
// are silently torn down even when the UI is active, causing repeated
|
|
37
|
+
// reconnect cycles. ws library heartbeat is opt-in.
|
|
38
|
+
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
39
|
+
const heartbeat = setInterval(() => {
|
|
40
|
+
if (ws.readyState === ws.OPEN) {
|
|
41
|
+
try {
|
|
42
|
+
ws.ping();
|
|
43
|
+
} catch {
|
|
44
|
+
// socket may have been closed concurrently — interval will be cleared below
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
48
|
+
const stopHeartbeat = () => clearInterval(heartbeat);
|
|
49
|
+
ws.on('close', stopHeartbeat);
|
|
50
|
+
ws.on('error', stopHeartbeat);
|
|
51
|
+
|
|
34
52
|
const incomingRequest = request as AuthenticatedWebSocketRequest;
|
|
35
53
|
const url = incomingRequest.url ?? '/';
|
|
36
54
|
const pathname = new URL(url, 'http://localhost').pathname;
|
package/server/routes/agent.js
CHANGED
|
@@ -592,12 +592,14 @@ class ResponseCollector {
|
|
|
592
592
|
}
|
|
593
593
|
}
|
|
594
594
|
|
|
595
|
+
const inputTokens = totalInput + totalCacheRead + totalCacheCreation;
|
|
596
|
+
|
|
595
597
|
return {
|
|
596
|
-
inputTokens
|
|
598
|
+
inputTokens,
|
|
597
599
|
outputTokens: totalOutput,
|
|
598
600
|
cacheReadTokens: totalCacheRead,
|
|
599
601
|
cacheCreationTokens: totalCacheCreation,
|
|
600
|
-
totalTokens:
|
|
602
|
+
totalTokens: inputTokens + totalOutput
|
|
601
603
|
};
|
|
602
604
|
}
|
|
603
605
|
}
|
|
@@ -268,16 +268,35 @@ Custom commands can be created in:
|
|
|
268
268
|
tokenUsage.contextWindow ??
|
|
269
269
|
0,
|
|
270
270
|
) || 0;
|
|
271
|
-
const
|
|
271
|
+
const normalizedInputValue =
|
|
272
|
+
tokenUsage.inputTokens ??
|
|
273
|
+
tokenUsage.input ??
|
|
274
|
+
tokenUsage.cumulativeInputTokens ??
|
|
275
|
+
tokenUsage.breakdown?.input ??
|
|
276
|
+
tokenUsage.promptTokens;
|
|
277
|
+
const directInputTokens =
|
|
272
278
|
Number(
|
|
273
|
-
|
|
274
|
-
tokenUsage.input ??
|
|
279
|
+
normalizedInputValue ??
|
|
275
280
|
tokenUsage.input_tokens ??
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
281
|
+
0
|
|
282
|
+
) || 0;
|
|
283
|
+
const cacheReadTokens =
|
|
284
|
+
Number(
|
|
285
|
+
tokenUsage.cacheReadTokens ??
|
|
286
|
+
tokenUsage.cache_read_input_tokens ??
|
|
287
|
+
tokenUsage.cacheReadInputTokens ??
|
|
288
|
+
0,
|
|
289
|
+
) || 0;
|
|
290
|
+
const cacheCreationTokens =
|
|
291
|
+
Number(
|
|
292
|
+
tokenUsage.cacheCreationTokens ??
|
|
293
|
+
tokenUsage.cache_creation_input_tokens ??
|
|
294
|
+
tokenUsage.cacheCreationInputTokens ??
|
|
279
295
|
0,
|
|
280
296
|
) || 0;
|
|
297
|
+
const inputTokens = normalizedInputValue == null
|
|
298
|
+
? directInputTokens + cacheReadTokens + cacheCreationTokens
|
|
299
|
+
: directInputTokens;
|
|
281
300
|
const outputTokens =
|
|
282
301
|
Number(
|
|
283
302
|
tokenUsage.outputTokens ??
|
|
@@ -288,8 +307,9 @@ Custom commands can be created in:
|
|
|
288
307
|
tokenUsage.completionTokens ??
|
|
289
308
|
0,
|
|
290
309
|
) || 0;
|
|
291
|
-
const
|
|
292
|
-
const
|
|
310
|
+
const computedUsed = inputTokens + outputTokens;
|
|
311
|
+
const hasTokenBreakdown = computedUsed > 0;
|
|
312
|
+
const used = Math.max(reportedUsed, computedUsed);
|
|
293
313
|
|
|
294
314
|
return {
|
|
295
315
|
type: "builtin",
|
|
@@ -302,7 +322,7 @@ Custom commands can be created in:
|
|
|
302
322
|
...(hasTokenBreakdown
|
|
303
323
|
? {
|
|
304
324
|
tokenBreakdown: {
|
|
305
|
-
input:
|
|
325
|
+
input: inputTokens,
|
|
306
326
|
output: outputTokens,
|
|
307
327
|
},
|
|
308
328
|
}
|