@cloudcli-ai/cloudcli 1.29.5 → 1.30.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.
Files changed (179) hide show
  1. package/README.de.md +1 -1
  2. package/README.ja.md +1 -1
  3. package/README.ko.md +1 -1
  4. package/README.md +1 -1
  5. package/README.ru.md +1 -1
  6. package/README.tr.md +252 -0
  7. package/README.zh-CN.md +1 -1
  8. package/dist/assets/index-DI22MZMb.css +32 -0
  9. package/dist/assets/index-RX-W_g_Q.js +1262 -0
  10. package/dist/index.html +2 -2
  11. package/dist-server/server/claude-sdk.js +13 -8
  12. package/dist-server/server/claude-sdk.js.map +1 -1
  13. package/dist-server/server/cursor-cli.js +7 -7
  14. package/dist-server/server/cursor-cli.js.map +1 -1
  15. package/dist-server/server/gemini-cli.js +5 -5
  16. package/dist-server/server/gemini-cli.js.map +1 -1
  17. package/dist-server/server/gemini-response-handler.js +2 -2
  18. package/dist-server/server/gemini-response-handler.js.map +1 -1
  19. package/dist-server/server/index.js +27 -25
  20. package/dist-server/server/index.js.map +1 -1
  21. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +103 -0
  22. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -0
  23. package/dist-server/server/modules/providers/list/claude/claude-mcp.provider.js +103 -0
  24. package/dist-server/server/modules/providers/list/claude/claude-mcp.provider.js.map +1 -0
  25. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +266 -0
  26. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -0
  27. package/dist-server/server/modules/providers/list/claude/claude.provider.js +13 -0
  28. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -0
  29. package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js +84 -0
  30. package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js.map +1 -0
  31. package/dist-server/server/modules/providers/list/codex/codex-mcp.provider.js +107 -0
  32. package/dist-server/server/modules/providers/list/codex/codex-mcp.provider.js.map +1 -0
  33. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +282 -0
  34. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -0
  35. package/dist-server/server/modules/providers/list/codex/codex.provider.js +13 -0
  36. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -0
  37. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +118 -0
  38. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -0
  39. package/dist-server/server/modules/providers/list/cursor/cursor-mcp.provider.js +80 -0
  40. package/dist-server/server/modules/providers/list/cursor/cursor-mcp.provider.js.map +1 -0
  41. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +369 -0
  42. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -0
  43. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +13 -0
  44. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -0
  45. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +131 -0
  46. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +1 -0
  47. package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js +82 -0
  48. package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js.map +1 -0
  49. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +207 -0
  50. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -0
  51. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +13 -0
  52. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +1 -0
  53. package/dist-server/server/modules/providers/provider.registry.js +31 -0
  54. package/dist-server/server/modules/providers/provider.registry.js.map +1 -0
  55. package/dist-server/server/modules/providers/provider.routes.js +159 -0
  56. package/dist-server/server/modules/providers/provider.routes.js.map +1 -0
  57. package/dist-server/server/modules/providers/services/mcp.service.js +69 -0
  58. package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -0
  59. package/dist-server/server/modules/providers/services/provider-auth.service.js +25 -0
  60. package/dist-server/server/modules/providers/services/provider-auth.service.js.map +1 -0
  61. package/dist-server/server/modules/providers/services/sessions.service.js +29 -0
  62. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -0
  63. package/dist-server/server/modules/providers/shared/base/abstract.provider.js +14 -0
  64. package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -0
  65. package/dist-server/server/modules/providers/shared/mcp/mcp.provider.js +102 -0
  66. package/dist-server/server/modules/providers/shared/mcp/mcp.provider.js.map +1 -0
  67. package/dist-server/server/modules/providers/tests/mcp.test.js +250 -0
  68. package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -0
  69. package/dist-server/server/openai-codex.js +5 -5
  70. package/dist-server/server/openai-codex.js.map +1 -1
  71. package/dist-server/server/routes/codex.js +2 -278
  72. package/dist-server/server/routes/codex.js.map +1 -1
  73. package/dist-server/server/routes/commands.js +0 -44
  74. package/dist-server/server/routes/commands.js.map +1 -1
  75. package/dist-server/server/routes/cursor.js +9 -489
  76. package/dist-server/server/routes/cursor.js.map +1 -1
  77. package/dist-server/server/routes/mcp-utils.js +1 -18
  78. package/dist-server/server/routes/mcp-utils.js.map +1 -1
  79. package/dist-server/server/routes/messages.js +6 -6
  80. package/dist-server/server/routes/messages.js.map +1 -1
  81. package/dist-server/server/routes/settings.js +10 -0
  82. package/dist-server/server/routes/settings.js.map +1 -1
  83. package/dist-server/server/routes/taskmaster.js +0 -459
  84. package/dist-server/server/routes/taskmaster.js.map +1 -1
  85. package/dist-server/server/shared/interfaces.js +2 -0
  86. package/dist-server/server/shared/interfaces.js.map +1 -0
  87. package/dist-server/server/shared/types.js +3 -0
  88. package/dist-server/server/shared/types.js.map +1 -0
  89. package/dist-server/server/shared/utils.js +150 -0
  90. package/dist-server/server/shared/utils.js.map +1 -0
  91. package/dist-server/server/utils/mcp-detector.js +0 -49
  92. package/dist-server/server/utils/mcp-detector.js.map +1 -1
  93. package/dist-server/shared/modelConstants.js +1 -0
  94. package/dist-server/shared/modelConstants.js.map +1 -1
  95. package/package.json +5 -2
  96. package/server/claude-sdk.js +14 -8
  97. package/server/cursor-cli.js +7 -7
  98. package/server/gemini-cli.js +5 -5
  99. package/server/gemini-response-handler.js +2 -2
  100. package/server/index.js +33 -29
  101. package/server/modules/providers/list/claude/claude-auth.provider.ts +123 -0
  102. package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -0
  103. package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -0
  104. package/server/modules/providers/list/claude/claude.provider.ts +15 -0
  105. package/server/modules/providers/list/codex/codex-auth.provider.ts +100 -0
  106. package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -0
  107. package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -0
  108. package/server/modules/providers/list/codex/codex.provider.ts +15 -0
  109. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -0
  110. package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -0
  111. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -0
  112. package/server/modules/providers/list/cursor/cursor.provider.ts +15 -0
  113. package/server/modules/providers/list/gemini/gemini-auth.provider.ts +151 -0
  114. package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -0
  115. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -0
  116. package/server/modules/providers/list/gemini/gemini.provider.ts +15 -0
  117. package/server/modules/providers/provider.registry.ts +36 -0
  118. package/server/modules/providers/provider.routes.ts +217 -0
  119. package/server/modules/providers/services/mcp.service.ts +94 -0
  120. package/server/modules/providers/services/provider-auth.service.ts +26 -0
  121. package/server/modules/providers/services/sessions.service.ts +45 -0
  122. package/server/modules/providers/shared/base/abstract.provider.ts +20 -0
  123. package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -0
  124. package/server/modules/providers/tests/mcp.test.ts +293 -0
  125. package/server/openai-codex.js +5 -5
  126. package/server/routes/codex.js +2 -312
  127. package/server/routes/commands.js +0 -49
  128. package/server/routes/cursor.js +15 -527
  129. package/server/routes/mcp-utils.js +2 -19
  130. package/server/routes/messages.js +6 -6
  131. package/server/routes/settings.js +10 -0
  132. package/server/routes/taskmaster.js +0 -492
  133. package/server/shared/interfaces.ts +54 -0
  134. package/server/shared/types.ts +172 -0
  135. package/server/shared/utils.ts +193 -0
  136. package/server/tsconfig.json +6 -3
  137. package/server/utils/mcp-detector.js +0 -51
  138. package/shared/modelConstants.js +1 -0
  139. package/dist/assets/index-BBAE1OJ_.css +0 -32
  140. package/dist/assets/index-Ci1GOyV7.js +0 -1262
  141. package/dist-server/server/providers/claude/adapter.js +0 -266
  142. package/dist-server/server/providers/claude/adapter.js.map +0 -1
  143. package/dist-server/server/providers/claude/status.js +0 -121
  144. package/dist-server/server/providers/claude/status.js.map +0 -1
  145. package/dist-server/server/providers/codex/adapter.js +0 -232
  146. package/dist-server/server/providers/codex/adapter.js.map +0 -1
  147. package/dist-server/server/providers/codex/status.js +0 -70
  148. package/dist-server/server/providers/codex/status.js.map +0 -1
  149. package/dist-server/server/providers/cursor/adapter.js +0 -342
  150. package/dist-server/server/providers/cursor/adapter.js.map +0 -1
  151. package/dist-server/server/providers/cursor/status.js +0 -118
  152. package/dist-server/server/providers/cursor/status.js.map +0 -1
  153. package/dist-server/server/providers/gemini/adapter.js +0 -174
  154. package/dist-server/server/providers/gemini/adapter.js.map +0 -1
  155. package/dist-server/server/providers/gemini/status.js +0 -104
  156. package/dist-server/server/providers/gemini/status.js.map +0 -1
  157. package/dist-server/server/providers/registry.js +0 -58
  158. package/dist-server/server/providers/registry.js.map +0 -1
  159. package/dist-server/server/providers/types.js +0 -117
  160. package/dist-server/server/providers/types.js.map +0 -1
  161. package/dist-server/server/providers/utils.js +0 -28
  162. package/dist-server/server/providers/utils.js.map +0 -1
  163. package/dist-server/server/routes/cli-auth.js +0 -25
  164. package/dist-server/server/routes/cli-auth.js.map +0 -1
  165. package/dist-server/server/routes/mcp.js +0 -480
  166. package/dist-server/server/routes/mcp.js.map +0 -1
  167. package/server/providers/claude/adapter.js +0 -278
  168. package/server/providers/claude/status.js +0 -136
  169. package/server/providers/codex/adapter.js +0 -248
  170. package/server/providers/codex/status.js +0 -78
  171. package/server/providers/cursor/adapter.js +0 -348
  172. package/server/providers/cursor/status.js +0 -128
  173. package/server/providers/gemini/adapter.js +0 -186
  174. package/server/providers/gemini/status.js +0 -111
  175. package/server/providers/registry.js +0 -67
  176. package/server/providers/types.js +0 -132
  177. package/server/providers/utils.js +0 -29
  178. package/server/routes/cli-auth.js +0 -27
  179. package/server/routes/mcp.js +0 -552
