@cloudcli-ai/cloudcli 1.35.1 → 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.
Files changed (223) hide show
  1. package/README.de.md +17 -18
  2. package/README.ja.md +16 -17
  3. package/README.ko.md +17 -18
  4. package/README.md +19 -20
  5. package/README.ru.md +17 -18
  6. package/README.tr.md +17 -18
  7. package/README.zh-CN.md +17 -18
  8. package/README.zh-TW.md +17 -18
  9. package/dist/api-docs.html +8 -3
  10. package/dist/assets/{index-D1IdCTT6.js → index-Bz9vXV2m.js} +265 -260
  11. package/dist/assets/index-DLmhQ15L.css +32 -0
  12. package/dist/assets/{vendor-xterm-CJZjLICi.js → vendor-xterm-CS4rQ5Mr.js} +12 -12
  13. package/dist/index.html +3 -3
  14. package/dist-server/server/claude-sdk.js +62 -116
  15. package/dist-server/server/claude-sdk.js.map +1 -1
  16. package/dist-server/server/cli.js +1 -4
  17. package/dist-server/server/cli.js.map +1 -1
  18. package/dist-server/server/cursor-cli.js +12 -11
  19. package/dist-server/server/cursor-cli.js.map +1 -1
  20. package/dist-server/server/index.js +10 -141
  21. package/dist-server/server/index.js.map +1 -1
  22. package/dist-server/server/modules/assets/assets.routes.js +89 -0
  23. package/dist-server/server/modules/assets/assets.routes.js.map +1 -0
  24. package/dist-server/server/modules/assets/index.js +4 -0
  25. package/dist-server/server/modules/assets/index.js.map +1 -0
  26. package/dist-server/server/modules/assets/services/image-assets.service.js +57 -0
  27. package/dist-server/server/modules/assets/services/image-assets.service.js.map +1 -0
  28. package/dist-server/server/modules/assets/tests/image-assets.service.test.js +36 -0
  29. package/dist-server/server/modules/assets/tests/image-assets.service.test.js.map +1 -0
  30. package/dist-server/server/modules/browser-use/browser-use.service.js +6 -3
  31. package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
  32. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +2 -2
  33. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
  34. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +0 -1
  35. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
  36. package/dist-server/server/modules/projects/services/project-clone.service.js +2 -1
  37. package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -1
  38. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +70 -1
  39. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  40. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +28 -0
  41. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  42. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +52 -11
  43. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  44. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +54 -1
  45. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  46. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +36 -1
  47. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  48. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +3 -17
  49. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -1
  50. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js +7 -1
  51. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  52. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +49 -13
  53. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
  54. package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js +0 -1
  55. package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js.map +1 -1
  56. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +145 -14
  57. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  58. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +24 -5
  59. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  60. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +10 -21
  61. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
  62. package/dist-server/server/modules/providers/provider.registry.js +0 -2
  63. package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
  64. package/dist-server/server/modules/providers/provider.routes.js +0 -1
  65. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  66. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +11 -13
  67. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  68. package/dist-server/server/modules/providers/services/provider-models.service.js +2 -2
  69. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  70. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +3 -77
  71. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  72. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +0 -1
  73. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  74. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +0 -13
  75. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  76. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +96 -0
  77. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -0
  78. package/dist-server/server/modules/providers/tests/mcp.test.js +3 -29
  79. package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -1
  80. package/dist-server/server/modules/providers/tests/opencode-models.test.js +65 -1
  81. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  82. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +117 -0
  83. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  84. package/dist-server/server/modules/providers/tests/provider-image-history.test.js +147 -0
  85. package/dist-server/server/modules/providers/tests/provider-image-history.test.js.map +1 -0
  86. package/dist-server/server/modules/providers/tests/provider-models.service.test.js +8 -8
  87. package/dist-server/server/modules/providers/tests/skills.test.js +3 -26
  88. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  89. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +14 -0
  90. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  91. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +36 -2
  92. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  93. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +3 -12
  94. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  95. package/dist-server/server/modules/websocket/tests/chat-image-filter.test.js +33 -0
  96. package/dist-server/server/modules/websocket/tests/chat-image-filter.test.js.map +1 -0
  97. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +37 -4
  98. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
  99. package/dist-server/server/openai-codex.js +16 -9
  100. package/dist-server/server/openai-codex.js.map +1 -1
  101. package/dist-server/server/opencode-cli.js +62 -7
  102. package/dist-server/server/opencode-cli.js.map +1 -1
  103. package/dist-server/server/opencode-cli.test.js +103 -3
  104. package/dist-server/server/opencode-cli.test.js.map +1 -1
  105. package/dist-server/server/routes/agent.js +22 -21
  106. package/dist-server/server/routes/agent.js.map +1 -1
  107. package/dist-server/server/routes/commands.js +1 -2
  108. package/dist-server/server/routes/commands.js.map +1 -1
  109. package/dist-server/server/routes/git.js +179 -53
  110. package/dist-server/server/routes/git.js.map +1 -1
  111. package/dist-server/server/routes/git.test.js +80 -0
  112. package/dist-server/server/routes/git.test.js.map +1 -0
  113. package/dist-server/server/routes/taskmaster.js +3 -1
  114. package/dist-server/server/routes/taskmaster.js.map +1 -1
  115. package/dist-server/server/routes/user.js +2 -1
  116. package/dist-server/server/routes/user.js.map +1 -1
  117. package/dist-server/server/shared/image-attachments.js +270 -0
  118. package/dist-server/server/shared/image-attachments.js.map +1 -0
  119. package/dist-server/server/shared/tests/image-attachments.test.js +220 -0
  120. package/dist-server/server/shared/tests/image-attachments.test.js.map +1 -0
  121. package/dist-server/server/shared/utils.js +43 -0
  122. package/dist-server/server/shared/utils.js.map +1 -1
  123. package/dist-server/server/utils/gitConfig.js +2 -1
  124. package/dist-server/server/utils/gitConfig.js.map +1 -1
  125. package/dist-server/server/utils/plugin-process-manager.js +2 -1
  126. package/dist-server/server/utils/plugin-process-manager.js.map +1 -1
  127. package/electron/launcher/launcher.js +1 -1
  128. package/package.json +3 -3
  129. package/public/api-docs.html +8 -3
  130. package/server/claude-sdk.js +69 -131
  131. package/server/cli.js +1 -4
  132. package/server/cursor-cli.js +13 -12
  133. package/server/index.js +11 -159
  134. package/server/modules/assets/assets.routes.ts +103 -0
  135. package/server/modules/assets/index.ts +3 -0
  136. package/server/modules/assets/services/image-assets.service.ts +82 -0
  137. package/server/modules/assets/tests/image-assets.service.test.ts +46 -0
  138. package/server/modules/browser-use/browser-use.service.ts +7 -3
  139. package/server/modules/database/tests/sessions-provider-mapping.test.ts +2 -2
  140. package/server/modules/notifications/services/notification-orchestrator.service.js +0 -1
  141. package/server/modules/projects/services/project-clone.service.ts +3 -1
  142. package/server/modules/providers/README.md +1 -7
  143. package/server/modules/providers/list/claude/claude-models.provider.ts +73 -1
  144. package/server/modules/providers/list/claude/claude-sessions.provider.ts +30 -0
  145. package/server/modules/providers/list/codex/codex-models.provider.ts +59 -11
  146. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +60 -1
  147. package/server/modules/providers/list/codex/codex-sessions.provider.ts +41 -1
  148. package/server/modules/providers/list/cursor/cursor-models.provider.ts +3 -17
  149. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +7 -1
  150. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +56 -15
  151. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +0 -1
  152. package/server/modules/providers/list/opencode/opencode-models.provider.ts +193 -18
  153. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +25 -4
  154. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +10 -21
  155. package/server/modules/providers/provider.registry.ts +0 -2
  156. package/server/modules/providers/provider.routes.ts +0 -1
  157. package/server/modules/providers/services/provider-capabilities.service.ts +13 -13
  158. package/server/modules/providers/services/provider-models.service.ts +2 -2
  159. package/server/modules/providers/services/session-conversations-search.service.ts +3 -93
  160. package/server/modules/providers/services/session-synchronizer.service.ts +0 -1
  161. package/server/modules/providers/services/sessions-watcher.service.ts +0 -14
  162. package/server/modules/providers/tests/codex-sessions.test.ts +111 -0
  163. package/server/modules/providers/tests/mcp.test.ts +3 -34
  164. package/server/modules/providers/tests/opencode-models.test.ts +69 -0
  165. package/server/modules/providers/tests/opencode-sessions.test.ts +139 -0
  166. package/server/modules/providers/tests/provider-image-history.test.ts +180 -0
  167. package/server/modules/providers/tests/provider-models.service.test.ts +8 -8
  168. package/server/modules/providers/tests/skills.test.ts +3 -42
  169. package/server/modules/websocket/services/chat-run-registry.service.ts +16 -0
  170. package/server/modules/websocket/services/chat-websocket.service.ts +41 -2
  171. package/server/modules/websocket/services/shell-websocket.service.ts +1 -11
  172. package/server/modules/websocket/tests/chat-image-filter.test.ts +44 -0
  173. package/server/modules/websocket/tests/chat-run-registry.test.ts +42 -4
  174. package/server/openai-codex.js +18 -8
  175. package/server/opencode-cli.js +64 -7
  176. package/server/opencode-cli.test.js +112 -3
  177. package/server/routes/agent.js +22 -21
  178. package/server/routes/commands.js +1 -2
  179. package/server/routes/git.js +201 -60
  180. package/server/routes/git.test.js +106 -0
  181. package/server/routes/taskmaster.js +3 -1
  182. package/server/routes/user.js +2 -1
  183. package/server/shared/image-attachments.ts +341 -0
  184. package/server/shared/tests/image-attachments.test.ts +298 -0
  185. package/server/shared/types.ts +8 -1
  186. package/server/shared/utils.ts +45 -0
  187. package/server/utils/gitConfig.js +2 -1
  188. package/server/utils/plugin-process-manager.js +3 -1
  189. package/dist/assets/index-BvX30NW5.css +0 -32
  190. package/dist/icons/gemini-ai-icon.svg +0 -1
  191. package/dist-server/server/gemini-cli.js +0 -551
  192. package/dist-server/server/gemini-cli.js.map +0 -1
  193. package/dist-server/server/gemini-response-handler.js +0 -106
  194. package/dist-server/server/gemini-response-handler.js.map +0 -1
  195. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +0 -254
  196. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +0 -1
  197. package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js +0 -82
  198. package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js.map +0 -1
  199. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +0 -24
  200. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +0 -1
  201. package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js +0 -312
  202. package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js.map +0 -1
  203. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +0 -474
  204. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +0 -1
  205. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +0 -40
  206. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +0 -1
  207. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +0 -19
  208. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +0 -1
  209. package/dist-server/server/routes/gemini.js +0 -21
  210. package/dist-server/server/routes/gemini.js.map +0 -1
  211. package/dist-server/server/sessionManager.js +0 -194
  212. package/dist-server/server/sessionManager.js.map +0 -1
  213. package/server/gemini-cli.js +0 -638
  214. package/server/gemini-response-handler.js +0 -117
  215. package/server/modules/providers/list/gemini/gemini-auth.provider.ts +0 -307
  216. package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +0 -110
  217. package/server/modules/providers/list/gemini/gemini-models.provider.ts +0 -39
  218. package/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.ts +0 -405
  219. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +0 -540
  220. package/server/modules/providers/list/gemini/gemini-skills.provider.ts +0 -44
  221. package/server/modules/providers/list/gemini/gemini.provider.ts +0 -27
  222. package/server/routes/gemini.js +0 -25
  223. 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,29 +49,28 @@ 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
