@cloudcli-ai/cloudcli 1.31.5 → 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-DqMVUeZS.js → index-skXSjBje.js} +290 -270
- package/dist/index.html +2 -2
- package/dist/modelConstants.js +841 -0
- package/dist-server/server/claude-sdk.js +67 -39
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cursor-cli.js +6 -9
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/gemini-cli.js +163 -35
- 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/database/migrations.js +11 -0
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/projects.db.js +12 -0
- package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -1
- package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js +61 -0
- package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +49 -8
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +1 -0
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/projects/projects.routes.js +12 -3
- package/dist-server/server/modules/projects/projects.routes.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-delete.service.js +14 -1
- package/dist-server/server/modules/projects/services/project-delete.service.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 +47 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/modules/providers/index.js +1 -0
- package/dist-server/server/modules/providers/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +27 -7
- 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-session-synchronizer.provider.js +55 -2
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +153 -14
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +198 -0
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +4 -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-session-synchronizer.provider.js +54 -2
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +21 -6
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +50 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +4 -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-session-synchronizer.provider.js +8 -30
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +187 -24
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +28 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +4 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +125 -2
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.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-session-synchronizer.provider.js +24 -8
- package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +10 -10
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +33 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +4 -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 +65 -3
- 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-conversations-search.service.js +129 -13
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
- 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 +13 -4
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +81 -7
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/skills.service.js +11 -0
- package/dist-server/server/modules/providers/services/skills.service.js.map +1 -0
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -1
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js +45 -0
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -0
- 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 +259 -0
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -0
- 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 +87 -29
- 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 +204 -211
- 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/claude-cli-path.js +103 -0
- package/dist-server/server/shared/claude-cli-path.js.map +1 -0
- package/dist-server/server/shared/claude-cli-path.test.js +45 -0
- package/dist-server/server/shared/claude-cli-path.test.js.map +1 -0
- package/dist-server/server/{utils → shared}/frontmatter.js +3 -3
- package/dist-server/server/shared/frontmatter.js.map +1 -0
- package/dist-server/server/shared/utils.js +368 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/dist-server/server/utils/commandParser.js +3 -3
- package/dist-server/server/utils/commandParser.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 +74 -40
- package/server/cursor-cli.js +6 -11
- package/server/gemini-cli.js +197 -43
- 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/database/migrations.ts +12 -0
- package/server/modules/database/repositories/projects.db.ts +13 -0
- package/server/modules/database/repositories/sessions.db.integration.test.ts +72 -0
- package/server/modules/database/repositories/sessions.db.ts +60 -9
- package/server/modules/database/schema.ts +1 -0
- package/server/modules/projects/projects.routes.ts +20 -3
- package/server/modules/projects/services/project-delete.service.ts +16 -1
- package/server/modules/projects/services/project-management.service.ts +2 -0
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +71 -1
- package/server/modules/providers/README.md +354 -0
- package/server/modules/providers/index.ts +2 -1
- package/server/modules/providers/list/claude/claude-auth.provider.ts +38 -13
- package/server/modules/providers/list/claude/claude-models.provider.ts +191 -0
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +68 -2
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +175 -14
- package/server/modules/providers/list/claude/claude-skills.provider.ts +257 -0
- package/server/modules/providers/list/claude/claude.provider.ts +11 -1
- package/server/modules/providers/list/codex/codex-models.provider.ts +125 -0
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +62 -2
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +25 -6
- package/server/modules/providers/list/codex/codex-skills.provider.ts +60 -0
- package/server/modules/providers/list/codex/codex.provider.ts +11 -1
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +820 -0
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +17 -40
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +216 -26
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +31 -0
- package/server/modules/providers/list/cursor/cursor.provider.ts +11 -1
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +158 -2
- package/server/modules/providers/list/gemini/gemini-models.provider.ts +42 -0
- package/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.ts +24 -20
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +10 -11
- package/server/modules/providers/list/gemini/gemini-skills.provider.ts +36 -0
- package/server/modules/providers/list/gemini/gemini.provider.ts +11 -1
- 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 +97 -4
- 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-conversations-search.service.ts +162 -13
- package/server/modules/providers/services/session-synchronizer.service.ts +1 -0
- package/server/modules/providers/services/sessions-watcher.service.ts +14 -4
- package/server/modules/providers/services/sessions.service.ts +112 -9
- package/server/modules/providers/services/skills.service.ts +15 -0
- package/server/modules/providers/shared/base/abstract.provider.ts +4 -0
- package/server/modules/providers/shared/skills/skills.provider.ts +64 -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 +512 -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 +99 -30
- 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 +256 -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/claude-cli-path.test.ts +61 -0
- package/server/shared/claude-cli-path.ts +139 -0
- package/server/{utils/frontmatter.js → shared/frontmatter.ts} +3 -3
- package/server/shared/interfaces.ts +64 -0
- package/server/shared/types.ts +166 -1
- package/server/shared/utils.ts +478 -0
- package/server/utils/commandParser.js +5 -3
- package/dist/assets/index-6rYG_mRo.css +0 -32
- package/dist-server/server/utils/frontmatter.js.map +0 -1
- package/dist-server/shared/modelConstants.js +0 -98
- package/dist-server/shared/modelConstants.js.map +0 -1
- package/shared/modelConstants.js +0 -106
package/server/claude-sdk.js
CHANGED
|
@@ -17,7 +17,9 @@ 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';
|
|
22
|
+
import { resolveClaudeCodeExecutablePath } from './shared/claude-cli-path.js';
|
|
21
23
|
import {
|
|
22
24
|
createNotificationEvent,
|
|
23
25
|
notifyRunFailed,
|
|
@@ -153,11 +155,9 @@ function mapCliOptionsToSDK(options = {}) {
|
|
|
153
155
|
// Since SDK 0.2.113, options.env replaces process.env instead of overlaying it.
|
|
154
156
|
sdkOptions.env = { ...process.env };
|
|
155
157
|
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
// even when npm prune --production has removed those optional deps.
|
|
160
|
-
sdkOptions.pathToClaudeCodeExecutable = process.env.CLAUDE_CLI_PATH || 'claude';
|
|
158
|
+
// Resolve the executable eagerly on Windows because the SDK uses raw child_process.spawn,
|
|
159
|
+
// which does not reliably follow npm's shell wrappers like cross-spawn does.
|
|
160
|
+
sdkOptions.pathToClaudeCodeExecutable = resolveClaudeCodeExecutablePath(process.env.CLAUDE_CLI_PATH);
|
|
161
161
|
|
|
162
162
|
// Map working directory
|
|
163
163
|
if (cwd) {
|
|
@@ -205,7 +205,7 @@ function mapCliOptionsToSDK(options = {}) {
|
|
|
205
205
|
|
|
206
206
|
// Map model (default to sonnet)
|
|
207
207
|
// Valid models: sonnet, opus, haiku, opusplan, sonnet[1m]
|
|
208
|
-
sdkOptions.model = options.model ||
|
|
208
|
+
sdkOptions.model = options.model || CLAUDE_FALLBACK_MODELS.DEFAULT;
|
|
209
209
|
// Model logged at query start below
|
|
210
210
|
|
|
211
211
|
// Map system prompt configuration
|
|
@@ -285,43 +285,68 @@ function transformMessage(sdkMessage) {
|
|
|
285
285
|
return sdkMessage;
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
function readNumber(value) {
|
|
289
|
+
const parsed = Number(value);
|
|
290
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
288
293
|
/**
|
|
289
|
-
* Extracts token usage from SDK
|
|
290
|
-
*
|
|
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
|
|
291
298
|
* @returns {Object|null} Token budget object or null
|
|
292
299
|
*/
|
|
293
|
-
function extractTokenBudget(
|
|
294
|
-
if (
|
|
300
|
+
function extractTokenBudget(sdkMessage) {
|
|
301
|
+
if (!sdkMessage || typeof sdkMessage !== 'object') {
|
|
295
302
|
return null;
|
|
296
303
|
}
|
|
297
304
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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;
|
|
301
311
|
|
|
302
|
-
|
|
303
|
-
|
|
312
|
+
return {
|
|
313
|
+
used: totalUsed,
|
|
314
|
+
total: contextWindow,
|
|
315
|
+
inputTokens,
|
|
316
|
+
outputTokens,
|
|
317
|
+
breakdown: {
|
|
318
|
+
input: inputTokens,
|
|
319
|
+
output: outputTokens,
|
|
320
|
+
},
|
|
321
|
+
};
|
|
304
322
|
}
|
|
305
323
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const outputTokens = modelData.cumulativeOutputTokens || modelData.outputTokens || 0;
|
|
310
|
-
const cacheReadTokens = modelData.cumulativeCacheReadInputTokens || modelData.cacheReadInputTokens || 0;
|
|
311
|
-
const cacheCreationTokens = modelData.cumulativeCacheCreationInputTokens || modelData.cacheCreationInputTokens || 0;
|
|
324
|
+
if (!sdkMessage.modelUsage || typeof sdkMessage.modelUsage !== 'object') {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
312
327
|
|
|
313
|
-
//
|
|
314
|
-
const
|
|
328
|
+
// Fallback for older SDK messages with only modelUsage
|
|
329
|
+
const modelKey = Object.keys(sdkMessage.modelUsage)[0];
|
|
330
|
+
const modelData = sdkMessage.modelUsage[modelKey];
|
|
315
331
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
332
|
+
if (!modelData || typeof modelData !== 'object') {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
319
335
|
|
|
320
|
-
|
|
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;
|
|
321
340
|
|
|
322
341
|
return {
|
|
323
342
|
used: totalUsed,
|
|
324
|
-
total: contextWindow
|
|
343
|
+
total: contextWindow,
|
|
344
|
+
inputTokens,
|
|
345
|
+
outputTokens,
|
|
346
|
+
breakdown: {
|
|
347
|
+
input: inputTokens,
|
|
348
|
+
output: outputTokens,
|
|
349
|
+
},
|
|
325
350
|
};
|
|
326
351
|
}
|
|
327
352
|
|
|
@@ -492,8 +517,17 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
492
517
|
};
|
|
493
518
|
|
|
494
519
|
try {
|
|
520
|
+
const resolvedModel = await providerModelsService.resolveResumeModel(
|
|
521
|
+
'claude',
|
|
522
|
+
sessionId,
|
|
523
|
+
options.model,
|
|
524
|
+
);
|
|
525
|
+
|
|
495
526
|
// Map CLI options to SDK format
|
|
496
|
-
const sdkOptions = mapCliOptionsToSDK(
|
|
527
|
+
const sdkOptions = mapCliOptionsToSDK({
|
|
528
|
+
...options,
|
|
529
|
+
model: resolvedModel || options.model,
|
|
530
|
+
});
|
|
497
531
|
|
|
498
532
|
// Load MCP configuration
|
|
499
533
|
const mcpServers = await loadMcpConfig(options.cwd);
|
|
@@ -527,6 +561,12 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
527
561
|
}]
|
|
528
562
|
};
|
|
529
563
|
|
|
564
|
+
// Caveat: in 'auto' and 'bypassPermissions' modes the SDK resolves approval
|
|
565
|
+
// at the permission-mode step and skips this callback, so interactive tools
|
|
566
|
+
// (AskUserQuestion, ExitPlanMode) won't reach the UI — the classifier/bypass
|
|
567
|
+
// auto-approves them and the model acts on a generated answer. Move these
|
|
568
|
+
// tools to a PreToolUse hook (runs before the mode check) if we need them
|
|
569
|
+
// to work in those modes.
|
|
530
570
|
sdkOptions.canUseTool = async (toolName, input, context) => {
|
|
531
571
|
const requiresInteraction = TOOLS_REQUIRING_INTERACTION.has(toolName);
|
|
532
572
|
|
|
@@ -669,16 +709,10 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
669
709
|
ws.send(msg);
|
|
670
710
|
}
|
|
671
711
|
|
|
672
|
-
// Extract and send token budget updates from result
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
// Model info available in result message
|
|
677
|
-
}
|
|
678
|
-
const tokenBudgetData = extractTokenBudget(message);
|
|
679
|
-
if (tokenBudgetData) {
|
|
680
|
-
ws.send(createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: tokenBudgetData, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
|
|
681
|
-
}
|
|
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' }));
|
|
682
716
|
}
|
|
683
717
|
}
|
|
684
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
|
|
@@ -150,7 +153,6 @@ async function spawnCursor(command, options = {}, ws) {
|
|
|
150
153
|
|
|
151
154
|
try {
|
|
152
155
|
const response = JSON.parse(line);
|
|
153
|
-
console.log('Parsed JSON response:', response);
|
|
154
156
|
|
|
155
157
|
// Handle different message types
|
|
156
158
|
switch (response.type) {
|
|
@@ -159,7 +161,6 @@ async function spawnCursor(command, options = {}, ws) {
|
|
|
159
161
|
// Capture session ID
|
|
160
162
|
if (response.session_id && !capturedSessionId) {
|
|
161
163
|
capturedSessionId = response.session_id;
|
|
162
|
-
console.log('Captured session ID:', capturedSessionId);
|
|
163
164
|
|
|
164
165
|
// Update process key with captured session ID
|
|
165
166
|
if (processKey !== capturedSessionId) {
|
|
@@ -197,7 +198,6 @@ async function spawnCursor(command, options = {}, ws) {
|
|
|
197
198
|
|
|
198
199
|
case 'result': {
|
|
199
200
|
// Session complete — send stream end + lifecycle complete with result payload
|
|
200
|
-
console.log('Cursor session result:', response);
|
|
201
201
|
const resultText = typeof response.result === 'string' ? response.result : '';
|
|
202
202
|
ws.send(createNormalizedMessage({
|
|
203
203
|
kind: 'complete',
|
|
@@ -213,8 +213,6 @@ async function spawnCursor(command, options = {}, ws) {
|
|
|
213
213
|
// Unknown message types — ignore.
|
|
214
214
|
}
|
|
215
215
|
} catch (parseError) {
|
|
216
|
-
console.log('Non-JSON response:', line);
|
|
217
|
-
|
|
218
216
|
if (shouldSuppressForTrustRetry(line)) {
|
|
219
217
|
return;
|
|
220
218
|
}
|
|
@@ -228,7 +226,6 @@ async function spawnCursor(command, options = {}, ws) {
|
|
|
228
226
|
// Handle stdout (streaming JSON responses)
|
|
229
227
|
cursorProcess.stdout.on('data', (data) => {
|
|
230
228
|
const rawOutput = data.toString();
|
|
231
|
-
console.log('Cursor CLI stdout:', rawOutput);
|
|
232
229
|
|
|
233
230
|
// Stream chunks can split JSON objects across packets; keep trailing partial line.
|
|
234
231
|
stdoutLineBuffer += rawOutput;
|
|
@@ -254,8 +251,6 @@ async function spawnCursor(command, options = {}, ws) {
|
|
|
254
251
|
|
|
255
252
|
// Handle process completion
|
|
256
253
|
cursorProcess.on('close', async (code) => {
|
|
257
|
-
console.log(`Cursor CLI process exited with code ${code}`);
|
|
258
|
-
|
|
259
254
|
const finalSessionId = capturedSessionId || sessionId || processKey;
|
|
260
255
|
activeCursorProcesses.delete(finalSessionId);
|
|
261
256
|
|
package/server/gemini-cli.js
CHANGED
|
@@ -1,21 +1,131 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
|
-
import crossSpawn from 'cross-spawn';
|
|
3
|
-
|
|
4
|
-
// Use cross-spawn on Windows for correct .cmd resolution (same pattern as cursor-cli.js)
|
|
5
|
-
const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
|
|
6
2
|
import { promises as fs } from 'fs';
|
|
7
|
-
import path from 'path';
|
|
8
3
|
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
import crossSpawn from 'cross-spawn';
|
|
7
|
+
|
|
9
8
|
import sessionManager from './sessionManager.js';
|
|
10
9
|
import GeminiResponseHandler from './gemini-response-handler.js';
|
|
11
10
|
import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
|
|
12
11
|
import { providerAuthService } from './modules/providers/services/provider-auth.service.js';
|
|
12
|
+
import { providerModelsService } from './modules/providers/services/provider-models.service.js';
|
|
13
13
|
import { createNormalizedMessage } from './shared/utils.js';
|
|
14
14
|
|
|
15
|
+
// Use cross-spawn on Windows for correct .cmd resolution (same pattern as cursor-cli.js)
|
|
16
|
+
const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
|
|
17
|
+
|
|
15
18
|
let activeGeminiProcesses = new Map(); // Track active processes by session ID
|
|
16
19
|
|
|
20
|
+
function mapGeminiExitCodeToMessage(exitCode) {
|
|
21
|
+
switch (exitCode) {
|
|
22
|
+
case 42:
|
|
23
|
+
return 'Gemini rejected the request input (exit code 42).';
|
|
24
|
+
case 44:
|
|
25
|
+
return 'Gemini sandbox error (exit code 44). Check local sandbox/container settings.';
|
|
26
|
+
case 52:
|
|
27
|
+
return 'Gemini configuration error (exit code 52). Check your Gemini settings files for invalid JSON/config.';
|
|
28
|
+
case 53:
|
|
29
|
+
return 'Gemini conversation turn limit reached (exit code 53). Start a new Gemini session.';
|
|
30
|
+
default:
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const GEMINI_AUTH_ENV_KEYS = [
|
|
36
|
+
'GEMINI_API_KEY',
|
|
37
|
+
'GOOGLE_API_KEY',
|
|
38
|
+
'GOOGLE_CLOUD_PROJECT',
|
|
39
|
+
'GOOGLE_CLOUD_PROJECT_ID',
|
|
40
|
+
'GOOGLE_CLOUD_LOCATION',
|
|
41
|
+
'GOOGLE_APPLICATION_CREDENTIALS'
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
function parseEnvFileContent(content) {
|
|
45
|
+
const parsed = {};
|
|
46
|
+
|
|
47
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
48
|
+
const line = rawLine.trim();
|
|
49
|
+
if (!line || line.startsWith('#')) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const exportPrefix = 'export ';
|
|
54
|
+
const normalizedLine = line.startsWith(exportPrefix) ? line.slice(exportPrefix.length).trim() : line;
|
|
55
|
+
const separatorIndex = normalizedLine.indexOf('=');
|
|
56
|
+
|
|
57
|
+
if (separatorIndex <= 0) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const key = normalizedLine.slice(0, separatorIndex).trim();
|
|
62
|
+
if (!key) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let value = normalizedLine.slice(separatorIndex + 1).trim();
|
|
67
|
+
const hasDoubleQuotes = value.startsWith('"') && value.endsWith('"');
|
|
68
|
+
const hasSingleQuotes = value.startsWith('\'') && value.endsWith('\'');
|
|
69
|
+
|
|
70
|
+
if (hasDoubleQuotes || hasSingleQuotes) {
|
|
71
|
+
value = value.slice(1, -1);
|
|
72
|
+
} else {
|
|
73
|
+
// Support inline comments in unquoted values: KEY=value # comment
|
|
74
|
+
value = value.replace(/\s+#.*$/, '').trim();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
parsed[key] = value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return parsed;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function loadGeminiUserLevelEnv() {
|
|
84
|
+
const geminiCliHome = (process.env.GEMINI_CLI_HOME || '').trim() || os.homedir();
|
|
85
|
+
const envCandidates = [
|
|
86
|
+
path.join(geminiCliHome, '.gemini', '.env'),
|
|
87
|
+
path.join(geminiCliHome, '.env')
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
for (const envPath of envCandidates) {
|
|
91
|
+
try {
|
|
92
|
+
await fs.access(envPath);
|
|
93
|
+
const content = await fs.readFile(envPath, 'utf8');
|
|
94
|
+
return parseEnvFileContent(content);
|
|
95
|
+
} catch {
|
|
96
|
+
// Keep scanning for the next candidate.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function buildGeminiProcessEnv() {
|
|
104
|
+
const processEnv = { ...process.env };
|
|
105
|
+
if (processEnv.GEMINI_API_KEY || processEnv.GOOGLE_API_KEY || processEnv.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
106
|
+
return processEnv;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Gemini CLI docs recommend ~/.gemini/.env for persistent headless auth settings.
|
|
110
|
+
// When the server process was launched without shell profile variables, we still
|
|
111
|
+
// want the spawned CLI process to inherit those user-level credentials.
|
|
112
|
+
const userEnv = await loadGeminiUserLevelEnv();
|
|
113
|
+
for (const key of GEMINI_AUTH_ENV_KEYS) {
|
|
114
|
+
if (!processEnv[key] && userEnv[key]) {
|
|
115
|
+
processEnv[key] = userEnv[key];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return processEnv;
|
|
120
|
+
}
|
|
121
|
+
|
|
17
122
|
async function spawnGemini(command, options = {}, ws) {
|
|
18
123
|
const { sessionId, projectPath, cwd, toolsSettings, permissionMode, images, sessionSummary } = options;
|
|
124
|
+
const resolvedModel = await providerModelsService.resolveResumeModel(
|
|
125
|
+
'gemini',
|
|
126
|
+
sessionId,
|
|
127
|
+
options.model
|
|
128
|
+
);
|
|
19
129
|
let capturedSessionId = sessionId; // Track session ID throughout the process
|
|
20
130
|
let sessionCreatedSent = false; // Track if we've already sent session-created event
|
|
21
131
|
let assistantBlocks = []; // Accumulate the full response blocks including tools
|
|
@@ -100,6 +210,11 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
100
210
|
args.push('--debug');
|
|
101
211
|
}
|
|
102
212
|
|
|
213
|
+
// This integration runs Gemini in headless mode and cannot answer trust prompts.
|
|
214
|
+
// Skip folder-trust interactivity so authenticated runs don't fail with
|
|
215
|
+
// FatalUntrustedWorkspaceError in previously unseen directories.
|
|
216
|
+
args.push('--skip-trust');
|
|
217
|
+
|
|
103
218
|
// Add MCP config flag only if MCP servers are configured
|
|
104
219
|
try {
|
|
105
220
|
const geminiConfigPath = path.join(os.homedir(), '.gemini.json');
|
|
@@ -135,7 +250,7 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
135
250
|
}
|
|
136
251
|
|
|
137
252
|
// Add model for all sessions (both new and resumed)
|
|
138
|
-
let modelToUse =
|
|
253
|
+
let modelToUse = resolvedModel || 'gemini-2.5-flash';
|
|
139
254
|
args.push('--model', modelToUse);
|
|
140
255
|
args.push('--output-format', 'stream-json');
|
|
141
256
|
|
|
@@ -154,9 +269,6 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
154
269
|
|
|
155
270
|
// Try to find gemini in PATH first, then fall back to environment variable
|
|
156
271
|
const geminiPath = process.env.GEMINI_PATH || 'gemini';
|
|
157
|
-
console.log('Spawning Gemini CLI:', geminiPath, args.join(' '));
|
|
158
|
-
console.log('Working directory:', workingDir);
|
|
159
|
-
|
|
160
272
|
let spawnCmd = geminiPath;
|
|
161
273
|
let spawnArgs = args;
|
|
162
274
|
|
|
@@ -168,11 +280,13 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
168
280
|
spawnArgs = ['-c', 'exec "$0" "$@"', geminiPath, ...args];
|
|
169
281
|
}
|
|
170
282
|
|
|
283
|
+
const spawnEnv = await buildGeminiProcessEnv();
|
|
284
|
+
|
|
171
285
|
return new Promise((resolve, reject) => {
|
|
172
286
|
const geminiProcess = spawnFunction(spawnCmd, spawnArgs, {
|
|
173
287
|
cwd: workingDir,
|
|
174
288
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
175
|
-
env:
|
|
289
|
+
env: spawnEnv
|
|
176
290
|
});
|
|
177
291
|
let terminalNotificationSent = false;
|
|
178
292
|
let terminalFailureReason = null;
|
|
@@ -276,13 +390,44 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
276
390
|
}
|
|
277
391
|
},
|
|
278
392
|
onInit: (event) => {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
393
|
+
const discoveredSessionId = event?.session_id;
|
|
394
|
+
if (!discoveredSessionId) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// New Gemini sessions announce their canonical ID asynchronously via the
|
|
399
|
+
// initial `init` stream event. Avoid synthetic IDs and only register
|
|
400
|
+
// the session once that real ID is known (same model used by Claude/Codex).
|
|
401
|
+
if (!capturedSessionId) {
|
|
402
|
+
capturedSessionId = discoveredSessionId;
|
|
403
|
+
|
|
404
|
+
sessionManager.createSession(capturedSessionId, cwd || process.cwd());
|
|
405
|
+
if (command) {
|
|
406
|
+
sessionManager.addMessage(capturedSessionId, 'user', command);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (processKey !== capturedSessionId) {
|
|
410
|
+
activeGeminiProcesses.delete(processKey);
|
|
411
|
+
activeGeminiProcesses.set(capturedSessionId, geminiProcess);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
geminiProcess.sessionId = capturedSessionId;
|
|
415
|
+
|
|
416
|
+
if (ws.setSessionId && typeof ws.setSessionId === 'function') {
|
|
417
|
+
ws.setSessionId(capturedSessionId);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (!sessionId && !sessionCreatedSent) {
|
|
421
|
+
sessionCreatedSent = true;
|
|
422
|
+
ws.send(createNormalizedMessage({ kind: 'session_created', newSessionId: capturedSessionId, sessionId: capturedSessionId, provider: 'gemini' }));
|
|
284
423
|
}
|
|
285
424
|
}
|
|
425
|
+
|
|
426
|
+
const sess = sessionManager.getSession(capturedSessionId);
|
|
427
|
+
if (sess && !sess.cliSessionId) {
|
|
428
|
+
sess.cliSessionId = discoveredSessionId;
|
|
429
|
+
sessionManager.saveSession(capturedSessionId);
|
|
430
|
+
}
|
|
286
431
|
}
|
|
287
432
|
});
|
|
288
433
|
}
|
|
@@ -292,30 +437,6 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
292
437
|
const rawOutput = data.toString();
|
|
293
438
|
startTimeout(); // Re-arm the timeout
|
|
294
439
|
|
|
295
|
-
// For new sessions, create a session ID FIRST
|
|
296
|
-
if (!sessionId && !sessionCreatedSent && !capturedSessionId) {
|
|
297
|
-
capturedSessionId = `gemini_${Date.now()}`;
|
|
298
|
-
sessionCreatedSent = true;
|
|
299
|
-
|
|
300
|
-
// Create session in session manager
|
|
301
|
-
sessionManager.createSession(capturedSessionId, cwd || process.cwd());
|
|
302
|
-
|
|
303
|
-
// Save the user message now that we have a session ID
|
|
304
|
-
if (command) {
|
|
305
|
-
sessionManager.addMessage(capturedSessionId, 'user', command);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Update process key with captured session ID
|
|
309
|
-
if (processKey !== capturedSessionId) {
|
|
310
|
-
activeGeminiProcesses.delete(processKey);
|
|
311
|
-
activeGeminiProcesses.set(capturedSessionId, geminiProcess);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
ws.setSessionId && typeof ws.setSessionId === 'function' && ws.setSessionId(capturedSessionId);
|
|
315
|
-
|
|
316
|
-
ws.send(createNormalizedMessage({ kind: 'session_created', newSessionId: capturedSessionId, sessionId: capturedSessionId, provider: 'gemini' }));
|
|
317
|
-
}
|
|
318
|
-
|
|
319
440
|
if (responseHandler) {
|
|
320
441
|
responseHandler.processData(rawOutput);
|
|
321
442
|
} else if (rawOutput) {
|
|
@@ -381,12 +502,38 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
381
502
|
notifyTerminalState({ code });
|
|
382
503
|
resolve();
|
|
383
504
|
} else {
|
|
384
|
-
|
|
505
|
+
const socketSessionId = typeof ws.getSessionId === 'function' ? ws.getSessionId() : finalSessionId;
|
|
506
|
+
|
|
507
|
+
// code 127 = shell "command not found" - check installation
|
|
385
508
|
if (code === 127) {
|
|
386
509
|
const installed = await providerAuthService.isProviderInstalled('gemini');
|
|
387
510
|
if (!installed) {
|
|
388
|
-
|
|
389
|
-
ws.send(createNormalizedMessage({ kind: 'error', content:
|
|
511
|
+
terminalFailureReason = 'Gemini CLI is not installed. Please install it first: https://github.com/google-gemini/gemini-cli';
|
|
512
|
+
ws.send(createNormalizedMessage({ kind: 'error', content: terminalFailureReason, sessionId: socketSessionId, provider: 'gemini' }));
|
|
513
|
+
}
|
|
514
|
+
} else if (code === 41) {
|
|
515
|
+
// Gemini CLI documents exit code 41 as FatalAuthenticationError.
|
|
516
|
+
// Surface an actionable auth error instead of a generic exit-code message.
|
|
517
|
+
let authErrorSuffix = '';
|
|
518
|
+
try {
|
|
519
|
+
const authStatus = await providerAuthService.getProviderAuthStatus('gemini');
|
|
520
|
+
if (!authStatus?.authenticated && authStatus?.error) {
|
|
521
|
+
authErrorSuffix = ` Details: ${authStatus.error}`;
|
|
522
|
+
}
|
|
523
|
+
} catch {
|
|
524
|
+
// Keep base remediation text when auth status lookup fails.
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
terminalFailureReason =
|
|
528
|
+
'Gemini authentication failed (exit code 41). '
|
|
529
|
+
+ 'Run `gemini` in a terminal to choose an auth method, or configure a valid `GEMINI_API_KEY`.'
|
|
530
|
+
+ authErrorSuffix;
|
|
531
|
+
ws.send(createNormalizedMessage({ kind: 'error', content: terminalFailureReason, sessionId: socketSessionId, provider: 'gemini' }));
|
|
532
|
+
} else {
|
|
533
|
+
const mappedError = mapGeminiExitCodeToMessage(code);
|
|
534
|
+
if (mappedError) {
|
|
535
|
+
terminalFailureReason = mappedError;
|
|
536
|
+
ws.send(createNormalizedMessage({ kind: 'error', content: terminalFailureReason, sessionId: socketSessionId, provider: 'gemini' }));
|
|
390
537
|
}
|
|
391
538
|
}
|
|
392
539
|
|
|
@@ -394,7 +541,14 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
394
541
|
code,
|
|
395
542
|
error: code === null ? 'Gemini CLI process was terminated or timed out' : null
|
|
396
543
|
});
|
|
397
|
-
reject(
|
|
544
|
+
reject(
|
|
545
|
+
new Error(
|
|
546
|
+
terminalFailureReason
|
|
547
|
+
|| (code === null
|
|
548
|
+
? 'Gemini CLI process was terminated or timed out'
|
|
549
|
+
: `Gemini CLI exited with code ${code}`)
|
|
550
|
+
)
|
|
551
|
+
);
|
|
398
552
|
}
|
|
399
553
|
});
|
|
400
554
|
|
|
@@ -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() {
|