@cloudcli-ai/cloudcli 1.32.0 → 1.33.1
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.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/dist/api-docs.html +6 -7
- package/dist/assets/index-BsyL_xmI.js +1348 -0
- package/dist/assets/index-FSP9Hz7V.css +32 -0
- package/dist/index.html +2 -2
- package/dist/modelConstants.js +848 -0
- package/dist-server/server/claude-sdk.js +57 -34
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cursor-cli.js +6 -3
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/gemini-cli.js +3 -1
- package/dist-server/server/gemini-cli.js.map +1 -1
- package/dist-server/server/gemini-response-handler.js +34 -0
- package/dist-server/server/gemini-response-handler.js.map +1 -1
- package/dist-server/server/index.js +234 -67
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/database/index.js +1 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-management.service.js +1 -0
- package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +4 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +28 -6
- 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 +146 -0
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +84 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +7 -39
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +754 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +2 -15
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +2 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +27 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +3 -9
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +2 -0
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js +92 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js +181 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +267 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +115 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +410 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js +62 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode.provider.js +19 -0
- package/dist-server/server/modules/providers/list/opencode/opencode.provider.js.map +1 -0
- package/dist-server/server/modules/providers/provider.registry.js +2 -0
- package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +42 -1
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/mcp.service.js +1 -9
- package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js +226 -0
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +7 -0
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -1
- package/dist-server/server/modules/providers/tests/mcp.test.js +73 -6
- package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +66 -0
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +262 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js +298 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/skills.test.js +33 -0
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +12 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js +4 -4
- package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +18 -3
- 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/openai-codex.js +32 -4
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/opencode-cli.js +287 -0
- package/dist-server/server/opencode-cli.js.map +1 -0
- package/dist-server/server/opencode-cli.test.js +84 -0
- package/dist-server/server/opencode-cli.test.js.map +1 -0
- package/dist-server/server/routes/agent.js +21 -8
- package/dist-server/server/routes/agent.js.map +1 -1
- package/dist-server/server/routes/commands.js +202 -209
- package/dist-server/server/routes/commands.js.map +1 -1
- package/dist-server/server/routes/cursor.js +2 -2
- package/dist-server/server/routes/cursor.js.map +1 -1
- package/dist-server/server/routes/settings.js +0 -10
- package/dist-server/server/routes/settings.js.map +1 -1
- package/dist-server/server/routes/tests/commands.test.js +76 -0
- package/dist-server/server/routes/tests/commands.test.js.map +1 -0
- package/dist-server/server/shared/utils.js +286 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/package.json +5 -2
- package/public/api-docs.html +878 -0
- package/public/modelConstants.js +848 -0
- package/server/claude-sdk.js +64 -35
- package/server/cursor-cli.js +6 -3
- package/server/gemini-cli.js +7 -1
- package/server/gemini-response-handler.js +38 -0
- package/server/index.js +259 -75
- package/server/modules/database/index.ts +1 -0
- package/server/modules/projects/services/project-management.service.ts +2 -0
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +7 -1
- package/server/modules/providers/README.md +11 -3
- package/server/modules/providers/list/claude/claude-auth.provider.ts +34 -6
- package/server/modules/providers/list/claude/claude-models.provider.ts +196 -0
- package/server/modules/providers/list/claude/claude.provider.ts +3 -0
- package/server/modules/providers/list/codex/codex-models.provider.ts +125 -0
- package/server/modules/providers/list/codex/codex-skills.provider.ts +10 -50
- package/server/modules/providers/list/codex/codex.provider.ts +3 -0
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +820 -0
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +7 -20
- package/server/modules/providers/list/cursor/cursor.provider.ts +3 -0
- package/server/modules/providers/list/gemini/gemini-models.provider.ts +42 -0
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +3 -10
- package/server/modules/providers/list/gemini/gemini.provider.ts +3 -0
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +111 -0
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -0
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +339 -0
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +157 -0
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +506 -0
- package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -0
- package/server/modules/providers/list/opencode/opencode.provider.ts +27 -0
- package/server/modules/providers/provider.registry.ts +2 -0
- package/server/modules/providers/provider.routes.ts +62 -2
- package/server/modules/providers/services/mcp.service.ts +1 -12
- package/server/modules/providers/services/provider-models.service.ts +358 -0
- package/server/modules/providers/services/session-synchronizer.service.ts +1 -0
- package/server/modules/providers/services/sessions-watcher.service.ts +8 -0
- package/server/modules/providers/shared/base/abstract.provider.ts +2 -0
- package/server/modules/providers/tests/mcp.test.ts +93 -6
- package/server/modules/providers/tests/opencode-models.test.ts +73 -0
- package/server/modules/providers/tests/opencode-sessions.test.ts +334 -0
- package/server/modules/providers/tests/provider-models.service.test.ts +349 -0
- package/server/modules/providers/tests/skills.test.ts +66 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +15 -1
- package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +4 -4
- package/server/modules/websocket/services/shell-websocket.service.ts +22 -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/openai-codex.js +41 -4
- package/server/opencode-cli.js +336 -0
- package/server/opencode-cli.test.js +95 -0
- package/server/routes/agent.js +22 -8
- package/server/routes/commands.js +254 -233
- package/server/routes/cursor.js +2 -2
- package/server/routes/settings.js +1 -10
- package/server/routes/tests/commands.test.js +82 -0
- package/server/shared/interfaces.ts +45 -0
- package/server/shared/types.ts +88 -1
- package/server/shared/utils.ts +384 -0
- package/dist/assets/index-AaSE7-3t.js +0 -1336
- package/dist/assets/index-CKaku72N.css +0 -32
- package/dist-server/shared/modelConstants.js +0 -99
- package/dist-server/shared/modelConstants.js.map +0 -1
- package/shared/modelConstants.js +0 -107
|
@@ -9,7 +9,8 @@ import { spawn } from 'child_process';
|
|
|
9
9
|
import express from 'express';
|
|
10
10
|
import cors from 'cors';
|
|
11
11
|
import mime from 'mime-types';
|
|
12
|
-
import
|
|
12
|
+
import Database from 'better-sqlite3';
|
|
13
|
+
import { AppError, WORKSPACES_ROOT, getOpenCodeDatabasePath, validateWorkspacePath } from './shared/utils.js';
|
|
13
14
|
import { closeSessionsWatcher, initializeSessionsWatcher } from './modules/providers/index.js';
|
|
14
15
|
import { createWebSocketServer } from './modules/websocket/index.js';
|
|
15
16
|
import { getConnectableHost } from '../shared/networkHosts.js';
|
|
@@ -18,6 +19,7 @@ import { queryClaudeSDK, abortClaudeSDKSession, isClaudeSDKSessionActive, getAct
|
|
|
18
19
|
import { spawnCursor, abortCursorSession, isCursorSessionActive, getActiveCursorSessions, } from './cursor-cli.js';
|
|
19
20
|
import { queryCodex, abortCodexSession, isCodexSessionActive, getActiveCodexSessions, } from './openai-codex.js';
|
|
20
21
|
import { spawnGemini, abortGeminiSession, isGeminiSessionActive, getActiveGeminiSessions, } from './gemini-cli.js';
|
|
22
|
+
import { spawnOpenCode, abortOpenCodeSession, isOpenCodeSessionActive, getActiveOpenCodeSessions, } from './opencode-cli.js';
|
|
21
23
|
import sessionManager from './sessionManager.js';
|
|
22
24
|
import { stripAnsiSequences, normalizeDetectedUrl, extractUrlsFromText, shouldAutoOpenUrlFromOutput, } from './utils/url-detection.js';
|
|
23
25
|
import gitRoutes from './routes/git.js';
|
|
@@ -34,7 +36,7 @@ import geminiRoutes from './routes/gemini.js';
|
|
|
34
36
|
import pluginsRoutes from './routes/plugins.js';
|
|
35
37
|
import providerRoutes from './modules/providers/provider.routes.js';
|
|
36
38
|
import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
|
|
37
|
-
import { initializeDatabase, projectsDb } from './modules/database/index.js';
|
|
39
|
+
import { initializeDatabase, projectsDb, sessionsDb } from './modules/database/index.js';
|
|
38
40
|
import { configureWebPush } from './services/vapid-keys.js';
|
|
39
41
|
import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
|
|
40
42
|
import { IS_PLATFORM } from './constants/config.js';
|
|
@@ -58,21 +60,25 @@ const wss = createWebSocketServer(server, {
|
|
|
58
60
|
spawnCursor,
|
|
59
61
|
queryCodex,
|
|
60
62
|
spawnGemini,
|
|
63
|
+
spawnOpenCode,
|
|
61
64
|
abortClaudeSDKSession,
|
|
62
65
|
abortCursorSession,
|
|
63
66
|
abortCodexSession,
|
|
64
67
|
abortGeminiSession,
|
|
68
|
+
abortOpenCodeSession,
|
|
65
69
|
resolveToolApproval,
|
|
66
70
|
isClaudeSDKSessionActive,
|
|
67
71
|
isCursorSessionActive,
|
|
68
72
|
isCodexSessionActive,
|
|
69
73
|
isGeminiSessionActive,
|
|
74
|
+
isOpenCodeSessionActive,
|
|
70
75
|
reconnectSessionWriter,
|
|
71
76
|
getPendingApprovalsForSession,
|
|
72
77
|
getActiveClaudeSDKSessions,
|
|
73
78
|
getActiveCursorSessions,
|
|
74
79
|
getActiveCodexSessions,
|
|
75
80
|
getActiveGeminiSessions,
|
|
81
|
+
getActiveOpenCodeSessions,
|
|
76
82
|
},
|
|
77
83
|
shell: {
|
|
78
84
|
getSessionById: (sessionId) => sessionManager.getSession(sessionId),
|
|
@@ -1004,21 +1010,119 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1004
1010
|
return res.json({
|
|
1005
1011
|
used: 0,
|
|
1006
1012
|
total: 0,
|
|
1007
|
-
|
|
1013
|
+
inputTokens: 0,
|
|
1014
|
+
outputTokens: 0,
|
|
1015
|
+
breakdown: { input: 0, output: 0 },
|
|
1008
1016
|
unsupported: true,
|
|
1009
1017
|
message: 'Token usage tracking not available for Cursor sessions'
|
|
1010
1018
|
});
|
|
1011
1019
|
}
|
|
1012
|
-
// Handle Gemini sessions - they are raw logs in our current setup
|
|
1013
1020
|
if (provider === 'gemini') {
|
|
1021
|
+
const session = sessionsDb.getSessionById(safeSessionId);
|
|
1022
|
+
const sessionFilePath = session?.jsonl_path;
|
|
1023
|
+
if (!sessionFilePath) {
|
|
1024
|
+
return res.json({
|
|
1025
|
+
used: 0,
|
|
1026
|
+
inputTokens: 0,
|
|
1027
|
+
outputTokens: 0,
|
|
1028
|
+
breakdown: { input: 0, output: 0 },
|
|
1029
|
+
unsupported: true,
|
|
1030
|
+
message: 'Token usage tracking not available for this Gemini session'
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
let fileContent;
|
|
1034
|
+
try {
|
|
1035
|
+
fileContent = await fsPromises.readFile(sessionFilePath, 'utf8');
|
|
1036
|
+
}
|
|
1037
|
+
catch (error) {
|
|
1038
|
+
if (error.code === 'ENOENT') {
|
|
1039
|
+
return res.status(404).json({ error: 'Session file not found', path: sessionFilePath });
|
|
1040
|
+
}
|
|
1041
|
+
throw error;
|
|
1042
|
+
}
|
|
1043
|
+
const lines = fileContent.trim().split('\n');
|
|
1044
|
+
let inputTokens = 0;
|
|
1045
|
+
let outputTokens = 0;
|
|
1046
|
+
let totalTokens = 0;
|
|
1047
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1048
|
+
try {
|
|
1049
|
+
const entry = JSON.parse(lines[i]);
|
|
1050
|
+
if (!entry.tokens || typeof entry.tokens !== 'object') {
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
inputTokens = Number(entry.tokens.input || 0);
|
|
1054
|
+
outputTokens = Number(entry.tokens.output || 0);
|
|
1055
|
+
totalTokens = Number(entry.tokens.total || inputTokens + outputTokens || 0);
|
|
1056
|
+
break;
|
|
1057
|
+
}
|
|
1058
|
+
catch {
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1014
1062
|
return res.json({
|
|
1015
|
-
used:
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1063
|
+
used: totalTokens,
|
|
1064
|
+
inputTokens,
|
|
1065
|
+
outputTokens,
|
|
1066
|
+
breakdown: {
|
|
1067
|
+
input: inputTokens,
|
|
1068
|
+
output: outputTokens
|
|
1069
|
+
}
|
|
1020
1070
|
});
|
|
1021
1071
|
}
|
|
1072
|
+
if (provider === 'opencode') {
|
|
1073
|
+
const dbPath = getOpenCodeDatabasePath();
|
|
1074
|
+
if (!fs.existsSync(dbPath)) {
|
|
1075
|
+
return res.status(404).json({ error: 'OpenCode database not found' });
|
|
1076
|
+
}
|
|
1077
|
+
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
1078
|
+
try {
|
|
1079
|
+
const columns = db.prepare('PRAGMA table_info(session)').all();
|
|
1080
|
+
const columnNames = new Set(columns.map((column) => column.name));
|
|
1081
|
+
const requiredColumns = ['tokens_input', 'tokens_output', 'tokens_reasoning', 'tokens_cache_read', 'tokens_cache_write'];
|
|
1082
|
+
if (!requiredColumns.every((column) => columnNames.has(column))) {
|
|
1083
|
+
return res.json({
|
|
1084
|
+
used: 0,
|
|
1085
|
+
inputTokens: 0,
|
|
1086
|
+
outputTokens: 0,
|
|
1087
|
+
breakdown: { input: 0, output: 0 },
|
|
1088
|
+
unsupported: true,
|
|
1089
|
+
message: 'Token usage tracking is not available in this OpenCode database schema'
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
const row = db.prepare(`
|
|
1093
|
+
SELECT
|
|
1094
|
+
tokens_input AS inputTokens,
|
|
1095
|
+
tokens_output AS outputTokens,
|
|
1096
|
+
tokens_reasoning AS reasoningTokens,
|
|
1097
|
+
tokens_cache_read AS cacheReadTokens,
|
|
1098
|
+
tokens_cache_write AS cacheWriteTokens
|
|
1099
|
+
FROM session
|
|
1100
|
+
WHERE id = ?
|
|
1101
|
+
`).get(safeSessionId);
|
|
1102
|
+
if (!row) {
|
|
1103
|
+
return res.status(404).json({ error: 'OpenCode session not found', sessionId: safeSessionId });
|
|
1104
|
+
}
|
|
1105
|
+
const inputTokens = Number(row.inputTokens || 0) + Number(row.cacheReadTokens || 0);
|
|
1106
|
+
const outputTokens = Number(row.outputTokens || 0);
|
|
1107
|
+
const totalUsed = Number(row.inputTokens || 0)
|
|
1108
|
+
+ outputTokens
|
|
1109
|
+
+ Number(row.reasoningTokens || 0)
|
|
1110
|
+
+ Number(row.cacheReadTokens || 0)
|
|
1111
|
+
+ Number(row.cacheWriteTokens || 0);
|
|
1112
|
+
return res.json({
|
|
1113
|
+
used: totalUsed,
|
|
1114
|
+
inputTokens,
|
|
1115
|
+
outputTokens,
|
|
1116
|
+
breakdown: {
|
|
1117
|
+
input: inputTokens,
|
|
1118
|
+
output: outputTokens
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
finally {
|
|
1123
|
+
db.close();
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1022
1126
|
// Handle Codex sessions
|
|
1023
1127
|
if (provider === 'codex') {
|
|
1024
1128
|
const codexSessionsDir = path.join(homeDir, '.codex', 'sessions');
|
|
@@ -1059,6 +1163,8 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1059
1163
|
throw error;
|
|
1060
1164
|
}
|
|
1061
1165
|
const lines = fileContent.trim().split('\n');
|
|
1166
|
+
let inputTokens = 0;
|
|
1167
|
+
let outputTokens = 0;
|
|
1062
1168
|
let totalTokens = 0;
|
|
1063
1169
|
let contextWindow = 200000; // Default for Codex/OpenAI
|
|
1064
1170
|
// Find the latest token_count event with info (scan from end)
|
|
@@ -1069,7 +1175,9 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1069
1175
|
if (entry.type === 'event_msg' && entry.payload?.type === 'token_count' && entry.payload?.info) {
|
|
1070
1176
|
const tokenInfo = entry.payload.info;
|
|
1071
1177
|
if (tokenInfo.total_token_usage) {
|
|
1072
|
-
|
|
1178
|
+
inputTokens = tokenInfo.total_token_usage.input_tokens || 0;
|
|
1179
|
+
outputTokens = tokenInfo.total_token_usage.output_tokens || 0;
|
|
1180
|
+
totalTokens = tokenInfo.total_token_usage.total_tokens || inputTokens + outputTokens;
|
|
1073
1181
|
}
|
|
1074
1182
|
if (tokenInfo.model_context_window) {
|
|
1075
1183
|
contextWindow = tokenInfo.model_context_window;
|
|
@@ -1084,7 +1192,13 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1084
1192
|
}
|
|
1085
1193
|
return res.json({
|
|
1086
1194
|
used: totalTokens,
|
|
1087
|
-
total: contextWindow
|
|
1195
|
+
total: contextWindow,
|
|
1196
|
+
inputTokens,
|
|
1197
|
+
outputTokens,
|
|
1198
|
+
breakdown: {
|
|
1199
|
+
input: inputTokens,
|
|
1200
|
+
output: outputTokens
|
|
1201
|
+
}
|
|
1088
1202
|
});
|
|
1089
1203
|
}
|
|
1090
1204
|
// Handle Claude sessions (default)
|
|
@@ -1122,8 +1236,7 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1122
1236
|
const parsedContextWindow = parseInt(process.env.CONTEXT_WINDOW, 10);
|
|
1123
1237
|
const contextWindow = Number.isFinite(parsedContextWindow) ? parsedContextWindow : 160000;
|
|
1124
1238
|
let inputTokens = 0;
|
|
1125
|
-
let
|
|
1126
|
-
let cacheReadTokens = 0;
|
|
1239
|
+
let outputTokens = 0;
|
|
1127
1240
|
// Find the latest assistant message with usage data (scan from end)
|
|
1128
1241
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1129
1242
|
try {
|
|
@@ -1133,8 +1246,7 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1133
1246
|
const usage = entry.message.usage;
|
|
1134
1247
|
// Use token counts from latest assistant message only
|
|
1135
1248
|
inputTokens = usage.input_tokens || 0;
|
|
1136
|
-
|
|
1137
|
-
cacheReadTokens = usage.cache_read_input_tokens || 0;
|
|
1249
|
+
outputTokens = usage.output_tokens || 0;
|
|
1138
1250
|
break; // Stop after finding the latest assistant message
|
|
1139
1251
|
}
|
|
1140
1252
|
}
|
|
@@ -1143,15 +1255,15 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1143
1255
|
continue;
|
|
1144
1256
|
}
|
|
1145
1257
|
}
|
|
1146
|
-
|
|
1147
|
-
const totalUsed = inputTokens + cacheCreationTokens + cacheReadTokens;
|
|
1258
|
+
const totalUsed = inputTokens + outputTokens;
|
|
1148
1259
|
res.json({
|
|
1149
1260
|
used: totalUsed,
|
|
1150
1261
|
total: contextWindow,
|
|
1262
|
+
inputTokens,
|
|
1263
|
+
outputTokens,
|
|
1151
1264
|
breakdown: {
|
|
1152
1265
|
input: inputTokens,
|
|
1153
|
-
|
|
1154
|
-
cacheRead: cacheReadTokens
|
|
1266
|
+
output: outputTokens
|
|
1155
1267
|
}
|
|
1156
1268
|
});
|
|
1157
1269
|
}
|
|
@@ -1211,68 +1323,123 @@ function permToRwx(perm) {
|
|
|
1211
1323
|
const x = perm & 1 ? 'x' : '-';
|
|
1212
1324
|
return r + w + x;
|
|
1213
1325
|
}
|
|
1326
|
+
// Directories that are almost never interesting for a project tree but can
|
|
1327
|
+
// contain tens of thousands of files. Skipping them before recursion keeps
|
|
1328
|
+
// traversal time bounded on large monorepos and high-latency filesystems
|
|
1329
|
+
// (NFS / SMB).
|
|
1330
|
+
const IGNORED_DIRS = new Set([
|
|
1331
|
+
// JS / TS toolchains
|
|
1332
|
+
'node_modules', 'dist', 'build', '.next', '.nuxt', '.cache', '.parcel-cache',
|
|
1333
|
+
// VCS
|
|
1334
|
+
'.git', '.svn', '.hg',
|
|
1335
|
+
// Python
|
|
1336
|
+
'__pycache__', '.pytest_cache', '.mypy_cache', '.tox', 'venv', '.venv',
|
|
1337
|
+
// Rust / Go / Java / Ruby
|
|
1338
|
+
'target', 'vendor',
|
|
1339
|
+
// Build output / IDE
|
|
1340
|
+
'.gradle', '.idea', 'coverage', '.nyc_output'
|
|
1341
|
+
]);
|
|
1342
|
+
const DEFAULT_FS_CONCURRENCY = 64;
|
|
1343
|
+
const parsedFsConcurrency = Number.parseInt(process.env.FS_CONCURRENCY || '', 10);
|
|
1344
|
+
const FS_CONCURRENCY = Number.isFinite(parsedFsConcurrency) && parsedFsConcurrency > 0
|
|
1345
|
+
? parsedFsConcurrency
|
|
1346
|
+
: DEFAULT_FS_CONCURRENCY;
|
|
1347
|
+
let activeFsOperations = 0;
|
|
1348
|
+
const pendingFsOperations = [];
|
|
1349
|
+
async function acquire() {
|
|
1350
|
+
if (activeFsOperations < FS_CONCURRENCY) {
|
|
1351
|
+
activeFsOperations += 1;
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
await new Promise((resolve) => {
|
|
1355
|
+
pendingFsOperations.push(resolve);
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
function release() {
|
|
1359
|
+
const next = pendingFsOperations.shift();
|
|
1360
|
+
if (next) {
|
|
1361
|
+
next();
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
activeFsOperations = Math.max(0, activeFsOperations - 1);
|
|
1365
|
+
}
|
|
1214
1366
|
async function getFileTree(dirPath, maxDepth = 3, currentDepth = 0, showHidden = true) {
|
|
1215
1367
|
// Using fsPromises from import
|
|
1216
|
-
|
|
1368
|
+
let entries;
|
|
1217
1369
|
try {
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1370
|
+
await acquire();
|
|
1371
|
+
try {
|
|
1372
|
+
entries = await fsPromises.readdir(dirPath, { withFileTypes: true });
|
|
1373
|
+
}
|
|
1374
|
+
finally {
|
|
1375
|
+
release();
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
catch (error) {
|
|
1379
|
+
// Only log non-permission errors to avoid spam
|
|
1380
|
+
if (error.code !== 'EACCES' && error.code !== 'EPERM') {
|
|
1381
|
+
console.error('Error reading directory:', error);
|
|
1382
|
+
}
|
|
1383
|
+
return [];
|
|
1384
|
+
}
|
|
1385
|
+
const filteredEntries = entries.filter((entry) => !(entry.isDirectory() && IGNORED_DIRS.has(entry.name)));
|
|
1386
|
+
// Process every entry in parallel. On high-latency filesystems (NFS/SMB)
|
|
1387
|
+
// serial stat() was the real bottleneck — issuing them concurrently lets
|
|
1388
|
+
// the kernel pipeline the round-trips and the recursive calls overlap too.
|
|
1389
|
+
const items = await Promise.all(filteredEntries.map(async (entry) => {
|
|
1390
|
+
const itemPath = path.join(dirPath, entry.name);
|
|
1391
|
+
const item = {
|
|
1392
|
+
name: entry.name,
|
|
1393
|
+
path: itemPath,
|
|
1394
|
+
type: entry.isDirectory() ? 'directory' : 'file'
|
|
1395
|
+
};
|
|
1396
|
+
// Get file stats for additional metadata
|
|
1397
|
+
try {
|
|
1398
|
+
await acquire();
|
|
1236
1399
|
try {
|
|
1237
|
-
const stats = await fsPromises.
|
|
1400
|
+
const stats = await fsPromises.lstat(itemPath);
|
|
1238
1401
|
item.size = stats.size;
|
|
1239
1402
|
item.modified = stats.mtime.toISOString();
|
|
1403
|
+
// Mark symlinks so UI can distinguish them
|
|
1404
|
+
if (stats.isSymbolicLink()) {
|
|
1405
|
+
item.isSymlink = true;
|
|
1406
|
+
}
|
|
1240
1407
|
// Convert permissions to rwx format
|
|
1241
1408
|
const mode = stats.mode;
|
|
1242
1409
|
const ownerPerm = (mode >> 6) & 7;
|
|
1243
1410
|
const groupPerm = (mode >> 3) & 7;
|
|
1244
1411
|
const otherPerm = mode & 7;
|
|
1245
|
-
item.permissions =
|
|
1246
|
-
|
|
1412
|
+
item.permissions =
|
|
1413
|
+
((mode >> 6) & 7).toString() +
|
|
1414
|
+
((mode >> 3) & 7).toString() +
|
|
1415
|
+
(mode & 7).toString();
|
|
1416
|
+
item.permissionsRwx =
|
|
1417
|
+
permToRwx(ownerPerm) +
|
|
1418
|
+
permToRwx(groupPerm) +
|
|
1419
|
+
permToRwx(otherPerm);
|
|
1247
1420
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
item.size = 0;
|
|
1251
|
-
item.modified = null;
|
|
1252
|
-
item.permissions = '000';
|
|
1253
|
-
item.permissionsRwx = '---------';
|
|
1421
|
+
finally {
|
|
1422
|
+
release();
|
|
1254
1423
|
}
|
|
1255
|
-
if (entry.isDirectory() && currentDepth < maxDepth) {
|
|
1256
|
-
// Recursively get subdirectories but limit depth
|
|
1257
|
-
try {
|
|
1258
|
-
// Check if we can access the directory before trying to read it
|
|
1259
|
-
await fsPromises.access(item.path, fs.constants.R_OK);
|
|
1260
|
-
item.children = await getFileTree(item.path, maxDepth, currentDepth + 1, showHidden);
|
|
1261
|
-
}
|
|
1262
|
-
catch (e) {
|
|
1263
|
-
// Silently skip directories we can't access (permission denied, etc.)
|
|
1264
|
-
item.children = [];
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
items.push(item);
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
catch (error) {
|
|
1271
|
-
// Only log non-permission errors to avoid spam
|
|
1272
|
-
if (error.code !== 'EACCES' && error.code !== 'EPERM') {
|
|
1273
|
-
console.error('Error reading directory:', error);
|
|
1274
1424
|
}
|
|
1275
|
-
|
|
1425
|
+
catch (statError) {
|
|
1426
|
+
// If stat fails, provide default values
|
|
1427
|
+
item.size = 0;
|
|
1428
|
+
item.modified = null;
|
|
1429
|
+
item.permissions = '000';
|
|
1430
|
+
item.permissionsRwx = '---------';
|
|
1431
|
+
}
|
|
1432
|
+
if (entry.isDirectory() && currentDepth < maxDepth) {
|
|
1433
|
+
// Recurse. Let readdir's own EACCES bubble up through the catch in
|
|
1434
|
+
// the recursive call rather than doing a separate access() probe
|
|
1435
|
+
// (which doubled the round-trip count on SMB without adding info).
|
|
1436
|
+
// The recursive call starts with a bounded readdir; holding a permit
|
|
1437
|
+
// for the whole subtree can deadlock when sibling directories are
|
|
1438
|
+
// waiting on their own children.
|
|
1439
|
+
item.children = await getFileTree(itemPath, maxDepth, currentDepth + 1, showHidden);
|
|
1440
|
+
}
|
|
1441
|
+
return item;
|
|
1442
|
+
}));
|
|
1276
1443
|
return items.sort((a, b) => {
|
|
1277
1444
|
if (a.type !== b.type) {
|
|
1278
1445
|
return a.type === 'directory' ? -1 : 1;
|