@cloudcli-ai/cloudcli 1.32.0 → 1.33.0
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-MOIvpy53.css +32 -0
- package/dist/assets/index-skXSjBje.js +1351 -0
- package/dist/index.html +2 -2
- package/dist/modelConstants.js +841 -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 +131 -19
- 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 +25 -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 +141 -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 +199 -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 +270 -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 +9 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.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 +3 -1
- package/public/api-docs.html +878 -0
- package/public/modelConstants.js +841 -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 +144 -19
- 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 +30 -6
- package/server/modules/providers/list/claude/claude-models.provider.ts +191 -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 +325 -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 +318 -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 +9 -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
package/server/claude-sdk.js
CHANGED
|
@@ -17,7 +17,8 @@ import crypto from 'crypto';
|
|
|
17
17
|
import { promises as fs } from 'fs';
|
|
18
18
|
import path from 'path';
|
|
19
19
|
import os from 'os';
|
|
20
|
-
import {
|
|
20
|
+
import { CLAUDE_FALLBACK_MODELS } from './modules/providers/list/claude/claude-models.provider.js';
|
|
21
|
+
import { providerModelsService } from './modules/providers/services/provider-models.service.js';
|
|
21
22
|
import { resolveClaudeCodeExecutablePath } from './shared/claude-cli-path.js';
|
|
22
23
|
import {
|
|
23
24
|
createNotificationEvent,
|
|
@@ -204,7 +205,7 @@ function mapCliOptionsToSDK(options = {}) {
|
|
|
204
205
|
|
|
205
206
|
// Map model (default to sonnet)
|
|
206
207
|
// Valid models: sonnet, opus, haiku, opusplan, sonnet[1m]
|
|
207
|
-
sdkOptions.model = options.model ||
|
|
208
|
+
sdkOptions.model = options.model || CLAUDE_FALLBACK_MODELS.DEFAULT;
|
|
208
209
|
// Model logged at query start below
|
|
209
210
|
|
|
210
211
|
// Map system prompt configuration
|
|
@@ -284,43 +285,68 @@ function transformMessage(sdkMessage) {
|
|
|
284
285
|
return sdkMessage;
|
|
285
286
|
}
|
|
286
287
|
|
|
288
|
+
function readNumber(value) {
|
|
289
|
+
const parsed = Number(value);
|
|
290
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
287
293
|
/**
|
|
288
|
-
* Extracts token usage from SDK
|
|
289
|
-
*
|
|
294
|
+
* Extracts token usage from SDK messages.
|
|
295
|
+
* Prefers per-step `message.usage` (Claude message payload), then falls back
|
|
296
|
+
* to result-level usage/modelUsage for compatibility across SDK versions.
|
|
297
|
+
* @param {Object} sdkMessage - SDK stream message
|
|
290
298
|
* @returns {Object|null} Token budget object or null
|
|
291
299
|
*/
|
|
292
|
-
function extractTokenBudget(
|
|
293
|
-
if (
|
|
300
|
+
function extractTokenBudget(sdkMessage) {
|
|
301
|
+
if (!sdkMessage || typeof sdkMessage !== 'object') {
|
|
294
302
|
return null;
|
|
295
303
|
}
|
|
296
304
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
305
|
+
const messageUsage = sdkMessage.message?.usage || sdkMessage.usage;
|
|
306
|
+
if (messageUsage && typeof messageUsage === 'object') {
|
|
307
|
+
const inputTokens = readNumber(messageUsage.input_tokens ?? messageUsage.inputTokens);
|
|
308
|
+
const outputTokens = readNumber(messageUsage.output_tokens ?? messageUsage.outputTokens);
|
|
309
|
+
const totalUsed = inputTokens + outputTokens;
|
|
310
|
+
const contextWindow = parseInt(process.env.CONTEXT_WINDOW, 10) || 160000;
|
|
300
311
|
|
|
301
|
-
|
|
302
|
-
|
|
312
|
+
return {
|
|
313
|
+
used: totalUsed,
|
|
314
|
+
total: contextWindow,
|
|
315
|
+
inputTokens,
|
|
316
|
+
outputTokens,
|
|
317
|
+
breakdown: {
|
|
318
|
+
input: inputTokens,
|
|
319
|
+
output: outputTokens,
|
|
320
|
+
},
|
|
321
|
+
};
|
|
303
322
|
}
|
|
304
323
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const outputTokens = modelData.cumulativeOutputTokens || modelData.outputTokens || 0;
|
|
309
|
-
const cacheReadTokens = modelData.cumulativeCacheReadInputTokens || modelData.cacheReadInputTokens || 0;
|
|
310
|
-
const cacheCreationTokens = modelData.cumulativeCacheCreationInputTokens || modelData.cacheCreationInputTokens || 0;
|
|
324
|
+
if (!sdkMessage.modelUsage || typeof sdkMessage.modelUsage !== 'object') {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
311
327
|
|
|
312
|
-
//
|
|
313
|
-
const
|
|
328
|
+
// Fallback for older SDK messages with only modelUsage
|
|
329
|
+
const modelKey = Object.keys(sdkMessage.modelUsage)[0];
|
|
330
|
+
const modelData = sdkMessage.modelUsage[modelKey];
|
|
314
331
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
332
|
+
if (!modelData || typeof modelData !== 'object') {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
318
335
|
|
|
319
|
-
|
|
336
|
+
const inputTokens = readNumber(modelData.cumulativeInputTokens ?? modelData.inputTokens);
|
|
337
|
+
const outputTokens = readNumber(modelData.cumulativeOutputTokens ?? modelData.outputTokens);
|
|
338
|
+
const totalUsed = inputTokens + outputTokens;
|
|
339
|
+
const contextWindow = parseInt(process.env.CONTEXT_WINDOW, 10) || 160000;
|
|
320
340
|
|
|
321
341
|
return {
|
|
322
342
|
used: totalUsed,
|
|
323
|
-
total: contextWindow
|
|
343
|
+
total: contextWindow,
|
|
344
|
+
inputTokens,
|
|
345
|
+
outputTokens,
|
|
346
|
+
breakdown: {
|
|
347
|
+
input: inputTokens,
|
|
348
|
+
output: outputTokens,
|
|
349
|
+
},
|
|
324
350
|
};
|
|
325
351
|
}
|
|
326
352
|
|
|
@@ -491,8 +517,17 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
491
517
|
};
|
|
492
518
|
|
|
493
519
|
try {
|
|
520
|
+
const resolvedModel = await providerModelsService.resolveResumeModel(
|
|
521
|
+
'claude',
|
|
522
|
+
sessionId,
|
|
523
|
+
options.model,
|
|
524
|
+
);
|
|
525
|
+
|
|
494
526
|
// Map CLI options to SDK format
|
|
495
|
-
const sdkOptions = mapCliOptionsToSDK(
|
|
527
|
+
const sdkOptions = mapCliOptionsToSDK({
|
|
528
|
+
...options,
|
|
529
|
+
model: resolvedModel || options.model,
|
|
530
|
+
});
|
|
496
531
|
|
|
497
532
|
// Load MCP configuration
|
|
498
533
|
const mcpServers = await loadMcpConfig(options.cwd);
|
|
@@ -674,16 +709,10 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
674
709
|
ws.send(msg);
|
|
675
710
|
}
|
|
676
711
|
|
|
677
|
-
// Extract and send token budget updates from result
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
// Model info available in result message
|
|
682
|
-
}
|
|
683
|
-
const tokenBudgetData = extractTokenBudget(message);
|
|
684
|
-
if (tokenBudgetData) {
|
|
685
|
-
ws.send(createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: tokenBudgetData, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
|
|
686
|
-
}
|
|
712
|
+
// Extract and send token budget updates from assistant/result usage payloads
|
|
713
|
+
const tokenBudgetData = extractTokenBudget(message);
|
|
714
|
+
if (tokenBudgetData) {
|
|
715
|
+
ws.send(createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: tokenBudgetData, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
|
|
687
716
|
}
|
|
688
717
|
}
|
|
689
718
|
|
package/server/cursor-cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import crossSpawn from 'cross-spawn';
|
|
|
3
3
|
import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
|
|
4
4
|
import { sessionsService } from './modules/providers/services/sessions.service.js';
|
|
5
5
|
import { providerAuthService } from './modules/providers/services/provider-auth.service.js';
|
|
6
|
+
import { providerModelsService } from './modules/providers/services/provider-models.service.js';
|
|
6
7
|
import { createNormalizedMessage } from './shared/utils.js';
|
|
7
8
|
|
|
8
9
|
// Use cross-spawn on Windows for better command execution
|
|
@@ -28,6 +29,7 @@ function isWorkspaceTrustPrompt(text = '') {
|
|
|
28
29
|
async function spawnCursor(command, options = {}, ws) {
|
|
29
30
|
return new Promise(async (resolve, reject) => {
|
|
30
31
|
const { sessionId, projectPath, cwd, resume, toolsSettings, skipPermissions, model, sessionSummary } = options;
|
|
32
|
+
const resolvedModel = await providerModelsService.resolveResumeModel('cursor', sessionId, model);
|
|
31
33
|
let capturedSessionId = sessionId; // Track session ID throughout the process
|
|
32
34
|
let sessionCreatedSent = false; // Track if we've already sent session-created event
|
|
33
35
|
let hasRetriedWithTrust = false;
|
|
@@ -52,9 +54,10 @@ async function spawnCursor(command, options = {}, ws) {
|
|
|
52
54
|
// Provide a prompt (works for both new and resumed sessions)
|
|
53
55
|
baseArgs.push('-p', command);
|
|
54
56
|
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
// Model overrides are applied to both new and resumed sessions so a
|
|
58
|
+
// session-scoped change request can take effect on the next turn.
|
|
59
|
+
if (resolvedModel) {
|
|
60
|
+
baseArgs.push('--model', resolvedModel);
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
// Request streaming JSON when we are providing a prompt
|
package/server/gemini-cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import sessionManager from './sessionManager.js';
|
|
|
9
9
|
import GeminiResponseHandler from './gemini-response-handler.js';
|
|
10
10
|
import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
|
|
11
11
|
import { providerAuthService } from './modules/providers/services/provider-auth.service.js';
|
|
12
|
+
import { providerModelsService } from './modules/providers/services/provider-models.service.js';
|
|
12
13
|
import { createNormalizedMessage } from './shared/utils.js';
|
|
13
14
|
|
|
14
15
|
// Use cross-spawn on Windows for correct .cmd resolution (same pattern as cursor-cli.js)
|
|
@@ -120,6 +121,11 @@ async function buildGeminiProcessEnv() {
|
|
|
120
121
|
|
|
121
122
|
async function spawnGemini(command, options = {}, ws) {
|
|
122
123
|
const { sessionId, projectPath, cwd, toolsSettings, permissionMode, images, sessionSummary } = options;
|
|
124
|
+
const resolvedModel = await providerModelsService.resolveResumeModel(
|
|
125
|
+
'gemini',
|
|
126
|
+
sessionId,
|
|
127
|
+
options.model
|
|
128
|
+
);
|
|
123
129
|
let capturedSessionId = sessionId; // Track session ID throughout the process
|
|
124
130
|
let sessionCreatedSent = false; // Track if we've already sent session-created event
|
|
125
131
|
let assistantBlocks = []; // Accumulate the full response blocks including tools
|
|
@@ -244,7 +250,7 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
244
250
|
}
|
|
245
251
|
|
|
246
252
|
// Add model for all sessions (both new and resumed)
|
|
247
|
-
let modelToUse =
|
|
253
|
+
let modelToUse = resolvedModel || 'gemini-2.5-flash';
|
|
248
254
|
args.push('--model', modelToUse);
|
|
249
255
|
args.push('--output-format', 'stream-json');
|
|
250
256
|
|
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
// Gemini Response Handler - JSON Stream processing
|
|
2
2
|
import { sessionsService } from './modules/providers/services/sessions.service.js';
|
|
3
|
+
import { createNormalizedMessage } from './shared/utils.js';
|
|
4
|
+
|
|
5
|
+
function buildGeminiTokenBudget(tokens) {
|
|
6
|
+
if (!tokens || typeof tokens !== 'object') {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const parsedInputTokens = Number(tokens.input);
|
|
11
|
+
const parsedOutputTokens = Number(tokens.output);
|
|
12
|
+
const inputTokens = Number.isFinite(parsedInputTokens) ? parsedInputTokens : 0;
|
|
13
|
+
const outputTokens = Number.isFinite(parsedOutputTokens) ? parsedOutputTokens : 0;
|
|
14
|
+
const parsedUsed = Number(tokens.total);
|
|
15
|
+
const used = Number.isFinite(parsedUsed) ? parsedUsed : inputTokens + outputTokens;
|
|
16
|
+
if (!Number.isFinite(used) || used <= 0) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
used,
|
|
22
|
+
inputTokens,
|
|
23
|
+
outputTokens,
|
|
24
|
+
breakdown: {
|
|
25
|
+
input: inputTokens,
|
|
26
|
+
output: outputTokens,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
3
30
|
|
|
4
31
|
class GeminiResponseHandler {
|
|
5
32
|
constructor(ws, options = {}) {
|
|
@@ -60,6 +87,17 @@ class GeminiResponseHandler {
|
|
|
60
87
|
for (const msg of normalized) {
|
|
61
88
|
this.ws.send(msg);
|
|
62
89
|
}
|
|
90
|
+
|
|
91
|
+
const tokenBudget = buildGeminiTokenBudget(event.tokens);
|
|
92
|
+
if (tokenBudget) {
|
|
93
|
+
this.ws.send(createNormalizedMessage({
|
|
94
|
+
kind: 'status',
|
|
95
|
+
text: 'token_budget',
|
|
96
|
+
tokenBudget,
|
|
97
|
+
sessionId: sid,
|
|
98
|
+
provider: 'gemini',
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
63
101
|
}
|
|
64
102
|
|
|
65
103
|
forceFlush() {
|
package/server/index.js
CHANGED
|
@@ -10,8 +10,9 @@ import { spawn } from 'child_process';
|
|
|
10
10
|
import express from 'express';
|
|
11
11
|
import cors from 'cors';
|
|
12
12
|
import mime from 'mime-types';
|
|
13
|
+
import Database from 'better-sqlite3';
|
|
13
14
|
|
|
14
|
-
import { AppError, WORKSPACES_ROOT, validateWorkspacePath } from '@/shared/utils.js';
|
|
15
|
+
import { AppError, WORKSPACES_ROOT, getOpenCodeDatabasePath, validateWorkspacePath } from '@/shared/utils.js';
|
|
15
16
|
import { closeSessionsWatcher, initializeSessionsWatcher } from '@/modules/providers/index.js';
|
|
16
17
|
import { createWebSocketServer } from '@/modules/websocket/index.js';
|
|
17
18
|
|
|
@@ -45,6 +46,12 @@ import {
|
|
|
45
46
|
isGeminiSessionActive,
|
|
46
47
|
getActiveGeminiSessions,
|
|
47
48
|
} from './gemini-cli.js';
|
|
49
|
+
import {
|
|
50
|
+
spawnOpenCode,
|
|
51
|
+
abortOpenCodeSession,
|
|
52
|
+
isOpenCodeSessionActive,
|
|
53
|
+
getActiveOpenCodeSessions,
|
|
54
|
+
} from './opencode-cli.js';
|
|
48
55
|
import sessionManager from './sessionManager.js';
|
|
49
56
|
import {
|
|
50
57
|
stripAnsiSequences,
|
|
@@ -66,7 +73,7 @@ import geminiRoutes from './routes/gemini.js';
|
|
|
66
73
|
import pluginsRoutes from './routes/plugins.js';
|
|
67
74
|
import providerRoutes from './modules/providers/provider.routes.js';
|
|
68
75
|
import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
|
|
69
|
-
import { initializeDatabase, projectsDb } from './modules/database/index.js';
|
|
76
|
+
import { initializeDatabase, projectsDb, sessionsDb } from './modules/database/index.js';
|
|
70
77
|
import { configureWebPush } from './services/vapid-keys.js';
|
|
71
78
|
import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
|
|
72
79
|
import { IS_PLATFORM } from './constants/config.js';
|
|
@@ -94,21 +101,25 @@ const wss = createWebSocketServer(server, {
|
|
|
94
101
|
spawnCursor,
|
|
95
102
|
queryCodex,
|
|
96
103
|
spawnGemini,
|
|
104
|
+
spawnOpenCode,
|
|
97
105
|
abortClaudeSDKSession,
|
|
98
106
|
abortCursorSession,
|
|
99
107
|
abortCodexSession,
|
|
100
108
|
abortGeminiSession,
|
|
109
|
+
abortOpenCodeSession,
|
|
101
110
|
resolveToolApproval,
|
|
102
111
|
isClaudeSDKSessionActive,
|
|
103
112
|
isCursorSessionActive,
|
|
104
113
|
isCodexSessionActive,
|
|
105
114
|
isGeminiSessionActive,
|
|
115
|
+
isOpenCodeSessionActive,
|
|
106
116
|
reconnectSessionWriter,
|
|
107
117
|
getPendingApprovalsForSession,
|
|
108
118
|
getActiveClaudeSDKSessions,
|
|
109
119
|
getActiveCursorSessions,
|
|
110
120
|
getActiveCodexSessions,
|
|
111
121
|
getActiveGeminiSessions,
|
|
122
|
+
getActiveOpenCodeSessions,
|
|
112
123
|
},
|
|
113
124
|
shell: {
|
|
114
125
|
getSessionById: (sessionId) => sessionManager.getSession(sessionId),
|
|
@@ -1131,23 +1142,129 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1131
1142
|
return res.json({
|
|
1132
1143
|
used: 0,
|
|
1133
1144
|
total: 0,
|
|
1134
|
-
|
|
1145
|
+
inputTokens: 0,
|
|
1146
|
+
outputTokens: 0,
|
|
1147
|
+
breakdown: { input: 0, output: 0 },
|
|
1135
1148
|
unsupported: true,
|
|
1136
1149
|
message: 'Token usage tracking not available for Cursor sessions'
|
|
1137
1150
|
});
|
|
1138
1151
|
}
|
|
1139
1152
|
|
|
1140
|
-
// Handle Gemini sessions - they are raw logs in our current setup
|
|
1141
1153
|
if (provider === 'gemini') {
|
|
1154
|
+
const session = sessionsDb.getSessionById(safeSessionId);
|
|
1155
|
+
const sessionFilePath = session?.jsonl_path;
|
|
1156
|
+
if (!sessionFilePath) {
|
|
1157
|
+
return res.json({
|
|
1158
|
+
used: 0,
|
|
1159
|
+
inputTokens: 0,
|
|
1160
|
+
outputTokens: 0,
|
|
1161
|
+
breakdown: { input: 0, output: 0 },
|
|
1162
|
+
unsupported: true,
|
|
1163
|
+
message: 'Token usage tracking not available for this Gemini session'
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
let fileContent;
|
|
1168
|
+
try {
|
|
1169
|
+
fileContent = await fsPromises.readFile(sessionFilePath, 'utf8');
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
if (error.code === 'ENOENT') {
|
|
1172
|
+
return res.status(404).json({ error: 'Session file not found', path: sessionFilePath });
|
|
1173
|
+
}
|
|
1174
|
+
throw error;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
const lines = fileContent.trim().split('\n');
|
|
1178
|
+
let inputTokens = 0;
|
|
1179
|
+
let outputTokens = 0;
|
|
1180
|
+
let totalTokens = 0;
|
|
1181
|
+
|
|
1182
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1183
|
+
try {
|
|
1184
|
+
const entry = JSON.parse(lines[i]);
|
|
1185
|
+
if (!entry.tokens || typeof entry.tokens !== 'object') {
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
inputTokens = Number(entry.tokens.input || 0);
|
|
1190
|
+
outputTokens = Number(entry.tokens.output || 0);
|
|
1191
|
+
totalTokens = Number(entry.tokens.total || inputTokens + outputTokens || 0);
|
|
1192
|
+
break;
|
|
1193
|
+
} catch {
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1142
1198
|
return res.json({
|
|
1143
|
-
used:
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1199
|
+
used: totalTokens,
|
|
1200
|
+
inputTokens,
|
|
1201
|
+
outputTokens,
|
|
1202
|
+
breakdown: {
|
|
1203
|
+
input: inputTokens,
|
|
1204
|
+
output: outputTokens
|
|
1205
|
+
}
|
|
1148
1206
|
});
|
|
1149
1207
|
}
|
|
1150
1208
|
|
|
1209
|
+
if (provider === 'opencode') {
|
|
1210
|
+
const dbPath = getOpenCodeDatabasePath();
|
|
1211
|
+
if (!fs.existsSync(dbPath)) {
|
|
1212
|
+
return res.status(404).json({ error: 'OpenCode database not found' });
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
1216
|
+
try {
|
|
1217
|
+
const columns = db.prepare('PRAGMA table_info(session)').all();
|
|
1218
|
+
const columnNames = new Set(columns.map((column) => column.name));
|
|
1219
|
+
const requiredColumns = ['tokens_input', 'tokens_output', 'tokens_reasoning', 'tokens_cache_read', 'tokens_cache_write'];
|
|
1220
|
+
if (!requiredColumns.every((column) => columnNames.has(column))) {
|
|
1221
|
+
return res.json({
|
|
1222
|
+
used: 0,
|
|
1223
|
+
inputTokens: 0,
|
|
1224
|
+
outputTokens: 0,
|
|
1225
|
+
breakdown: { input: 0, output: 0 },
|
|
1226
|
+
unsupported: true,
|
|
1227
|
+
message: 'Token usage tracking is not available in this OpenCode database schema'
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
const row = db.prepare(`
|
|
1232
|
+
SELECT
|
|
1233
|
+
tokens_input AS inputTokens,
|
|
1234
|
+
tokens_output AS outputTokens,
|
|
1235
|
+
tokens_reasoning AS reasoningTokens,
|
|
1236
|
+
tokens_cache_read AS cacheReadTokens,
|
|
1237
|
+
tokens_cache_write AS cacheWriteTokens
|
|
1238
|
+
FROM session
|
|
1239
|
+
WHERE id = ?
|
|
1240
|
+
`).get(safeSessionId);
|
|
1241
|
+
|
|
1242
|
+
if (!row) {
|
|
1243
|
+
return res.status(404).json({ error: 'OpenCode session not found', sessionId: safeSessionId });
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const inputTokens = Number(row.inputTokens || 0) + Number(row.cacheReadTokens || 0);
|
|
1247
|
+
const outputTokens = Number(row.outputTokens || 0);
|
|
1248
|
+
const totalUsed = Number(row.inputTokens || 0)
|
|
1249
|
+
+ outputTokens
|
|
1250
|
+
+ Number(row.reasoningTokens || 0)
|
|
1251
|
+
+ Number(row.cacheReadTokens || 0)
|
|
1252
|
+
+ Number(row.cacheWriteTokens || 0);
|
|
1253
|
+
|
|
1254
|
+
return res.json({
|
|
1255
|
+
used: totalUsed,
|
|
1256
|
+
inputTokens,
|
|
1257
|
+
outputTokens,
|
|
1258
|
+
breakdown: {
|
|
1259
|
+
input: inputTokens,
|
|
1260
|
+
output: outputTokens
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1263
|
+
} finally {
|
|
1264
|
+
db.close();
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1151
1268
|
// Handle Codex sessions
|
|
1152
1269
|
if (provider === 'codex') {
|
|
1153
1270
|
const codexSessionsDir = path.join(homeDir, '.codex', 'sessions');
|
|
@@ -1188,6 +1305,8 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1188
1305
|
throw error;
|
|
1189
1306
|
}
|
|
1190
1307
|
const lines = fileContent.trim().split('\n');
|
|
1308
|
+
let inputTokens = 0;
|
|
1309
|
+
let outputTokens = 0;
|
|
1191
1310
|
let totalTokens = 0;
|
|
1192
1311
|
let contextWindow = 200000; // Default for Codex/OpenAI
|
|
1193
1312
|
|
|
@@ -1200,7 +1319,9 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1200
1319
|
if (entry.type === 'event_msg' && entry.payload?.type === 'token_count' && entry.payload?.info) {
|
|
1201
1320
|
const tokenInfo = entry.payload.info;
|
|
1202
1321
|
if (tokenInfo.total_token_usage) {
|
|
1203
|
-
|
|
1322
|
+
inputTokens = tokenInfo.total_token_usage.input_tokens || 0;
|
|
1323
|
+
outputTokens = tokenInfo.total_token_usage.output_tokens || 0;
|
|
1324
|
+
totalTokens = tokenInfo.total_token_usage.total_tokens || inputTokens + outputTokens;
|
|
1204
1325
|
}
|
|
1205
1326
|
if (tokenInfo.model_context_window) {
|
|
1206
1327
|
contextWindow = tokenInfo.model_context_window;
|
|
@@ -1215,7 +1336,13 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1215
1336
|
|
|
1216
1337
|
return res.json({
|
|
1217
1338
|
used: totalTokens,
|
|
1218
|
-
total: contextWindow
|
|
1339
|
+
total: contextWindow,
|
|
1340
|
+
inputTokens,
|
|
1341
|
+
outputTokens,
|
|
1342
|
+
breakdown: {
|
|
1343
|
+
input: inputTokens,
|
|
1344
|
+
output: outputTokens
|
|
1345
|
+
}
|
|
1219
1346
|
});
|
|
1220
1347
|
}
|
|
1221
1348
|
|
|
@@ -1258,8 +1385,7 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1258
1385
|
const parsedContextWindow = parseInt(process.env.CONTEXT_WINDOW, 10);
|
|
1259
1386
|
const contextWindow = Number.isFinite(parsedContextWindow) ? parsedContextWindow : 160000;
|
|
1260
1387
|
let inputTokens = 0;
|
|
1261
|
-
let
|
|
1262
|
-
let cacheReadTokens = 0;
|
|
1388
|
+
let outputTokens = 0;
|
|
1263
1389
|
|
|
1264
1390
|
// Find the latest assistant message with usage data (scan from end)
|
|
1265
1391
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
@@ -1272,8 +1398,7 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1272
1398
|
|
|
1273
1399
|
// Use token counts from latest assistant message only
|
|
1274
1400
|
inputTokens = usage.input_tokens || 0;
|
|
1275
|
-
|
|
1276
|
-
cacheReadTokens = usage.cache_read_input_tokens || 0;
|
|
1401
|
+
outputTokens = usage.output_tokens || 0;
|
|
1277
1402
|
|
|
1278
1403
|
break; // Stop after finding the latest assistant message
|
|
1279
1404
|
}
|
|
@@ -1283,16 +1408,16 @@ app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticate
|
|
|
1283
1408
|
}
|
|
1284
1409
|
}
|
|
1285
1410
|
|
|
1286
|
-
|
|
1287
|
-
const totalUsed = inputTokens + cacheCreationTokens + cacheReadTokens;
|
|
1411
|
+
const totalUsed = inputTokens + outputTokens;
|
|
1288
1412
|
|
|
1289
1413
|
res.json({
|
|
1290
1414
|
used: totalUsed,
|
|
1291
1415
|
total: contextWindow,
|
|
1416
|
+
inputTokens,
|
|
1417
|
+
outputTokens,
|
|
1292
1418
|
breakdown: {
|
|
1293
1419
|
input: inputTokens,
|
|
1294
|
-
|
|
1295
|
-
cacheRead: cacheReadTokens
|
|
1420
|
+
output: outputTokens
|
|
1296
1421
|
}
|
|
1297
1422
|
});
|
|
1298
1423
|
} catch (error) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { initializeDatabase } from '@/modules/database/init-db.js';
|
|
2
|
+
export { closeConnection, getConnection, getDatabasePath } from '@/modules/database/connection.js';
|
|
2
3
|
export { apiKeysDb } from '@/modules/database/repositories/api-keys.js';
|
|
3
4
|
export { appConfigDb } from '@/modules/database/repositories/app-config.js';
|
|
4
5
|
export { credentialsDb } from '@/modules/database/repositories/credentials.js';
|
|
@@ -33,6 +33,7 @@ type ProjectApiView = {
|
|
|
33
33
|
cursorSessions: [];
|
|
34
34
|
codexSessions: [];
|
|
35
35
|
geminiSessions: [];
|
|
36
|
+
opencodeSessions: [];
|
|
36
37
|
sessionMeta: {
|
|
37
38
|
hasMore: false;
|
|
38
39
|
total: 0;
|
|
@@ -84,6 +85,7 @@ function mapProjectRowToApiView(projectRow: ProjectRepositoryRow): ProjectApiVie
|
|
|
84
85
|
cursorSessions: [],
|
|
85
86
|
codexSessions: [],
|
|
86
87
|
geminiSessions: [],
|
|
88
|
+
opencodeSessions: [],
|
|
87
89
|
sessionMeta: {
|
|
88
90
|
hasMore: false,
|
|
89
91
|
total: 0,
|
|
@@ -14,7 +14,7 @@ type SessionSummary = {
|
|
|
14
14
|
lastActivity: string;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
type SessionsByProvider = Record<'claude' | 'cursor' | 'codex' | 'gemini', SessionSummary[]>;
|
|
17
|
+
type SessionsByProvider = Record<'claude' | 'cursor' | 'codex' | 'gemini' | 'opencode', SessionSummary[]>;
|
|
18
18
|
|
|
19
19
|
type SessionRepositoryRow = {
|
|
20
20
|
provider: string;
|
|
@@ -34,6 +34,7 @@ export type ProjectListItem = {
|
|
|
34
34
|
cursorSessions: SessionSummary[];
|
|
35
35
|
codexSessions: SessionSummary[];
|
|
36
36
|
geminiSessions: SessionSummary[];
|
|
37
|
+
opencodeSessions: SessionSummary[];
|
|
37
38
|
sessionMeta: {
|
|
38
39
|
hasMore: boolean;
|
|
39
40
|
total: number;
|
|
@@ -74,6 +75,7 @@ export type ProjectSessionsPageApiView = {
|
|
|
74
75
|
cursorSessions: SessionSummary[];
|
|
75
76
|
codexSessions: SessionSummary[];
|
|
76
77
|
geminiSessions: SessionSummary[];
|
|
78
|
+
opencodeSessions: SessionSummary[];
|
|
77
79
|
sessionMeta: {
|
|
78
80
|
hasMore: boolean;
|
|
79
81
|
total: number;
|
|
@@ -139,6 +141,7 @@ function bucketSessionRowsByProvider(rows: SessionRepositoryRow[]): SessionsByPr
|
|
|
139
141
|
cursor: [],
|
|
140
142
|
codex: [],
|
|
141
143
|
gemini: [],
|
|
144
|
+
opencode: [],
|
|
142
145
|
};
|
|
143
146
|
|
|
144
147
|
for (const row of rows) {
|
|
@@ -253,6 +256,7 @@ export async function getProjectsWithSessions(
|
|
|
253
256
|
cursorSessions: sessionsPage.sessionsByProvider.cursor,
|
|
254
257
|
codexSessions: sessionsPage.sessionsByProvider.codex,
|
|
255
258
|
geminiSessions: sessionsPage.sessionsByProvider.gemini,
|
|
259
|
+
opencodeSessions: sessionsPage.sessionsByProvider.opencode,
|
|
256
260
|
sessionMeta: {
|
|
257
261
|
hasMore: sessionsPage.hasMore,
|
|
258
262
|
total: sessionsPage.total,
|
|
@@ -309,6 +313,7 @@ export async function getArchivedProjectsWithSessions(
|
|
|
309
313
|
cursorSessions: sessionsPage.sessionsByProvider.cursor,
|
|
310
314
|
codexSessions: sessionsPage.sessionsByProvider.codex,
|
|
311
315
|
geminiSessions: sessionsPage.sessionsByProvider.gemini,
|
|
316
|
+
opencodeSessions: sessionsPage.sessionsByProvider.opencode,
|
|
312
317
|
sessionMeta: {
|
|
313
318
|
hasMore: sessionsPage.hasMore,
|
|
314
319
|
total: sessionsPage.total,
|
|
@@ -341,6 +346,7 @@ export async function getProjectSessionsPage(
|
|
|
341
346
|
cursorSessions: sessionsPage.sessionsByProvider.cursor,
|
|
342
347
|
codexSessions: sessionsPage.sessionsByProvider.codex,
|
|
343
348
|
geminiSessions: sessionsPage.sessionsByProvider.gemini,
|
|
349
|
+
opencodeSessions: sessionsPage.sessionsByProvider.opencode,
|
|
344
350
|
sessionMeta: {
|
|
345
351
|
hasMore: sessionsPage.hasMore,
|
|
346
352
|
total: sessionsPage.total,
|