@cloudcli-ai/cloudcli 1.36.0 → 1.36.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 +17 -18
- package/README.ja.md +16 -17
- package/README.ko.md +17 -18
- package/README.md +19 -20
- package/README.ru.md +17 -18
- package/README.tr.md +17 -18
- package/README.zh-CN.md +17 -18
- package/README.zh-TW.md +17 -18
- package/dist/api-docs.html +2 -3
- package/dist/assets/{index-S6cIy7hj.js → index-Bz9vXV2m.js} +264 -259
- package/dist/assets/index-DLmhQ15L.css +32 -0
- package/dist/assets/{vendor-xterm-CJZjLICi.js → vendor-xterm-CS4rQ5Mr.js} +12 -12
- package/dist/index.html +3 -3
- package/dist-server/server/claude-sdk.js +37 -90
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cli.js +1 -4
- package/dist-server/server/cli.js.map +1 -1
- package/dist-server/server/cursor-cli.js +12 -11
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/index.js +10 -141
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/assets/assets.routes.js +89 -0
- package/dist-server/server/modules/assets/assets.routes.js.map +1 -0
- package/dist-server/server/modules/assets/index.js +4 -0
- package/dist-server/server/modules/assets/index.js.map +1 -0
- package/dist-server/server/modules/assets/services/image-assets.service.js +57 -0
- package/dist-server/server/modules/assets/services/image-assets.service.js.map +1 -0
- package/dist-server/server/modules/assets/tests/image-assets.service.test.js +36 -0
- package/dist-server/server/modules/assets/tests/image-assets.service.test.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js +6 -3
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +2 -2
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +0 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-clone.service.js +2 -1
- package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +28 -0
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +54 -1
- 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 +36 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +3 -17
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js +7 -1
- 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 +49 -13
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js +0 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +8 -14
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +24 -5
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +10 -21
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.registry.js +0 -2
- package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +0 -1
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +7 -14
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +3 -77
- 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 +0 -1
- 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 +0 -13
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +96 -0
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/mcp.test.js +3 -29
- package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +7 -0
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +117 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-image-history.test.js +147 -0
- package/dist-server/server/modules/providers/tests/provider-image-history.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js +8 -8
- package/dist-server/server/modules/providers/tests/skills.test.js +3 -26
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +14 -0
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +36 -2
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +3 -12
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/chat-image-filter.test.js +33 -0
- package/dist-server/server/modules/websocket/tests/chat-image-filter.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +37 -4
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
- package/dist-server/server/openai-codex.js +8 -2
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/opencode-cli.js +43 -6
- package/dist-server/server/opencode-cli.js.map +1 -1
- package/dist-server/server/opencode-cli.test.js +103 -3
- package/dist-server/server/opencode-cli.test.js.map +1 -1
- package/dist-server/server/routes/agent.js +9 -19
- package/dist-server/server/routes/agent.js.map +1 -1
- package/dist-server/server/routes/commands.js +1 -2
- package/dist-server/server/routes/commands.js.map +1 -1
- package/dist-server/server/routes/git.js +179 -53
- package/dist-server/server/routes/git.js.map +1 -1
- package/dist-server/server/routes/git.test.js +80 -0
- package/dist-server/server/routes/git.test.js.map +1 -0
- package/dist-server/server/routes/taskmaster.js +3 -1
- package/dist-server/server/routes/taskmaster.js.map +1 -1
- package/dist-server/server/routes/user.js +2 -1
- package/dist-server/server/routes/user.js.map +1 -1
- package/dist-server/server/shared/image-attachments.js +270 -0
- package/dist-server/server/shared/image-attachments.js.map +1 -0
- package/dist-server/server/shared/tests/image-attachments.test.js +220 -0
- package/dist-server/server/shared/tests/image-attachments.test.js.map +1 -0
- package/dist-server/server/shared/utils.js +43 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/dist-server/server/utils/gitConfig.js +2 -1
- package/dist-server/server/utils/gitConfig.js.map +1 -1
- package/dist-server/server/utils/plugin-process-manager.js +2 -1
- package/dist-server/server/utils/plugin-process-manager.js.map +1 -1
- package/electron/launcher/launcher.js +1 -1
- package/package.json +3 -3
- package/public/api-docs.html +2 -3
- package/server/claude-sdk.js +37 -105
- package/server/cli.js +1 -4
- package/server/cursor-cli.js +13 -12
- package/server/index.js +11 -159
- package/server/modules/assets/assets.routes.ts +103 -0
- package/server/modules/assets/index.ts +3 -0
- package/server/modules/assets/services/image-assets.service.ts +82 -0
- package/server/modules/assets/tests/image-assets.service.test.ts +46 -0
- package/server/modules/browser-use/browser-use.service.ts +7 -3
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +2 -2
- package/server/modules/notifications/services/notification-orchestrator.service.js +0 -1
- package/server/modules/projects/services/project-clone.service.ts +3 -1
- package/server/modules/providers/README.md +1 -7
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +30 -0
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +60 -1
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +41 -1
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +3 -17
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +7 -1
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +56 -15
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +0 -1
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +15 -19
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +25 -4
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +10 -21
- package/server/modules/providers/provider.registry.ts +0 -2
- package/server/modules/providers/provider.routes.ts +0 -1
- package/server/modules/providers/services/provider-capabilities.service.ts +7 -14
- package/server/modules/providers/services/provider-models.service.ts +1 -1
- package/server/modules/providers/services/session-conversations-search.service.ts +3 -93
- package/server/modules/providers/services/session-synchronizer.service.ts +0 -1
- package/server/modules/providers/services/sessions-watcher.service.ts +0 -14
- package/server/modules/providers/tests/codex-sessions.test.ts +111 -0
- package/server/modules/providers/tests/mcp.test.ts +3 -34
- package/server/modules/providers/tests/opencode-models.test.ts +7 -0
- package/server/modules/providers/tests/opencode-sessions.test.ts +139 -0
- package/server/modules/providers/tests/provider-image-history.test.ts +180 -0
- package/server/modules/providers/tests/provider-models.service.test.ts +8 -8
- package/server/modules/providers/tests/skills.test.ts +3 -42
- package/server/modules/websocket/services/chat-run-registry.service.ts +16 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +41 -2
- package/server/modules/websocket/services/shell-websocket.service.ts +1 -11
- package/server/modules/websocket/tests/chat-image-filter.test.ts +44 -0
- package/server/modules/websocket/tests/chat-run-registry.test.ts +42 -4
- package/server/openai-codex.js +9 -1
- package/server/opencode-cli.js +44 -6
- package/server/opencode-cli.test.js +112 -3
- package/server/routes/agent.js +9 -19
- package/server/routes/commands.js +1 -2
- package/server/routes/git.js +201 -60
- package/server/routes/git.test.js +106 -0
- package/server/routes/taskmaster.js +3 -1
- package/server/routes/user.js +2 -1
- package/server/shared/image-attachments.ts +341 -0
- package/server/shared/tests/image-attachments.test.ts +298 -0
- package/server/shared/types.ts +1 -1
- package/server/shared/utils.ts +45 -0
- package/server/utils/gitConfig.js +2 -1
- package/server/utils/plugin-process-manager.js +3 -1
- package/dist/assets/index-uLUE6Qt1.css +0 -32
- package/dist/icons/gemini-ai-icon.svg +0 -1
- package/dist-server/server/gemini-cli.js +0 -551
- package/dist-server/server/gemini-cli.js.map +0 -1
- package/dist-server/server/gemini-response-handler.js +0 -106
- package/dist-server/server/gemini-response-handler.js.map +0 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +0 -254
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +0 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js +0 -82
- package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js.map +0 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +0 -24
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +0 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js +0 -312
- package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js.map +0 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +0 -474
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +0 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +0 -40
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +0 -1
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +0 -19
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +0 -1
- package/dist-server/server/routes/gemini.js +0 -21
- package/dist-server/server/routes/gemini.js.map +0 -1
- package/dist-server/server/sessionManager.js +0 -194
- package/dist-server/server/sessionManager.js.map +0 -1
- package/server/gemini-cli.js +0 -638
- package/server/gemini-response-handler.js +0 -117
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +0 -307
- package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +0 -110
- package/server/modules/providers/list/gemini/gemini-models.provider.ts +0 -39
- package/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.ts +0 -405
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +0 -540
- package/server/modules/providers/list/gemini/gemini-skills.provider.ts +0 -44
- package/server/modules/providers/list/gemini/gemini.provider.ts +0 -27
- package/server/routes/gemini.js +0 -25
- package/server/sessionManager.js +0 -226
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
|
|
3
1
|
import Database from 'better-sqlite3';
|
|
4
2
|
import crossSpawn from 'cross-spawn';
|
|
5
3
|
|
|
@@ -51,23 +49,15 @@ export const OPENCODE_FALLBACK_MODELS: ProviderModelsDefinition = {
|
|
|
51
49
|
label: 'GPT-5.4 Mini',
|
|
52
50
|
description: 'openai - openai/gpt-5.4-mini',
|
|
53
51
|
},
|
|
54
|
-
{
|
|
55
|
-
value: 'google/gemini-2.5-pro',
|
|
56
|
-
label: 'Gemini 2.5 Pro',
|
|
57
|
-
description: 'google - google/gemini-2.5-pro',
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
value: 'google/gemini-2.5-flash',
|
|
61
|
-
label: 'Gemini 2.5 Flash',
|
|
62
|
-
description: 'google - google/gemini-2.5-flash',
|
|
63
|
-
},
|
|
64
52
|
],
|
|
65
53
|
DEFAULT: 'anthropic/claude-sonnet-4-5',
|
|
66
54
|
};
|
|
67
55
|
|
|
68
56
|
const OPEN_CODE_MODELS_TIMEOUT_MS = 20_000;
|
|
69
57
|
const MODEL_ID_LINE = /^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._-]*$/i;
|
|
70
|
-
|
|
58
|
+
// cross-spawn resolves .cmd shims/PATHEXT on Windows and delegates to
|
|
59
|
+
// child_process.spawn everywhere else.
|
|
60
|
+
const spawnFunction = crossSpawn;
|
|
71
61
|
const DATE_TOKEN = /^\d{8}$/;
|
|
72
62
|
const SIMPLE_NUMBER_TOKEN = /^\d$/;
|
|
73
63
|
const VERSION_TOKEN = /^[a-z]\d+$/i;
|
|
@@ -239,6 +229,10 @@ const readOpenCodeModelParts = (id: string): { upstreamProvider: string; slug: s
|
|
|
239
229
|
};
|
|
240
230
|
};
|
|
241
231
|
|
|
232
|
+
const isSupportedOpenCodeModelId = (id: string): boolean => (
|
|
233
|
+
readOpenCodeModelParts(id).upstreamProvider.toLowerCase() !== 'google'
|
|
234
|
+
);
|
|
235
|
+
|
|
242
236
|
const readOpenCodeVerboseModelId = (model: OpenCodeVerboseModel): string | null => {
|
|
243
237
|
const id = readOptionalString(model.id);
|
|
244
238
|
if (!id) {
|
|
@@ -296,7 +290,7 @@ const readOpenCodeEffortValues = (
|
|
|
296
290
|
|
|
297
291
|
const mapOpenCodeVerboseModel = (model: OpenCodeVerboseModel): ProviderModelOption | null => {
|
|
298
292
|
const value = readOpenCodeVerboseModelId(model);
|
|
299
|
-
if (!value) {
|
|
293
|
+
if (!value || !isSupportedOpenCodeModelId(value)) {
|
|
300
294
|
return null;
|
|
301
295
|
}
|
|
302
296
|
|
|
@@ -315,11 +309,13 @@ const mapOpenCodeVerboseModel = (model: OpenCodeVerboseModel): ProviderModelOpti
|
|
|
315
309
|
};
|
|
316
310
|
|
|
317
311
|
export const buildOpenCodeDefinitionFromIds = (ids: string[]): ProviderModelsDefinition => {
|
|
318
|
-
const options: ProviderModelOption[] = ids
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
312
|
+
const options: ProviderModelOption[] = ids
|
|
313
|
+
.filter(isSupportedOpenCodeModelId)
|
|
314
|
+
.map((value) => ({
|
|
315
|
+
value,
|
|
316
|
+
label: labelForOpenCodeModelId(value),
|
|
317
|
+
description: descriptionForOpenCodeModelId(value),
|
|
318
|
+
}));
|
|
323
319
|
|
|
324
320
|
const defaultValue = options.find((option) => option.value === OPENCODE_FALLBACK_MODELS.DEFAULT)?.value
|
|
325
321
|
?? options[0]?.value
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
normalizeSessionName,
|
|
12
12
|
readJsonRecord,
|
|
13
13
|
readOptionalString,
|
|
14
|
+
unwrapJsonStringLiteral,
|
|
14
15
|
} from '@/shared/utils.js';
|
|
15
16
|
|
|
16
17
|
type OpenCodeSessionRow = {
|
|
@@ -128,9 +129,26 @@ export class OpenCodeSessionSynchronizer implements IProviderSessionSynchronizer
|
|
|
128
129
|
const existingSession = sessionsDb.getSessionByProviderSessionId(sessionId)
|
|
129
130
|
?? sessionsDb.getSessionById(sessionId);
|
|
130
131
|
const existingName = existingSession?.custom_name;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
|
|
133
|
+
// Sessions started by sending a message from cloudcli carry a distinct
|
|
134
|
+
// app-allocated session_id mapped to the provider id. For these we title the
|
|
135
|
+
// conversation from the first user message the user typed, matching how the
|
|
136
|
+
// app titles a brand-new conversation. Sessions discovered purely by
|
|
137
|
+
// indexing (session_id === provider_session_id) keep OpenCode's own stored
|
|
138
|
+
// title.
|
|
139
|
+
const isAppCreated =
|
|
140
|
+
existingSession != null &&
|
|
141
|
+
existingSession.provider_session_id != null &&
|
|
142
|
+
existingSession.session_id !== existingSession.provider_session_id;
|
|
143
|
+
|
|
144
|
+
let nextName: string | undefined;
|
|
145
|
+
if (existingName && existingName !== fallbackTitle) {
|
|
146
|
+
nextName = existingName;
|
|
147
|
+
} else if (isAppCreated) {
|
|
148
|
+
nextName = this.readFirstUserText(db, sessionId) ?? readOptionalString(row.title);
|
|
149
|
+
} else {
|
|
150
|
+
nextName = readOptionalString(row.title) ?? this.readFirstUserText(db, sessionId);
|
|
151
|
+
}
|
|
134
152
|
|
|
135
153
|
// OpenCode stores every session in one shared sqlite database, so jsonl_path
|
|
136
154
|
// must stay null to avoid deleting opencode.db when one app session is removed.
|
|
@@ -163,7 +181,10 @@ export class OpenCodeSessionSynchronizer implements IProviderSessionSynchronizer
|
|
|
163
181
|
`).get(sessionId) as { data: string | null } | undefined;
|
|
164
182
|
|
|
165
183
|
const data = readJsonRecord(row?.data);
|
|
166
|
-
|
|
184
|
+
const text = readOptionalString(data?.text);
|
|
185
|
+
// OpenCode persists the first prompt as a JSON string literal (e.g.
|
|
186
|
+
// `"hello"`), so decode it to avoid titling the session with quotes.
|
|
187
|
+
return text === undefined ? undefined : unwrapJsonStringLiteral(text);
|
|
167
188
|
} catch {
|
|
168
189
|
return undefined;
|
|
169
190
|
}
|
|
@@ -2,6 +2,7 @@ import fsSync from 'node:fs';
|
|
|
2
2
|
|
|
3
3
|
import Database from 'better-sqlite3';
|
|
4
4
|
|
|
5
|
+
import { parseImagesInputTag } from '@/shared/image-attachments.js';
|
|
5
6
|
import type { IProviderSessions } from '@/shared/interfaces.js';
|
|
6
7
|
import type { AnyRecord, FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
|
|
7
8
|
import {
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
readJsonRecord,
|
|
14
15
|
readOptionalString,
|
|
15
16
|
sliceTailPage,
|
|
17
|
+
unwrapJsonStringLiteral,
|
|
16
18
|
} from '@/shared/utils.js';
|
|
17
19
|
|
|
18
20
|
const PROVIDER = 'opencode';
|
|
@@ -59,25 +61,6 @@ const formatToolContent = (value: unknown): string => {
|
|
|
59
61
|
}
|
|
60
62
|
};
|
|
61
63
|
|
|
62
|
-
/**
|
|
63
|
-
* OpenCode can persist the first prompt as a JSON string literal inside a text
|
|
64
|
-
* part, for example `"hello"` instead of `hello`. Decode only complete JSON
|
|
65
|
-
* string literals so normal assistant/user prose remains untouched.
|
|
66
|
-
*/
|
|
67
|
-
const unwrapJsonStringLiteral = (value: string): string => {
|
|
68
|
-
const trimmed = value.trim();
|
|
69
|
-
if (!trimmed.startsWith('"') || !trimmed.endsWith('"')) {
|
|
70
|
-
return value;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
const parsed = JSON.parse(trimmed);
|
|
75
|
-
return typeof parsed === 'string' ? parsed : value;
|
|
76
|
-
} catch {
|
|
77
|
-
return value;
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
64
|
const extractText = (value: unknown): string => {
|
|
82
65
|
if (typeof value === 'string') {
|
|
83
66
|
return unwrapJsonStringLiteral(value);
|
|
@@ -418,8 +401,13 @@ export class OpenCodeSessionsProvider implements IProviderSessions {
|
|
|
418
401
|
}
|
|
419
402
|
|
|
420
403
|
if (partType === 'text') {
|
|
421
|
-
const
|
|
422
|
-
|
|
404
|
+
const rawContent = extractText(partData);
|
|
405
|
+
// User prompts sent with attachments carry an <images_input> path
|
|
406
|
+
// list; strip it for display and surface the paths as images.
|
|
407
|
+
const { text: content, attachments } = messageRole === 'user'
|
|
408
|
+
? parseImagesInputTag(rawContent)
|
|
409
|
+
: { text: rawContent, attachments: [] };
|
|
410
|
+
if (content.trim() || attachments.length > 0) {
|
|
423
411
|
normalized.push(createNormalizedMessage({
|
|
424
412
|
id: baseId,
|
|
425
413
|
sessionId,
|
|
@@ -428,6 +416,7 @@ export class OpenCodeSessionsProvider implements IProviderSessions {
|
|
|
428
416
|
kind: 'text',
|
|
429
417
|
role: messageRole === 'user' ? 'user' : 'assistant',
|
|
430
418
|
content,
|
|
419
|
+
images: attachments.length > 0 ? attachments : undefined,
|
|
431
420
|
}));
|
|
432
421
|
}
|
|
433
422
|
continue;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ClaudeProvider } from '@/modules/providers/list/claude/claude.provider.js';
|
|
2
2
|
import { CodexProvider } from '@/modules/providers/list/codex/codex.provider.js';
|
|
3
3
|
import { CursorProvider } from '@/modules/providers/list/cursor/cursor.provider.js';
|
|
4
|
-
import { GeminiProvider } from '@/modules/providers/list/gemini/gemini.provider.js';
|
|
5
4
|
import { OpenCodeProvider } from '@/modules/providers/list/opencode/opencode.provider.js';
|
|
6
5
|
import type { IProvider } from '@/shared/interfaces.js';
|
|
7
6
|
import type { LLMProvider } from '@/shared/types.js';
|
|
@@ -11,7 +10,6 @@ const providers: Record<LLMProvider, IProvider> = {
|
|
|
11
10
|
claude: new ClaudeProvider(),
|
|
12
11
|
codex: new CodexProvider(),
|
|
13
12
|
cursor: new CursorProvider(),
|
|
14
|
-
gemini: new GeminiProvider(),
|
|
15
13
|
opencode: new OpenCodeProvider(),
|
|
16
14
|
};
|
|
17
15
|
|
|
@@ -46,7 +46,7 @@ const PROVIDER_CAPABILITIES: Record<LLMProvider, ProviderCapabilities> = {
|
|
|
46
46
|
provider: 'cursor',
|
|
47
47
|
permissionModes: ['default', 'acceptEdits', 'bypassPermissions', 'plan'],
|
|
48
48
|
defaultPermissionMode: 'default',
|
|
49
|
-
supportsImages:
|
|
49
|
+
supportsImages: true,
|
|
50
50
|
supportsAbort: true,
|
|
51
51
|
supportsPermissionRequests: false,
|
|
52
52
|
supportsTokenUsage: false,
|
|
@@ -56,27 +56,20 @@ const PROVIDER_CAPABILITIES: Record<LLMProvider, ProviderCapabilities> = {
|
|
|
56
56
|
provider: 'codex',
|
|
57
57
|
permissionModes: ['default', 'acceptEdits', 'bypassPermissions'],
|
|
58
58
|
defaultPermissionMode: 'default',
|
|
59
|
-
supportsImages:
|
|
59
|
+
supportsImages: true,
|
|
60
60
|
supportsAbort: true,
|
|
61
61
|
supportsPermissionRequests: false,
|
|
62
62
|
supportsTokenUsage: true,
|
|
63
63
|
supportsEffort: true,
|
|
64
64
|
},
|
|
65
|
-
gemini: {
|
|
66
|
-
provider: 'gemini',
|
|
67
|
-
permissionModes: ['default', 'acceptEdits', 'bypassPermissions', 'plan'],
|
|
68
|
-
defaultPermissionMode: 'default',
|
|
69
|
-
supportsImages: false,
|
|
70
|
-
supportsAbort: true,
|
|
71
|
-
supportsPermissionRequests: false,
|
|
72
|
-
supportsTokenUsage: true,
|
|
73
|
-
supportsEffort: false,
|
|
74
|
-
},
|
|
75
65
|
opencode: {
|
|
76
66
|
provider: 'opencode',
|
|
77
|
-
|
|
67
|
+
// Mapped by the runtime onto OpenCode's controls: `--agent plan` (plan),
|
|
68
|
+
// `--auto` (bypassPermissions) and the OPENCODE_PERMISSION env var
|
|
69
|
+
// (acceptEdits). See resolveOpenCodePermissionOptions in opencode-cli.js.
|
|
70
|
+
permissionModes: ['default', 'acceptEdits', 'bypassPermissions', 'plan'],
|
|
78
71
|
defaultPermissionMode: 'default',
|
|
79
|
-
supportsImages:
|
|
72
|
+
supportsImages: true,
|
|
80
73
|
supportsAbort: true,
|
|
81
74
|
supportsPermissionRequests: false,
|
|
82
75
|
supportsTokenUsage: true,
|
|
@@ -17,7 +17,7 @@ import { readProviderSessionActiveModelChange } from '@/shared/utils.js';
|
|
|
17
17
|
|
|
18
18
|
export const PROVIDER_MODELS_CACHE_TTL_MS = 3 * 24 * 60 * 60 * 1000;
|
|
19
19
|
const PROVIDER_MODELS_CACHE_VERSION = 2;
|
|
20
|
-
const UNCACHED_PROVIDERS = new Set<LLMProvider>(['claude'
|
|
20
|
+
const UNCACHED_PROVIDERS = new Set<LLMProvider>(['claude']);
|
|
21
21
|
|
|
22
22
|
type ProviderModelsServiceDependencies = {
|
|
23
23
|
resolveProvider?: (provider: LLMProvider) => Pick<IProvider, 'models'>;
|
|
@@ -8,7 +8,7 @@ import { rgPath } from '@vscode/ripgrep';
|
|
|
8
8
|
import { projectsDb, sessionsDb } from '@/modules/database/index.js';
|
|
9
9
|
|
|
10
10
|
type AnyRecord = Record<string, any>;
|
|
11
|
-
type SearchableProvider = 'claude' | 'codex'
|
|
11
|
+
type SearchableProvider = 'claude' | 'codex';
|
|
12
12
|
|
|
13
13
|
type SearchSnippetHighlight = {
|
|
14
14
|
start: number;
|
|
@@ -82,7 +82,7 @@ type ProjectBucket = {
|
|
|
82
82
|
sessions: SearchableSessionRow[];
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
const SUPPORTED_PROVIDERS = new Set<SearchableProvider>(['claude', 'codex'
|
|
85
|
+
const SUPPORTED_PROVIDERS = new Set<SearchableProvider>(['claude', 'codex']);
|
|
86
86
|
const MAX_MATCHES_PER_SESSION = 2;
|
|
87
87
|
const RIPGREP_FILE_CHUNK_SIZE = 40;
|
|
88
88
|
const RIPGREP_CHUNK_CONCURRENCY = 6;
|
|
@@ -455,21 +455,6 @@ function extractCodexText(content: unknown): string {
|
|
|
455
455
|
.join(' ');
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
-
function extractGeminiText(content: unknown): string {
|
|
459
|
-
if (typeof content === 'string') {
|
|
460
|
-
return content;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
if (!Array.isArray(content)) {
|
|
464
|
-
return '';
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
return content
|
|
468
|
-
.filter((part: AnyRecord) => typeof part?.text === 'string')
|
|
469
|
-
.map((part: AnyRecord) => String(part.text))
|
|
470
|
-
.join(' ');
|
|
471
|
-
}
|
|
472
|
-
|
|
473
458
|
function normalizeSearchableSessions(rows: SessionRepositoryRow[]): SearchableSessionRow[] {
|
|
474
459
|
const normalizedRows: SearchableSessionRow[] = [];
|
|
475
460
|
const projectArchiveStateByPath = new Map<string, boolean>();
|
|
@@ -1065,81 +1050,6 @@ async function parseCodexSessionMatches(
|
|
|
1065
1050
|
};
|
|
1066
1051
|
}
|
|
1067
1052
|
|
|
1068
|
-
async function parseGeminiSessionMatches(
|
|
1069
|
-
session: SearchableSessionRow,
|
|
1070
|
-
runtime: SearchRuntime,
|
|
1071
|
-
): Promise<SessionConversationResult | null> {
|
|
1072
|
-
let data: string;
|
|
1073
|
-
try {
|
|
1074
|
-
data = await fs.readFile(session.jsonl_path, 'utf8');
|
|
1075
|
-
} catch {
|
|
1076
|
-
return null;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
let parsed: AnyRecord;
|
|
1080
|
-
try {
|
|
1081
|
-
parsed = JSON.parse(data) as AnyRecord;
|
|
1082
|
-
} catch {
|
|
1083
|
-
return null;
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
const sourceMessages = Array.isArray(parsed.messages) ? parsed.messages as AnyRecord[] : [];
|
|
1087
|
-
if (sourceMessages.length === 0) {
|
|
1088
|
-
return null;
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
const matches: SessionConversationMatch[] = [];
|
|
1092
|
-
let firstUserText: string | null = null;
|
|
1093
|
-
|
|
1094
|
-
for (const msg of sourceMessages) {
|
|
1095
|
-
if (runtime.totalMatches >= runtime.limit || runtime.isAborted()) {
|
|
1096
|
-
break;
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
const role = msg.type === 'user'
|
|
1100
|
-
? 'user'
|
|
1101
|
-
: (msg.type === 'gemini' || msg.type === 'assistant')
|
|
1102
|
-
? 'assistant'
|
|
1103
|
-
: null;
|
|
1104
|
-
if (!role) {
|
|
1105
|
-
continue;
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
const text = extractGeminiText(msg.content);
|
|
1109
|
-
if (!text) {
|
|
1110
|
-
continue;
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
if (role === 'user' && !firstUserText) {
|
|
1114
|
-
firstUserText = text;
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
if (!runtime.matchesQuery(text)) {
|
|
1118
|
-
continue;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
const { snippet, highlights } = runtime.buildSnippet(text);
|
|
1122
|
-
addSessionMatch(runtime, matches, {
|
|
1123
|
-
role,
|
|
1124
|
-
snippet,
|
|
1125
|
-
highlights,
|
|
1126
|
-
timestamp: msg.timestamp ? String(msg.timestamp) : null,
|
|
1127
|
-
provider: 'gemini',
|
|
1128
|
-
});
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
if (matches.length === 0) {
|
|
1132
|
-
return null;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
return {
|
|
1136
|
-
sessionId: session.session_id,
|
|
1137
|
-
provider: 'gemini',
|
|
1138
|
-
sessionSummary: toSummaryText(session.custom_name, firstUserText, 'Gemini Session'),
|
|
1139
|
-
matches,
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
1053
|
async function parseSessionMatches(
|
|
1144
1054
|
session: SearchableSessionRow,
|
|
1145
1055
|
runtime: SearchRuntime,
|
|
@@ -1150,7 +1060,7 @@ async function parseSessionMatches(
|
|
|
1150
1060
|
if (session.provider === 'codex') {
|
|
1151
1061
|
return parseCodexSessionMatches(session, runtime);
|
|
1152
1062
|
}
|
|
1153
|
-
return
|
|
1063
|
+
return null;
|
|
1154
1064
|
}
|
|
1155
1065
|
|
|
1156
1066
|
export async function searchConversations(
|
|
@@ -25,16 +25,6 @@ const PROVIDER_WATCH_PATHS: Array<{ provider: LLMProvider; rootPath: string }> =
|
|
|
25
25
|
provider: 'codex',
|
|
26
26
|
rootPath: path.join(os.homedir(), '.codex', 'sessions'),
|
|
27
27
|
},
|
|
28
|
-
// {
|
|
29
|
-
// provider: 'gemini',
|
|
30
|
-
// rootPath: path.join(os.homedir(), '.gemini', 'sessions'),
|
|
31
|
-
// },
|
|
32
|
-
// Keep `sessions/` watcher disabled: Gemini also mirrors artifacts there,
|
|
33
|
-
// which causes duplicate synchronization events.
|
|
34
|
-
{
|
|
35
|
-
provider: 'gemini',
|
|
36
|
-
rootPath: path.join(os.homedir(), '.gemini', 'tmp'),
|
|
37
|
-
},
|
|
38
28
|
{
|
|
39
29
|
provider: 'opencode',
|
|
40
30
|
rootPath: path.join(os.homedir(), '.local', 'share', 'opencode'),
|
|
@@ -81,10 +71,6 @@ function isWatcherTargetFile(provider: LLMProvider, filePath: string): boolean {
|
|
|
81
71
|
return path.basename(filePath) === 'opencode.db';
|
|
82
72
|
}
|
|
83
73
|
|
|
84
|
-
if (provider === 'gemini') {
|
|
85
|
-
return filePath.endsWith('.json') || filePath.endsWith('.jsonl');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
74
|
return filePath.endsWith('.jsonl');
|
|
89
75
|
}
|
|
90
76
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
|
|
7
|
+
import { closeConnection, initializeDatabase, sessionsDb } from '@/modules/database/index.js';
|
|
8
|
+
import { CodexSessionSynchronizer } from '@/modules/providers/list/codex/codex-session-synchronizer.provider.js';
|
|
9
|
+
|
|
10
|
+
const patchHomeDir = (nextHomeDir: string) => {
|
|
11
|
+
const original = os.homedir;
|
|
12
|
+
(os as any).homedir = () => nextHomeDir;
|
|
13
|
+
return () => {
|
|
14
|
+
(os as any).homedir = original;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
async function withIsolatedDatabase(runTest: () => void | Promise<void>): Promise<void> {
|
|
19
|
+
const previousDatabasePath = process.env.DATABASE_PATH;
|
|
20
|
+
const tempDirectory = await mkdtemp(path.join(os.tmpdir(), 'codex-provider-db-'));
|
|
21
|
+
const databasePath = path.join(tempDirectory, 'auth.db');
|
|
22
|
+
|
|
23
|
+
closeConnection();
|
|
24
|
+
process.env.DATABASE_PATH = databasePath;
|
|
25
|
+
await initializeDatabase();
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
await runTest();
|
|
29
|
+
} finally {
|
|
30
|
+
closeConnection();
|
|
31
|
+
if (previousDatabasePath === undefined) {
|
|
32
|
+
delete process.env.DATABASE_PATH;
|
|
33
|
+
} else {
|
|
34
|
+
process.env.DATABASE_PATH = previousDatabasePath;
|
|
35
|
+
}
|
|
36
|
+
await rm(tempDirectory, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Writes one Codex rollout transcript. `firstUserMessage` mirrors the
|
|
42
|
+
* `event_msg`/`user_message` payload the runtime records for the prompt the
|
|
43
|
+
* user typed; omitting it produces a transcript with no user turn.
|
|
44
|
+
*/
|
|
45
|
+
const writeCodexTranscript = async (
|
|
46
|
+
homeDir: string,
|
|
47
|
+
codexSessionId: string,
|
|
48
|
+
workspacePath: string,
|
|
49
|
+
firstUserMessage?: string,
|
|
50
|
+
): Promise<string> => {
|
|
51
|
+
const sessionsDir = path.join(homeDir, '.codex', 'sessions', '2026', '07', '07');
|
|
52
|
+
await mkdir(sessionsDir, { recursive: true });
|
|
53
|
+
|
|
54
|
+
const lines: string[] = [
|
|
55
|
+
JSON.stringify({ type: 'session_meta', payload: { id: codexSessionId, cwd: workspacePath } }),
|
|
56
|
+
];
|
|
57
|
+
if (firstUserMessage !== undefined) {
|
|
58
|
+
lines.push(JSON.stringify({ type: 'event_msg', payload: { type: 'user_message', message: firstUserMessage } }));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const filePath = path.join(sessionsDir, `rollout-${codexSessionId}.jsonl`);
|
|
62
|
+
await writeFile(filePath, `${lines.join('\n')}\n`, 'utf8');
|
|
63
|
+
return filePath;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
test('Codex synchronizer titles app-created sessions from the first user message', { concurrency: false }, async () => {
|
|
67
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-session-sync-app-'));
|
|
68
|
+
const workspacePath = path.join(tempRoot, 'workspace');
|
|
69
|
+
await mkdir(workspacePath, { recursive: true });
|
|
70
|
+
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
await writeCodexTranscript(tempRoot, 'codex-app-1', workspacePath, 'Fix the login redirect bug');
|
|
74
|
+
await withIsolatedDatabase(async () => {
|
|
75
|
+
// The app allocates its own id and later maps the provider id onto it,
|
|
76
|
+
// exactly as a message sent from cloudcli does.
|
|
77
|
+
sessionsDb.createAppSession('app-1', 'codex', workspacePath);
|
|
78
|
+
sessionsDb.assignProviderSessionId('app-1', 'codex-app-1');
|
|
79
|
+
|
|
80
|
+
const synchronizer = new CodexSessionSynchronizer();
|
|
81
|
+
await synchronizer.synchronize();
|
|
82
|
+
|
|
83
|
+
assert.equal(sessionsDb.getSessionById('app-1')?.custom_name, 'Fix the login redirect bug');
|
|
84
|
+
});
|
|
85
|
+
} finally {
|
|
86
|
+
restoreHomeDir();
|
|
87
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('Codex synchronizer leaves indexed sessions untitled when no name is available', { concurrency: false }, async () => {
|
|
92
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-session-sync-indexed-'));
|
|
93
|
+
const workspacePath = path.join(tempRoot, 'workspace');
|
|
94
|
+
await mkdir(workspacePath, { recursive: true });
|
|
95
|
+
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// A CLI-created session has no app row; its first user message must NOT be
|
|
99
|
+
// used as the title, preserving the existing indexing behavior.
|
|
100
|
+
await writeCodexTranscript(tempRoot, 'codex-indexed-1', workspacePath, 'This prompt should be ignored');
|
|
101
|
+
await withIsolatedDatabase(async () => {
|
|
102
|
+
const synchronizer = new CodexSessionSynchronizer();
|
|
103
|
+
await synchronizer.synchronize();
|
|
104
|
+
|
|
105
|
+
assert.equal(sessionsDb.getSessionById('codex-indexed-1')?.custom_name, 'Untitled Codex Session');
|
|
106
|
+
});
|
|
107
|
+
} finally {
|
|
108
|
+
restoreHomeDir();
|
|
109
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
@@ -257,34 +257,15 @@ test('providerMcpService handles opencode MCP config and capability validation',
|
|
|
257
257
|
});
|
|
258
258
|
|
|
259
259
|
/**
|
|
260
|
-
* This test covers
|
|
260
|
+
* This test covers Cursor MCP JSON format and user/project scope persistence.
|
|
261
261
|
*/
|
|
262
|
-
test('providerMcpService handles
|
|
262
|
+
test('providerMcpService handles cursor MCP JSON config formats', { concurrency: false }, async () => {
|
|
263
263
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-gc-'));
|
|
264
264
|
const workspacePath = path.join(tempRoot, 'workspace');
|
|
265
265
|
await fs.mkdir(workspacePath, { recursive: true });
|
|
266
266
|
|
|
267
267
|
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
268
268
|
try {
|
|
269
|
-
await providerMcpService.upsertProviderMcpServer('gemini', {
|
|
270
|
-
name: 'gemini-stdio',
|
|
271
|
-
scope: 'user',
|
|
272
|
-
transport: 'stdio',
|
|
273
|
-
command: 'node',
|
|
274
|
-
args: ['server.js'],
|
|
275
|
-
env: { TOKEN: '$TOKEN' },
|
|
276
|
-
cwd: './server',
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
await providerMcpService.upsertProviderMcpServer('gemini', {
|
|
280
|
-
name: 'gemini-http',
|
|
281
|
-
scope: 'project',
|
|
282
|
-
transport: 'http',
|
|
283
|
-
url: 'https://gemini.example.com/mcp',
|
|
284
|
-
headers: { Authorization: 'Bearer token' },
|
|
285
|
-
workspacePath,
|
|
286
|
-
});
|
|
287
|
-
|
|
288
269
|
await providerMcpService.upsertProviderMcpServer('cursor', {
|
|
289
270
|
name: 'cursor-stdio',
|
|
290
271
|
scope: 'project',
|
|
@@ -303,15 +284,6 @@ test('providerMcpService handles gemini and cursor MCP JSON config formats', { c
|
|
|
303
284
|
headers: { API_KEY: 'value' },
|
|
304
285
|
});
|
|
305
286
|
|
|
306
|
-
const geminiUserConfig = await readJson(path.join(tempRoot, '.gemini', 'settings.json'));
|
|
307
|
-
const geminiUserServer = (geminiUserConfig.mcpServers as Record<string, unknown>)['gemini-stdio'] as Record<string, unknown>;
|
|
308
|
-
assert.equal(geminiUserServer.command, 'node');
|
|
309
|
-
assert.equal(geminiUserServer.type, undefined);
|
|
310
|
-
|
|
311
|
-
const geminiProjectConfig = await readJson(path.join(workspacePath, '.gemini', 'settings.json'));
|
|
312
|
-
const geminiProjectServer = (geminiProjectConfig.mcpServers as Record<string, unknown>)['gemini-http'] as Record<string, unknown>;
|
|
313
|
-
assert.equal(geminiProjectServer.type, 'http');
|
|
314
|
-
|
|
315
287
|
const cursorUserConfig = await readJson(path.join(tempRoot, '.cursor', 'mcp.json'));
|
|
316
288
|
const cursorHttpServer = (cursorUserConfig.mcpServers as Record<string, unknown>)['cursor-http'] as Record<string, unknown>;
|
|
317
289
|
assert.equal(cursorHttpServer.url, 'http://localhost:3333/mcp');
|
|
@@ -341,7 +313,7 @@ test('providerMcpService global adder writes to all providers and rejects unsupp
|
|
|
341
313
|
workspacePath,
|
|
342
314
|
});
|
|
343
315
|
|
|
344
|
-
assert.equal(globalResult.length,
|
|
316
|
+
assert.equal(globalResult.length, 4);
|
|
345
317
|
assert.ok(globalResult.every((entry) => entry.created === true));
|
|
346
318
|
|
|
347
319
|
const claudeProject = await readJson(path.join(workspacePath, '.mcp.json'));
|
|
@@ -350,9 +322,6 @@ test('providerMcpService global adder writes to all providers and rejects unsupp
|
|
|
350
322
|
const codexProject = TOML.parse(await fs.readFile(path.join(workspacePath, '.codex', 'config.toml'), 'utf8')) as Record<string, unknown>;
|
|
351
323
|
assert.ok((codexProject.mcp_servers as Record<string, unknown>)['global-http']);
|
|
352
324
|
|
|
353
|
-
const geminiProject = await readJson(path.join(workspacePath, '.gemini', 'settings.json'));
|
|
354
|
-
assert.ok((geminiProject.mcpServers as Record<string, unknown>)['global-http']);
|
|
355
|
-
|
|
356
325
|
const opencodeProject = await readJson(path.join(workspacePath, 'opencode.json'));
|
|
357
326
|
assert.ok((opencodeProject.mcp as Record<string, unknown>)['global-http']);
|
|
358
327
|
|
|
@@ -30,6 +30,7 @@ test('OpenCode models provider formats frontend labels from provider-prefixed id
|
|
|
30
30
|
'opencode/nemotron-3-super-free',
|
|
31
31
|
'anthropic/claude-3-5-sonnet-20241022',
|
|
32
32
|
'anthropic/claude-opus-4-7-fast',
|
|
33
|
+
'google/model-alpha',
|
|
33
34
|
'openai/gpt-5.4-mini-fast',
|
|
34
35
|
'openai/gpt-5.5-pro',
|
|
35
36
|
'newprovider/alpha-v12-special-20261231',
|
|
@@ -104,6 +105,12 @@ anthropic/claude-sonnet-5
|
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
}
|
|
108
|
+
google/model-alpha
|
|
109
|
+
{
|
|
110
|
+
"id": "model-alpha",
|
|
111
|
+
"providerID": "google",
|
|
112
|
+
"name": "Model Alpha"
|
|
113
|
+
}
|
|
107
114
|
`);
|
|
108
115
|
|
|
109
116
|
const definition = buildOpenCodeDefinitionFromVerboseModels(models);
|