@@ -0,0 +1,151 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import spawn from 'cross-spawn';
6
+
7
+ import type { IProviderAuth } from '@/shared/interfaces.js';
8
+ import type { ProviderAuthStatus } from '@/shared/types.js';
9
+ import { readObjectRecord, readOptionalString } from '@/shared/utils.js';
10
+
11
+ type GeminiCredentialsStatus = {
12
+ authenticated: boolean;
13
+ email: string | null;
14
+ method: string | null;
15
+ error?: string;
16
+ };
17
+
18
+ export class GeminiProviderAuth implements IProviderAuth {
19
+ /**
20
+ * Checks whether the Gemini CLI is available on this host.
21
+ */
22
+ private checkInstalled(): boolean {
23
+ const cliPath = process.env.GEMINI_PATH || 'gemini';
24
+ try {
25
+ spawn.sync(cliPath, ['--version'], { stdio: 'ignore', timeout: 5000 });
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Returns Gemini CLI installation and credential status.
34
+ */
35
+ async getStatus(): Promise<ProviderAuthStatus> {
36
+ const installed = this.checkInstalled();
37
+
38
+ if (!installed) {
39
+ return {
40
+ installed,
41
+ provider: 'gemini',
42
+ authenticated: false,
43
+ email: null,
44
+ method: null,
45
+ error: 'Gemini CLI is not installed',
46
+ };
47
+ }
48
+
49
+ const credentials = await this.checkCredentials();
50
+
51
+ return {
52
+ installed,
53
+ provider: 'gemini',
54
+ authenticated: credentials.authenticated,
55
+ email: credentials.email,
56
+ method: credentials.method,
57
+ error: credentials.authenticated ? undefined : credentials.error || 'Not authenticated',
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Checks Gemini credentials from API key env vars or local OAuth credential files.
63
+ */
64
+ private async checkCredentials(): Promise<GeminiCredentialsStatus> {
65
+ if (process.env.GEMINI_API_KEY?.trim()) {
66
+ return { authenticated: true, email: 'API Key Auth', method: 'api_key' };
67
+ }
68
+
69
+ try {
70
+ const credsPath = path.join(os.homedir(), '.gemini', 'oauth_creds.json');
71
+ const content = await readFile(credsPath, 'utf8');
72
+ const creds = readObjectRecord(JSON.parse(content)) ?? {};
73
+ const accessToken = readOptionalString(creds.access_token);
74
+
75
+ if (!accessToken) {
76
+ return {
77
+ authenticated: false,
78
+ email: null,
79
+ method: null,
80
+ error: 'No valid tokens found in oauth_creds',
81
+ };
82
+ }
83
+
84
+ const refreshToken = readOptionalString(creds.refresh_token);
85
+ const tokenInfo = await this.getTokenInfoEmail(accessToken);
86
+ if (tokenInfo.valid) {
87
+ return {
88
+ authenticated: true,
89
+ email: tokenInfo.email || 'OAuth Session',
90
+ method: 'credentials_file',
91
+ };
92
+ }
93
+
94
+ if (!refreshToken) {
95
+ return {
96
+ authenticated: false,
97
+ email: null,
98
+ method: 'credentials_file',
99
+ error: 'Access token invalid and no refresh token found',
100
+ };
101
+ }
102
+
103
+ return {
104
+ authenticated: true,
105
+ email: await this.getActiveAccountEmail() || 'OAuth Session',
106
+ method: 'credentials_file',
107
+ };
108
+ } catch {
109
+ return {
110
+ authenticated: false,
111
+ email: null,
112
+ method: null,
113
+ error: 'Gemini CLI not configured',
114
+ };
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Validates a Gemini OAuth access token and returns an email when Google reports one.
120
+ */
121
+ private async getTokenInfoEmail(accessToken: string): Promise<{ valid: boolean; email: string | null }> {
122
+ try {
123
+ const tokenRes = await fetch(`https://oauth2.googleapis.com/tokeninfo?access_token=${accessToken}`);
124
+ if (!tokenRes.ok) {
125
+ return { valid: false, email: null };
126
+ }
127
+
128
+ const tokenInfo = readObjectRecord(await tokenRes.json());
129
+ return {
130
+ valid: true,
131
+ email: readOptionalString(tokenInfo?.email) ?? null,
132
+ };
133
+ } catch {
134
+ return { valid: false, email: null };
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Reads Gemini's active local Google account as an offline fallback for display.
140
+ */
141
+ private async getActiveAccountEmail(): Promise<string | null> {
142
+ try {
143
+ const accPath = path.join(os.homedir(), '.gemini', 'google_accounts.json');
144
+ const accContent = await readFile(accPath, 'utf8');
145
+ const accounts = readObjectRecord(JSON.parse(accContent));
146
+ return readOptionalString(accounts?.active) ?? null;
147
+ } catch {
148
+ return null;
149
+ }
150
+ }
151
+ }
@@ -0,0 +1,110 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+
4
+ import { McpProvider } from '@/modules/providers/shared/mcp/mcp.provider.js';
5
+ import type { McpScope, ProviderMcpServer, UpsertProviderMcpServerInput } from '@/shared/types.js';
6
+ import {
7
+ AppError,
8
+ readJsonConfig,
9
+ readObjectRecord,
10
+ readOptionalString,
11
+ readStringArray,
12
+ readStringRecord,
13
+ writeJsonConfig,
14
+ } from '@/shared/utils.js';
15
+
16
+ export class GeminiMcpProvider extends McpProvider {
17
+ constructor() {
18
+ super('gemini', ['user', 'project'], ['stdio', 'http', 'sse']);
19
+ }
20
+
21
+ protected async readScopedServers(scope: McpScope, workspacePath: string): Promise<Record<string, unknown>> {
22
+ const filePath = scope === 'user'
23
+ ? path.join(os.homedir(), '.gemini', 'settings.json')
24
+ : path.join(workspacePath, '.gemini', 'settings.json');
25
+ const config = await readJsonConfig(filePath);
26
+ return readObjectRecord(config.mcpServers) ?? {};
27
+ }
28
+
29
+ protected async writeScopedServers(
30
+ scope: McpScope,
31
+ workspacePath: string,
32
+ servers: Record<string, unknown>,
33
+ ): Promise<void> {
34
+ const filePath = scope === 'user'
35
+ ? path.join(os.homedir(), '.gemini', 'settings.json')
36
+ : path.join(workspacePath, '.gemini', 'settings.json');
37
+ const config = await readJsonConfig(filePath);
38
+ config.mcpServers = servers;
39
+ await writeJsonConfig(filePath, config);
40
+ }
41
+
42
+ protected buildServerConfig(input: UpsertProviderMcpServerInput): Record<string, unknown> {
43
+ if (input.transport === 'stdio') {
44
+ if (!input.command?.trim()) {
45
+ throw new AppError('command is required for stdio MCP servers.', {
46
+ code: 'MCP_COMMAND_REQUIRED',
47
+ statusCode: 400,
48
+ });
49
+ }
50
+
51
+ return {
52
+ command: input.command,
53
+ args: input.args ?? [],
54
+ env: input.env ?? {},
55
+ cwd: input.cwd,
56
+ };
57
+ }
58
+
59
+ if (!input.url?.trim()) {
60
+ throw new AppError('url is required for http/sse MCP servers.', {
61
+ code: 'MCP_URL_REQUIRED',
62
+ statusCode: 400,
63
+ });
64
+ }
65
+
66
+ return {
67
+ type: input.transport,
68
+ url: input.url,
69
+ headers: input.headers ?? {},
70
+ };
71
+ }
72
+
73
+ protected normalizeServerConfig(
74
+ scope: McpScope,
75
+ name: string,
76
+ rawConfig: unknown,
77
+ ): ProviderMcpServer | null {
78
+ if (!rawConfig || typeof rawConfig !== 'object') {
79
+ return null;
80
+ }
81
+
82
+ const config = rawConfig as Record<string, unknown>;
83
+ if (typeof config.command === 'string') {
84
+ return {
85
+ provider: 'gemini',
86
+ name,
87
+ scope,
88
+ transport: 'stdio',
89
+ command: config.command,
90
+ args: readStringArray(config.args),
91
+ env: readStringRecord(config.env),
92
+ cwd: readOptionalString(config.cwd),
93
+ };
94
+ }
95
+
96
+ if (typeof config.url === 'string') {
97
+ const transport = readOptionalString(config.type) === 'sse' ? 'sse' : 'http';
98
+ return {
99
+ provider: 'gemini',
100
+ name,
101
+ scope,
102
+ transport,
103
+ url: config.url,
104
+ headers: readStringRecord(config.headers),
105
+ };
106
+ }
107
+
108
+ return null;
109
+ }
110
+ }
@@ -0,0 +1,227 @@
1
+ import sessionManager from '@/sessionManager.js';
2
+ import { getGeminiCliSessionMessages } from '@/projects.js';
3
+ import type { IProviderSessions } from '@/shared/interfaces.js';
4
+ import type { AnyRecord, FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
5
+ import { createNormalizedMessage, generateMessageId, readObjectRecord } from '@/shared/utils.js';
6
+
7
+ const PROVIDER = 'gemini';
8
+
9
+ export class GeminiSessionsProvider implements IProviderSessions {
10
+ /**
11
+ * Normalizes live Gemini stream-json events into the shared message shape.
12
+ *
13
+ * Gemini history uses a different session file shape, so fetchHistory handles
14
+ * that separately after loading raw persisted messages.
15
+ */
16
+ normalizeMessage(rawMessage: unknown, sessionId: string | null): NormalizedMessage[] {
17
+ const raw = readObjectRecord(rawMessage);
18
+ if (!raw) {
19
+ return [];
20
+ }
21
+
22
+ const ts = raw.timestamp || new Date().toISOString();
23
+ const baseId = raw.uuid || generateMessageId('gemini');
24
+
25
+ if (raw.type === 'message' && raw.role === 'assistant') {
26
+ const content = raw.content || '';
27
+ const messages: NormalizedMessage[] = [];
28
+ if (content) {
29
+ messages.push(createNormalizedMessage({
30
+ id: baseId,
31
+ sessionId,
32
+ timestamp: ts,
33
+ provider: PROVIDER,
34
+ kind: 'stream_delta',
35
+ content,
36
+ }));
37
+ }
38
+ if (raw.delta !== true) {
39
+ messages.push(createNormalizedMessage({
40
+ sessionId,
41
+ timestamp: ts,
42
+ provider: PROVIDER,
43
+ kind: 'stream_end',
44
+ }));
45
+ }
46
+ return messages;
47
+ }
48
+
49
+ if (raw.type === 'tool_use') {
50
+ return [createNormalizedMessage({
51
+ id: baseId,
52
+ sessionId,
53
+ timestamp: ts,
54
+ provider: PROVIDER,
55
+ kind: 'tool_use',
56
+ toolName: raw.tool_name,
57
+ toolInput: raw.parameters || {},
58
+ toolId: raw.tool_id || baseId,
59
+ })];
60
+ }
61
+
62
+ if (raw.type === 'tool_result') {
63
+ return [createNormalizedMessage({
64
+ id: baseId,
65
+ sessionId,
66
+ timestamp: ts,
67
+ provider: PROVIDER,
68
+ kind: 'tool_result',
69
+ toolId: raw.tool_id || '',
70
+ content: raw.output === undefined ? '' : String(raw.output),
71
+ isError: raw.status === 'error',
72
+ })];
73
+ }
74
+
75
+ if (raw.type === 'result') {
76
+ const messages = [createNormalizedMessage({
77
+ sessionId,
78
+ timestamp: ts,
79
+ provider: PROVIDER,
80
+ kind: 'stream_end',
81
+ })];
82
+ if (raw.stats?.total_tokens) {
83
+ messages.push(createNormalizedMessage({
84
+ sessionId,
85
+ timestamp: ts,
86
+ provider: PROVIDER,
87
+ kind: 'status',
88
+ text: 'Complete',
89
+ tokens: raw.stats.total_tokens,
90
+ canInterrupt: false,
91
+ }));
92
+ }
93
+ return messages;
94
+ }
95
+
96
+ if (raw.type === 'error') {
97
+ return [createNormalizedMessage({
98
+ id: baseId,
99
+ sessionId,
100
+ timestamp: ts,
101
+ provider: PROVIDER,
102
+ kind: 'error',
103
+ content: raw.error || raw.message || 'Unknown Gemini streaming error',
104
+ })];
105
+ }
106
+
107
+ return [];
108
+ }
109
+
110
+ /**
111
+ * Loads Gemini history from the in-memory session manager first, then falls
112
+ * back to Gemini CLI session files on disk.
113
+ */
114
+ async fetchHistory(
115
+ sessionId: string,
116
+ options: FetchHistoryOptions = {},
117
+ ): Promise<FetchHistoryResult> {
118
+ const { limit = null, offset = 0 } = options;
119
+
120
+ let rawMessages: AnyRecord[];
121
+ try {
122
+ rawMessages = sessionManager.getSessionMessages(sessionId) as AnyRecord[];
123
+
124
+ if (rawMessages.length === 0) {
125
+ rawMessages = await getGeminiCliSessionMessages(sessionId) as AnyRecord[];
126
+ }
127
+ } catch (error) {
128
+ const message = error instanceof Error ? error.message : String(error);
129
+ console.warn(`[GeminiProvider] Failed to load session ${sessionId}:`, message);
130
+ return { messages: [], total: 0, hasMore: false, offset: 0, limit: null };
131
+ }
132
+
133
+ const normalized: NormalizedMessage[] = [];
134
+ for (let i = 0; i < rawMessages.length; i++) {
135
+ const raw = rawMessages[i];
136
+ const ts = raw.timestamp || new Date().toISOString();
137
+ const baseId = raw.uuid || generateMessageId('gemini');
138
+
139
+ const role = raw.message?.role || raw.role;
140
+ const content = raw.message?.content || raw.content;
141
+
142
+ if (!role || !content) {
143
+ continue;
144
+ }
145
+
146
+ const normalizedRole = role === 'user' ? 'user' : 'assistant';
147
+
148
+ if (Array.isArray(content)) {
149
+ for (let partIdx = 0; partIdx < content.length; partIdx++) {
150
+ const part = content[partIdx];
151
+ if (part.type === 'text' && part.text) {
152
+ normalized.push(createNormalizedMessage({
153
+ id: `${baseId}_${partIdx}`,
154
+ sessionId,
155
+ timestamp: ts,
156
+ provider: PROVIDER,
157
+ kind: 'text',
158
+ role: normalizedRole,
159
+ content: part.text,
160
+ }));
161
+ } else if (part.type === 'tool_use') {
162
+ normalized.push(createNormalizedMessage({
163
+ id: `${baseId}_${partIdx}`,
164
+ sessionId,
165
+ timestamp: ts,
166
+ provider: PROVIDER,
167
+ kind: 'tool_use',
168
+ toolName: part.name,
169
+ toolInput: part.input,
170
+ toolId: part.id || generateMessageId('gemini_tool'),
171
+ }));
172
+ } else if (part.type === 'tool_result') {
173
+ normalized.push(createNormalizedMessage({
174
+ id: `${baseId}_${partIdx}`,
175
+ sessionId,
176
+ timestamp: ts,
177
+ provider: PROVIDER,
178
+ kind: 'tool_result',
179
+ toolId: part.tool_use_id || '',
180
+ content: part.content === undefined ? '' : String(part.content),
181
+ isError: Boolean(part.is_error),
182
+ }));
183
+ }
184
+ }
185
+ } else if (typeof content === 'string' && content.trim()) {
186
+ normalized.push(createNormalizedMessage({
187
+ id: baseId,
188
+ sessionId,
189
+ timestamp: ts,
190
+ provider: PROVIDER,
191
+ kind: 'text',
192
+ role: normalizedRole,
193
+ content,
194
+ }));
195
+ }
196
+ }
197
+
198
+ const toolResultMap = new Map<string, NormalizedMessage>();
199
+ for (const msg of normalized) {
200
+ if (msg.kind === 'tool_result' && msg.toolId) {
201
+ toolResultMap.set(msg.toolId, msg);
202
+ }
203
+ }
204
+ for (const msg of normalized) {
205
+ if (msg.kind === 'tool_use' && msg.toolId && toolResultMap.has(msg.toolId)) {
206
+ const toolResult = toolResultMap.get(msg.toolId);
207
+ if (toolResult) {
208
+ msg.toolResult = { content: toolResult.content, isError: toolResult.isError };
209
+ }
210
+ }
211
+ }
212
+
213
+ const start = Math.max(0, offset);
214
+ const pageLimit = limit === null ? null : Math.max(0, limit);
215
+ const messages = pageLimit === null
216
+ ? normalized.slice(start)
217
+ : normalized.slice(start, start + pageLimit);
218
+
219
+ return {
220
+ messages,
221
+ total: normalized.length,
222
+ hasMore: pageLimit === null ? false : start + pageLimit < normalized.length,
223
+ offset: start,
224
+ limit: pageLimit,
225
+ };
226
+ }
227
+ }
@@ -0,0 +1,15 @@
1
+ import { AbstractProvider } from '@/modules/providers/shared/base/abstract.provider.js';
2
+ import { GeminiProviderAuth } from '@/modules/providers/list/gemini/gemini-auth.provider.js';
3
+ import { GeminiMcpProvider } from '@/modules/providers/list/gemini/gemini-mcp.provider.js';
4
+ import { GeminiSessionsProvider } from '@/modules/providers/list/gemini/gemini-sessions.provider.js';
5
+ import type { IProviderAuth, IProviderSessions } from '@/shared/interfaces.js';
6
+
7
+ export class GeminiProvider extends AbstractProvider {
8
+ readonly mcp = new GeminiMcpProvider();
9
+ readonly auth: IProviderAuth = new GeminiProviderAuth();
10
+ readonly sessions: IProviderSessions = new GeminiSessionsProvider();
11
+
12
+ constructor() {
13
+ super('gemini');
14
+ }
15
+ }
@@ -0,0 +1,36 @@
1
+ import { ClaudeProvider } from '@/modules/providers/list/claude/claude.provider.js';
2
+ import { CodexProvider } from '@/modules/providers/list/codex/codex.provider.js';
3
+ import { CursorProvider } from '@/modules/providers/list/cursor/cursor.provider.js';
4
+ import { GeminiProvider } from '@/modules/providers/list/gemini/gemini.provider.js';
5
+ import type { IProvider } from '@/shared/interfaces.js';
6
+ import type { LLMProvider } from '@/shared/types.js';
7
+ import { AppError } from '@/shared/utils.js';
8
+
9
+ const providers: Record<LLMProvider, IProvider> = {
10
+ claude: new ClaudeProvider(),
11
+ codex: new CodexProvider(),
12
+ cursor: new CursorProvider(),
13
+ gemini: new GeminiProvider(),
14
+ };
15
+
16
+ /**
17
+ * Central registry for resolving concrete provider implementations by id.
18
+ */
19
+ export const providerRegistry = {
20
+ listProviders(): IProvider[] {
21
+ return Object.values(providers);
22
+ },
23
+
24
+ resolveProvider(provider: string): IProvider {
25
+ const key = provider as LLMProvider;
26
+ const resolvedProvider = providers[key];
27
+ if (!resolvedProvider) {
28
+ throw new AppError(`Unsupported provider "${provider}".`, {
29
+ code: 'UNSUPPORTED_PROVIDER',
30
+ statusCode: 400,
31
+ });
32
+ }
33
+
34
+ return resolvedProvider;
35
+ },
36
+ };