- const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
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;
74
64
  const NUMERIC_TOKEN = /^\d+(?:\.\d+)*$/;
75
65
  const SHORT_ACRONYM_TOKEN = /^[a-z]{2,3}$/;
76
66
 
67
+ type OpenCodeVerboseModel = {
68
+ id?: string;
69
+ name?: string;
70
+ providerID?: string;
71
+ variants?: Record<string, unknown>;
72
+ };
73
+
77
74
  export const parseOpenCodeModelsStdout = (stdout: string): string[] => {
78
75
  const ids: string[] = [];
79
76
 
@@ -91,6 +88,83 @@ export const parseOpenCodeModelsStdout = (stdout: string): string[] => {
91
88
  return [...new Set(ids)];
92
89
  };
93
90
 
91
+ const countJsonBraceDelta = (value: string): number => {
92
+ let delta = 0;
93
+ let inString = false;
94
+ let escaped = false;
95
+
96
+ for (const character of value) {
97
+ if (escaped) {
98
+ escaped = false;
99
+ continue;
100
+ }
101
+
102
+ if (character === '\\') {
103
+ escaped = inString;
104
+ continue;
105
+ }
106
+
107
+ if (character === '"') {
108
+ inString = !inString;
109
+ continue;
110
+ }
111
+
112
+ if (inString) {
113
+ continue;
114
+ }
115
+
116
+ if (character === '{') {
117
+ delta += 1;
118
+ } else if (character === '}') {
119
+ delta -= 1;
120
+ }
121
+ }
122
+
123
+ return delta;
124
+ };
125
+
126
+ const isOpenCodeVerboseModel = (value: unknown): value is OpenCodeVerboseModel => {
127
+ const record = readObjectRecord(value);
128
+ return Boolean(record && readOptionalString(record.id));
129
+ };
130
+
131
+ export const parseOpenCodeVerboseModelsStdout = (stdout: string): OpenCodeVerboseModel[] => {
132
+ const models: OpenCodeVerboseModel[] = [];
133
+ let buffer: string[] = [];
134
+ let depth = 0;
135
+
136
+ for (const rawLine of stdout.split(/\r?\n/)) {
137
+ const line = rawLine.trim();
138
+ if (buffer.length === 0) {
139
+ if (line === '{') {
140
+ buffer = [rawLine];
141
+ depth = 1;
142
+ }
143
+ continue;
144
+ }
145
+
146
+ buffer.push(rawLine);
147
+ depth += countJsonBraceDelta(rawLine);
148
+
149
+ if (depth !== 0) {
150
+ continue;
151
+ }
152
+
153
+ try {
154
+ const parsed = JSON.parse(buffer.join('\n'));
155
+ if (isOpenCodeVerboseModel(parsed)) {
156
+ models.push(parsed);
157
+ }
158
+ } catch {
159
+ // Ignore malformed verbose blocks and fall back to the plain id parser.
160
+ }
161
+
162
+ buffer = [];
163
+ }
164
+
165
+ return models;
166
+ };
167
+
94
168
  const formatDateToken = (token: string): string => (
95
169
  `${token.slice(0, 4)}-${token.slice(4, 6)}-${token.slice(6, 8)}`
96
170
  );
@@ -155,6 +229,24 @@ const readOpenCodeModelParts = (id: string): { upstreamProvider: string; slug: s
155
229
  };
156
230
  };
157
231
 
232
+ const isSupportedOpenCodeModelId = (id: string): boolean => (
233
+ readOpenCodeModelParts(id).upstreamProvider.toLowerCase() !== 'google'
234
+ );
235
+
236
+ const readOpenCodeVerboseModelId = (model: OpenCodeVerboseModel): string | null => {
237
+ const id = readOptionalString(model.id);
238
+ if (!id) {
239
+ return null;
240
+ }
241
+
242
+ if (id.includes('/')) {
243
+ return id;
244
+ }
245
+
246
+ const upstreamProvider = readOptionalString(model.providerID);
247
+ return upstreamProvider ? `${upstreamProvider}/${id}` : id;
248
+ };
249
+
158
250
  const labelForOpenCodeModelId = (id: string): string => {
159
251
  const fallbackLabel = OPENCODE_FALLBACK_MODELS.OPTIONS.find((option) => option.value === id)?.label;
160
252
  if (fallbackLabel) {
@@ -170,12 +262,90 @@ const descriptionForOpenCodeModelId = (id: string): string => {
170
262
  return upstreamProvider ? `${upstreamProvider} - ${id}` : id;
171
263
  };
172
264
 
173
- export const buildOpenCodeDefinitionFromIds = (ids: string[]): ProviderModelsDefinition => {
174
- const options: ProviderModelOption[] = ids.map((value) => ({
265
+ const readOpenCodeVariantEffort = (key: string, value: unknown): string | null => {
266
+ const variant = readObjectRecord(value);
267
+ return readOptionalString(variant?.reasoningEffort)
268
+ ?? readOptionalString(variant?.effort)
269
+ ?? key;
270
+ };
271
+
272
+ const readOpenCodeEffortValues = (
273
+ variants: OpenCodeVerboseModel['variants'],
274
+ ): NonNullable<ProviderModelOption['effort']>['values'] => {
275
+ const effortValues: NonNullable<ProviderModelOption['effort']>['values'] = [];
276
+ const seenValues = new Set<string>();
277
+
278
+ for (const [key, value] of Object.entries(variants ?? {})) {
279
+ const effort = readOpenCodeVariantEffort(key, value);
280
+ if (!effort || seenValues.has(effort)) {
281
+ continue;
282
+ }
283
+
284
+ seenValues.add(effort);
285
+ effortValues.push({ value: effort });
286
+ }
287
+
288
+ return effortValues;
289
+ };
290
+
291
+ const mapOpenCodeVerboseModel = (model: OpenCodeVerboseModel): ProviderModelOption | null => {
292
+ const value = readOpenCodeVerboseModelId(model);
293
+ if (!value || !isSupportedOpenCodeModelId(value)) {
294
+ return null;
295
+ }
296
+
297
+ const effortValues = readOpenCodeEffortValues(model.variants);
298
+
299
+ return {
175
300
  value,
176
- label: labelForOpenCodeModelId(value),
301
+ label: readOptionalString(model.name) ?? labelForOpenCodeModelId(value),
177
302
  description: descriptionForOpenCodeModelId(value),
178
- }));
303
+ effort: effortValues.length > 0
304
+ ? {
305
+ values: effortValues,
306
+ }
307
+ : undefined,
308
+ };
309
+ };
310
+
311
+ export const buildOpenCodeDefinitionFromIds = (ids: string[]): ProviderModelsDefinition => {
312
+ const options: ProviderModelOption[] = ids
313
+ .filter(isSupportedOpenCodeModelId)
314
+ .map((value) => ({
315
+ value,
316
+ label: labelForOpenCodeModelId(value),
317
+ description: descriptionForOpenCodeModelId(value),
318
+ }));
319
+
320
+ const defaultValue = options.find((option) => option.value === OPENCODE_FALLBACK_MODELS.DEFAULT)?.value
321
+ ?? options[0]?.value
322
+ ?? OPENCODE_FALLBACK_MODELS.DEFAULT;
323
+
324
+ return {
325
+ OPTIONS: options,
326
+ DEFAULT: defaultValue,
327
+ };
328
+ };
329
+
330
+ export const buildOpenCodeDefinitionFromVerboseModels = (
331
+ models: OpenCodeVerboseModel[],
332
+ ): ProviderModelsDefinition => {
333
+ const options: ProviderModelOption[] = [];
334
+ const seenValues = new Set<string>();
335
+
336
+ for (const model of models) {
337
+ const mappedModel = mapOpenCodeVerboseModel(model);
338
+ if (!mappedModel || seenValues.has(mappedModel.value)) {
339
+ continue;
340
+ }
341
+
342
+ seenValues.add(mappedModel.value);
343
+ options.push(mappedModel);
344
+ }
345
+
346
+ if (options.length === 0) {
347
+ return OPENCODE_FALLBACK_MODELS;
348
+ }
179
349
 
180
350
  const defaultValue = options.find((option) => option.value === OPENCODE_FALLBACK_MODELS.DEFAULT)?.value
181
351
  ?? options[0]?.value
@@ -214,7 +384,7 @@ const parseOpenCodeSessionModelValue = (rawModel: unknown): string | null => {
214
384
  };
215
385
 
216
386
  const runOpenCodeModelsCommand = (): Promise<string> => new Promise((resolve, reject) => {
217
- const openCodeProcess = spawnFunction('opencode', ['models'], {
387
+ const openCodeProcess = spawnFunction('opencode', ['models', '--verbose'], {
218
388
  cwd: process.cwd(),
219
389
  env: { ...process.env },
220
390
  });
@@ -273,6 +443,11 @@ export class OpenCodeProviderModels implements IProviderModels {
273
443
  async getSupportedModels(): Promise<ProviderModelsDefinition> {
274
444
  try {
275
445
  const stdout = await runOpenCodeModelsCommand();
446
+ const verboseModels = parseOpenCodeVerboseModelsStdout(stdout);
447
+ if (verboseModels.length > 0) {
448
+ return buildOpenCodeDefinitionFromVerboseModels(verboseModels);
449
+ }
450
+
276
451
  const ids = parseOpenCodeModelsStdout(stdout);
277
452
  if (ids.length === 0) {
278
453
  return OPENCODE_FALLBACK_MODELS;
@@ -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
- const nextName = existingName && existingName !== fallbackTitle
132
- ? existingName
133
- : readOptionalString(row.title) ?? this.readFirstUserText(db, sessionId);
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
- return readOptionalString(data?.text);
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 content = extractText(partData);
422
- if (content.trim()) {
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
 
@@ -285,7 +285,6 @@ const parseProvider = (value: unknown): LLMProvider => {
285
285
  normalized === 'claude'
286
286
  || normalized === 'codex'
287
287
  || normalized === 'cursor'
288
- || normalized === 'gemini'
289
288
  || normalized === 'opencode'
290
289
  ) {
291
290
  return normalized;
@@ -21,6 +21,8 @@ type ProviderCapabilities = {
21
21
  supportsPermissionRequests: boolean;
22
22
  /** Whether the token-usage endpoint has data for this provider. */
23
23
  supportsTokenUsage: boolean;
24
+ /** Whether the provider runtime can accept model-level reasoning effort. */
25
+ supportsEffort: boolean;
24
26
  };
25
27
 
26
28
  /**
@@ -38,42 +40,40 @@ const PROVIDER_CAPABILITIES: Record<LLMProvider, ProviderCapabilities> = {
38
40
  supportsAbort: true,
39
41
  supportsPermissionRequests: true,
40
42
  supportsTokenUsage: true,
43
+ supportsEffort: true,
41
44
  },
42
45
  cursor: {
43
46
  provider: 'cursor',
44
47
  permissionModes: ['default', 'acceptEdits', 'bypassPermissions', 'plan'],
45
48
  defaultPermissionMode: 'default',
46
- supportsImages: false,
49
+ supportsImages: true,
47
50
  supportsAbort: true,
48
51
  supportsPermissionRequests: false,
49
52
  supportsTokenUsage: false,
53
+ supportsEffort: false,
50
54
  },
51
55
  codex: {
52
56
  provider: 'codex',
53
57
  permissionModes: ['default', 'acceptEdits', 'bypassPermissions'],
54
58
  defaultPermissionMode: 'default',
55
- supportsImages: false,
56
- supportsAbort: true,
57
- supportsPermissionRequests: false,
58
- supportsTokenUsage: true,
59
- },
60
- gemini: {
61
- provider: 'gemini',
62
- permissionModes: ['default', 'acceptEdits', 'bypassPermissions', 'plan'],
63
- defaultPermissionMode: 'default',
64
- supportsImages: false,
59
+ supportsImages: true,
65
60
  supportsAbort: true,
66
61
  supportsPermissionRequests: false,
67
62
  supportsTokenUsage: true,
63
+ supportsEffort: true,
68
64
  },
69
65
  opencode: {
70
66
  provider: 'opencode',
71
- permissionModes: ['default'],
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'],
72
71
  defaultPermissionMode: 'default',
73
- supportsImages: false,
72
+ supportsImages: true,
74
73
  supportsAbort: true,
75
74
  supportsPermissionRequests: false,
76
75
  supportsTokenUsage: true,
76
+ supportsEffort: true,
77
77
  },
78
78
  };
79
79
 
@@ -16,8 +16,8 @@ import type {
16
16
  import { readProviderSessionActiveModelChange } from '@/shared/utils.js';
17
17
 
18
18
  export const PROVIDER_MODELS_CACHE_TTL_MS = 3 * 24 * 60 * 60 * 1000;
19
- const PROVIDER_MODELS_CACHE_VERSION = 1;
20
- const UNCACHED_PROVIDERS = new Set<LLMProvider>(['claude', 'gemini']);
19
+ const PROVIDER_MODELS_CACHE_VERSION = 2;
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' | 'gemini';
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', 'gemini']);
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 parseGeminiSessionMatches(session, runtime);
1063
+ return null;
1154
1064
  }
1155
1065
 
1156
1066
  export async function searchConversations(
@@ -21,7 +21,6 @@ export const sessionSynchronizerService = {
21
21
  claude: 0,
22
22
  codex: 0,
23
23
  cursor: 0,
24
- gemini: 0,
25
24
  opencode: 0,
26
25
  };
27
26
  const failures: string[] = [];
@@ -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