@siteboon/claude-code-ui 1.19.1 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/assets/index-Cxnz_sny.css +32 -0
- package/dist/assets/index-DN2ZJcRJ.js +1381 -0
- package/dist/assets/{vendor-codemirror-l-lAmaJ1.js → vendor-codemirror-BMLq5tLB.js} +8 -8
- package/dist/assets/{vendor-xterm-DfaPXD3y.js → vendor-xterm-CJZjLICi.js} +10 -10
- package/dist/icons/gemini-ai-icon.svg +1 -0
- package/dist/index.html +4 -4
- package/package.json +4 -3
- package/server/claude-sdk.js +3 -0
- package/server/database/db.js +18 -2
- package/server/gemini-cli.js +455 -0
- package/server/gemini-response-handler.js +140 -0
- package/server/index.js +311 -227
- package/server/load-env.js +5 -0
- package/server/projects.js +292 -275
- package/server/routes/agent.js +15 -4
- package/server/routes/auth.js +3 -3
- package/server/routes/cli-auth.js +114 -0
- package/server/routes/gemini.js +46 -0
- package/server/sessionManager.js +226 -0
- package/shared/modelConstants.js +19 -0
- package/dist/assets/index-0DqtvI36.js +0 -1413
- package/dist/assets/index-BPHfv_yU.css +0 -32
- package/server/database/auth.db +0 -0
package/server/routes/agent.js
CHANGED
|
@@ -9,6 +9,7 @@ import { addProjectManually } from '../projects.js';
|
|
|
9
9
|
import { queryClaudeSDK } from '../claude-sdk.js';
|
|
10
10
|
import { spawnCursor } from '../cursor-cli.js';
|
|
11
11
|
import { queryCodex } from '../openai-codex.js';
|
|
12
|
+
import { spawnGemini } from '../gemini-cli.js';
|
|
12
13
|
import { Octokit } from '@octokit/rest';
|
|
13
14
|
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
|
|
14
15
|
import { IS_PLATFORM } from '../constants/config.js';
|
|
@@ -629,7 +630,7 @@ class ResponseCollector {
|
|
|
629
630
|
* - Source for auto-generated branch names (if createBranch=true and no branchName)
|
|
630
631
|
* - Fallback for PR title if no commits are made
|
|
631
632
|
*
|
|
632
|
-
* @param {string} provider - (Optional) AI provider to use. Options: 'claude' | 'cursor'
|
|
633
|
+
* @param {string} provider - (Optional) AI provider to use. Options: 'claude' | 'cursor' | 'codex' | 'gemini'
|
|
633
634
|
* Default: 'claude'
|
|
634
635
|
*
|
|
635
636
|
* @param {boolean} stream - (Optional) Enable Server-Sent Events (SSE) streaming for real-time updates.
|
|
@@ -747,7 +748,7 @@ class ResponseCollector {
|
|
|
747
748
|
* Input Validations (400 Bad Request):
|
|
748
749
|
* - Either githubUrl OR projectPath must be provided (not neither)
|
|
749
750
|
* - message must be non-empty string
|
|
750
|
-
* - provider must be 'claude' or '
|
|
751
|
+
* - provider must be 'claude', 'cursor', 'codex', or 'gemini'
|
|
751
752
|
* - createBranch/createPR requires githubUrl OR projectPath (not neither)
|
|
752
753
|
* - branchName must pass Git naming rules (if provided)
|
|
753
754
|
*
|
|
@@ -855,8 +856,8 @@ router.post('/', validateExternalApiKey, async (req, res) => {
|
|
|
855
856
|
return res.status(400).json({ error: 'message is required' });
|
|
856
857
|
}
|
|
857
858
|
|
|
858
|
-
if (!['claude', 'cursor', 'codex'].includes(provider)) {
|
|
859
|
-
return res.status(400).json({ error: 'provider must be "claude", "cursor", or "
|
|
859
|
+
if (!['claude', 'cursor', 'codex', 'gemini'].includes(provider)) {
|
|
860
|
+
return res.status(400).json({ error: 'provider must be "claude", "cursor", "codex", or "gemini"' });
|
|
860
861
|
}
|
|
861
862
|
|
|
862
863
|
// Validate GitHub branch/PR creation requirements
|
|
@@ -971,6 +972,16 @@ router.post('/', validateExternalApiKey, async (req, res) => {
|
|
|
971
972
|
model: model || CODEX_MODELS.DEFAULT,
|
|
972
973
|
permissionMode: 'bypassPermissions'
|
|
973
974
|
}, writer);
|
|
975
|
+
} else if (provider === 'gemini') {
|
|
976
|
+
console.log('✨ Starting Gemini CLI session');
|
|
977
|
+
|
|
978
|
+
await spawnGemini(message.trim(), {
|
|
979
|
+
projectPath: finalProjectPath,
|
|
980
|
+
cwd: finalProjectPath,
|
|
981
|
+
sessionId: null,
|
|
982
|
+
model: model,
|
|
983
|
+
skipPermissions: true // CLI mode bypasses permissions
|
|
984
|
+
}, writer);
|
|
974
985
|
}
|
|
975
986
|
|
|
976
987
|
// Handle GitHub branch and PR creation after successful agent completion
|
package/server/routes/auth.js
CHANGED
|
@@ -53,11 +53,11 @@ router.post('/register', async (req, res) => {
|
|
|
53
53
|
// Generate token
|
|
54
54
|
const token = generateToken(user);
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
db.prepare('COMMIT').run();
|
|
57
|
+
|
|
58
|
+
// Update last login (non-fatal, outside transaction)
|
|
57
59
|
userDb.updateLastLogin(user.id);
|
|
58
60
|
|
|
59
|
-
db.prepare('COMMIT').run();
|
|
60
|
-
|
|
61
61
|
res.json({
|
|
62
62
|
success: true,
|
|
63
63
|
user: { id: user.id, username: user.username },
|
|
@@ -74,6 +74,46 @@ router.get('/codex/status', async (req, res) => {
|
|
|
74
74
|
}
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
router.get('/gemini/status', async (req, res) => {
|
|
78
|
+
try {
|
|
79
|
+
const result = await checkGeminiCredentials();
|
|
80
|
+
|
|
81
|
+
res.json({
|
|
82
|
+
authenticated: result.authenticated,
|
|
83
|
+
email: result.email,
|
|
84
|
+
error: result.error
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Error checking Gemini auth status:', error);
|
|
89
|
+
res.status(500).json({
|
|
90
|
+
authenticated: false,
|
|
91
|
+
email: null,
|
|
92
|
+
error: error.message
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Checks Claude authentication credentials using two methods with priority order:
|
|
99
|
+
*
|
|
100
|
+
* Priority 1: ANTHROPIC_API_KEY environment variable
|
|
101
|
+
* Priority 2: ~/.claude/.credentials.json OAuth tokens
|
|
102
|
+
*
|
|
103
|
+
* The Claude Agent SDK prioritizes environment variables over authenticated subscriptions.
|
|
104
|
+
* This matching behavior ensures consistency with how the SDK authenticates.
|
|
105
|
+
*
|
|
106
|
+
* References:
|
|
107
|
+
* - https://support.claude.com/en/articles/12304248-managing-api-key-environment-variables-in-claude-code
|
|
108
|
+
* "Claude Code prioritizes environment variable API keys over authenticated subscriptions"
|
|
109
|
+
* - https://platform.claude.com/docs/en/agent-sdk/overview
|
|
110
|
+
* SDK authentication documentation
|
|
111
|
+
*
|
|
112
|
+
* @returns {Promise<Object>} Authentication status with { authenticated, email, method }
|
|
113
|
+
* - authenticated: boolean indicating if valid credentials exist
|
|
114
|
+
* - email: user email or auth method identifier
|
|
115
|
+
* - method: 'api_key' for env var, 'credentials_file' for OAuth tokens
|
|
116
|
+
*/
|
|
77
117
|
async function checkClaudeCredentials() {
|
|
78
118
|
try {
|
|
79
119
|
const credPath = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
@@ -260,4 +300,78 @@ async function checkCodexCredentials() {
|
|
|
260
300
|
}
|
|
261
301
|
}
|
|
262
302
|
|
|
303
|
+
async function checkGeminiCredentials() {
|
|
304
|
+
if (process.env.GEMINI_API_KEY && process.env.GEMINI_API_KEY.trim()) {
|
|
305
|
+
return {
|
|
306
|
+
authenticated: true,
|
|
307
|
+
email: 'API Key Auth'
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const credsPath = path.join(os.homedir(), '.gemini', 'oauth_creds.json');
|
|
313
|
+
const content = await fs.readFile(credsPath, 'utf8');
|
|
314
|
+
const creds = JSON.parse(content);
|
|
315
|
+
|
|
316
|
+
if (creds.access_token) {
|
|
317
|
+
let email = 'OAuth Session';
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
// Validate token against Google API
|
|
321
|
+
const tokenRes = await fetch(`https://oauth2.googleapis.com/tokeninfo?access_token=${creds.access_token}`);
|
|
322
|
+
if (tokenRes.ok) {
|
|
323
|
+
const tokenInfo = await tokenRes.json();
|
|
324
|
+
if (tokenInfo.email) {
|
|
325
|
+
email = tokenInfo.email;
|
|
326
|
+
}
|
|
327
|
+
} else if (!creds.refresh_token) {
|
|
328
|
+
// Token invalid and no refresh token available
|
|
329
|
+
return {
|
|
330
|
+
authenticated: false,
|
|
331
|
+
email: null,
|
|
332
|
+
error: 'Access token invalid and no refresh token found'
|
|
333
|
+
};
|
|
334
|
+
} else {
|
|
335
|
+
// Token might be expired but we have a refresh token, so CLI will refresh it
|
|
336
|
+
try {
|
|
337
|
+
const accPath = path.join(os.homedir(), '.gemini', 'google_accounts.json');
|
|
338
|
+
const accContent = await fs.readFile(accPath, 'utf8');
|
|
339
|
+
const accounts = JSON.parse(accContent);
|
|
340
|
+
if (accounts.active) {
|
|
341
|
+
email = accounts.active;
|
|
342
|
+
}
|
|
343
|
+
} catch (e) { }
|
|
344
|
+
}
|
|
345
|
+
} catch (e) {
|
|
346
|
+
// Network error, fallback to checking local accounts file
|
|
347
|
+
try {
|
|
348
|
+
const accPath = path.join(os.homedir(), '.gemini', 'google_accounts.json');
|
|
349
|
+
const accContent = await fs.readFile(accPath, 'utf8');
|
|
350
|
+
const accounts = JSON.parse(accContent);
|
|
351
|
+
if (accounts.active) {
|
|
352
|
+
email = accounts.active;
|
|
353
|
+
}
|
|
354
|
+
} catch (err) { }
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
authenticated: true,
|
|
359
|
+
email: email
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
authenticated: false,
|
|
365
|
+
email: null,
|
|
366
|
+
error: 'No valid tokens found in oauth_creds'
|
|
367
|
+
};
|
|
368
|
+
} catch (error) {
|
|
369
|
+
return {
|
|
370
|
+
authenticated: false,
|
|
371
|
+
email: null,
|
|
372
|
+
error: 'Gemini CLI not configured'
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
263
377
|
export default router;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import sessionManager from '../sessionManager.js';
|
|
3
|
+
|
|
4
|
+
const router = express.Router();
|
|
5
|
+
|
|
6
|
+
router.get('/sessions/:sessionId/messages', async (req, res) => {
|
|
7
|
+
try {
|
|
8
|
+
const { sessionId } = req.params;
|
|
9
|
+
|
|
10
|
+
if (!sessionId || typeof sessionId !== 'string' || !/^[a-zA-Z0-9_.-]{1,100}$/.test(sessionId)) {
|
|
11
|
+
return res.status(400).json({ success: false, error: 'Invalid session ID format' });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const messages = sessionManager.getSessionMessages(sessionId);
|
|
15
|
+
|
|
16
|
+
res.json({
|
|
17
|
+
success: true,
|
|
18
|
+
messages: messages,
|
|
19
|
+
total: messages.length,
|
|
20
|
+
hasMore: false,
|
|
21
|
+
offset: 0,
|
|
22
|
+
limit: messages.length
|
|
23
|
+
});
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('Error fetching Gemini session messages:', error);
|
|
26
|
+
res.status(500).json({ success: false, error: error.message });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
router.delete('/sessions/:sessionId', async (req, res) => {
|
|
31
|
+
try {
|
|
32
|
+
const { sessionId } = req.params;
|
|
33
|
+
|
|
34
|
+
if (!sessionId || typeof sessionId !== 'string' || !/^[a-zA-Z0-9_.-]{1,100}$/.test(sessionId)) {
|
|
35
|
+
return res.status(400).json({ success: false, error: 'Invalid session ID format' });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await sessionManager.deleteSession(sessionId);
|
|
39
|
+
res.json({ success: true });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(`Error deleting Gemini session ${req.params.sessionId}:`, error);
|
|
42
|
+
res.status(500).json({ success: false, error: error.message });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export default router;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
class SessionManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
// Store sessions in memory with conversation history
|
|
8
|
+
this.sessions = new Map();
|
|
9
|
+
this.maxSessions = 100;
|
|
10
|
+
this.sessionsDir = path.join(os.homedir(), '.gemini', 'sessions');
|
|
11
|
+
this.ready = this.init();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async init() {
|
|
15
|
+
await this.initSessionsDir();
|
|
16
|
+
await this.loadSessions();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async initSessionsDir() {
|
|
20
|
+
try {
|
|
21
|
+
await fs.mkdir(this.sessionsDir, { recursive: true });
|
|
22
|
+
} catch (error) {
|
|
23
|
+
// console.error('Error creating sessions directory:', error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Create a new session
|
|
28
|
+
createSession(sessionId, projectPath) {
|
|
29
|
+
const session = {
|
|
30
|
+
id: sessionId,
|
|
31
|
+
projectPath: projectPath,
|
|
32
|
+
messages: [],
|
|
33
|
+
createdAt: new Date(),
|
|
34
|
+
lastActivity: new Date()
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Evict oldest session from memory if we exceed limit
|
|
38
|
+
if (this.sessions.size >= this.maxSessions) {
|
|
39
|
+
const oldestKey = this.sessions.keys().next().value;
|
|
40
|
+
if (oldestKey) this.sessions.delete(oldestKey);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.sessions.set(sessionId, session);
|
|
44
|
+
this.saveSession(sessionId);
|
|
45
|
+
|
|
46
|
+
return session;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Add a message to session
|
|
50
|
+
addMessage(sessionId, role, content) {
|
|
51
|
+
let session = this.sessions.get(sessionId);
|
|
52
|
+
|
|
53
|
+
if (!session) {
|
|
54
|
+
// Create session if it doesn't exist
|
|
55
|
+
session = this.createSession(sessionId, '');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const message = {
|
|
59
|
+
role: role, // 'user' or 'assistant'
|
|
60
|
+
content: content,
|
|
61
|
+
timestamp: new Date()
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
session.messages.push(message);
|
|
65
|
+
session.lastActivity = new Date();
|
|
66
|
+
|
|
67
|
+
this.saveSession(sessionId);
|
|
68
|
+
|
|
69
|
+
return session;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Get session by ID
|
|
73
|
+
getSession(sessionId) {
|
|
74
|
+
return this.sessions.get(sessionId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Get all sessions for a project
|
|
78
|
+
getProjectSessions(projectPath) {
|
|
79
|
+
const sessions = [];
|
|
80
|
+
|
|
81
|
+
for (const [id, session] of this.sessions) {
|
|
82
|
+
if (session.projectPath === projectPath) {
|
|
83
|
+
sessions.push({
|
|
84
|
+
id: session.id,
|
|
85
|
+
summary: this.getSessionSummary(session),
|
|
86
|
+
messageCount: session.messages.length,
|
|
87
|
+
lastActivity: session.lastActivity
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return sessions.sort((a, b) =>
|
|
93
|
+
new Date(b.lastActivity) - new Date(a.lastActivity)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Get session summary
|
|
98
|
+
getSessionSummary(session) {
|
|
99
|
+
if (session.messages.length === 0) {
|
|
100
|
+
return 'New Session';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Find first user message
|
|
104
|
+
const firstUserMessage = session.messages.find(m => m.role === 'user');
|
|
105
|
+
if (firstUserMessage) {
|
|
106
|
+
const content = firstUserMessage.content;
|
|
107
|
+
return content.length > 50 ? content.substring(0, 50) + '...' : content;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return 'New Session';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Build conversation context for Gemini
|
|
114
|
+
buildConversationContext(sessionId, maxMessages = 10) {
|
|
115
|
+
const session = this.sessions.get(sessionId);
|
|
116
|
+
|
|
117
|
+
if (!session || session.messages.length === 0) {
|
|
118
|
+
return '';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Get last N messages for context
|
|
122
|
+
const recentMessages = session.messages.slice(-maxMessages);
|
|
123
|
+
|
|
124
|
+
let context = 'Here is the conversation history:\n\n';
|
|
125
|
+
|
|
126
|
+
for (const msg of recentMessages) {
|
|
127
|
+
if (msg.role === 'user') {
|
|
128
|
+
context += `User: ${msg.content}\n`;
|
|
129
|
+
} else {
|
|
130
|
+
context += `Assistant: ${msg.content}\n`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
context += '\nBased on the conversation history above, please answer the following:\n';
|
|
135
|
+
|
|
136
|
+
return context;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Prevent path traversal
|
|
140
|
+
_safeFilePath(sessionId) {
|
|
141
|
+
const safeId = String(sessionId).replace(/[/\\]|\.\./g, '');
|
|
142
|
+
return path.join(this.sessionsDir, `${safeId}.json`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Save session to disk
|
|
146
|
+
async saveSession(sessionId) {
|
|
147
|
+
const session = this.sessions.get(sessionId);
|
|
148
|
+
if (!session) return;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const filePath = this._safeFilePath(sessionId);
|
|
152
|
+
await fs.writeFile(filePath, JSON.stringify(session, null, 2));
|
|
153
|
+
} catch (error) {
|
|
154
|
+
// console.error('Error saving session:', error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Load sessions from disk
|
|
159
|
+
async loadSessions() {
|
|
160
|
+
try {
|
|
161
|
+
const files = await fs.readdir(this.sessionsDir);
|
|
162
|
+
|
|
163
|
+
for (const file of files) {
|
|
164
|
+
if (file.endsWith('.json')) {
|
|
165
|
+
try {
|
|
166
|
+
const filePath = path.join(this.sessionsDir, file);
|
|
167
|
+
const data = await fs.readFile(filePath, 'utf8');
|
|
168
|
+
const session = JSON.parse(data);
|
|
169
|
+
|
|
170
|
+
// Convert dates
|
|
171
|
+
session.createdAt = new Date(session.createdAt);
|
|
172
|
+
session.lastActivity = new Date(session.lastActivity);
|
|
173
|
+
session.messages.forEach(msg => {
|
|
174
|
+
msg.timestamp = new Date(msg.timestamp);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
this.sessions.set(session.id, session);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
// console.error(`Error loading session ${file}:`, error);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Enforce eviction after loading to prevent massive memory usage
|
|
185
|
+
while (this.sessions.size > this.maxSessions) {
|
|
186
|
+
const oldestKey = this.sessions.keys().next().value;
|
|
187
|
+
if (oldestKey) this.sessions.delete(oldestKey);
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
// console.error('Error loading sessions:', error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Delete a session
|
|
195
|
+
async deleteSession(sessionId) {
|
|
196
|
+
this.sessions.delete(sessionId);
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const filePath = this._safeFilePath(sessionId);
|
|
200
|
+
await fs.unlink(filePath);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
// console.error('Error deleting session file:', error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Get session messages for display
|
|
207
|
+
getSessionMessages(sessionId) {
|
|
208
|
+
const session = this.sessions.get(sessionId);
|
|
209
|
+
if (!session) return [];
|
|
210
|
+
|
|
211
|
+
return session.messages.map(msg => ({
|
|
212
|
+
type: 'message',
|
|
213
|
+
message: {
|
|
214
|
+
role: msg.role,
|
|
215
|
+
content: msg.content
|
|
216
|
+
},
|
|
217
|
+
timestamp: msg.timestamp.toISOString()
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Singleton instance
|
|
223
|
+
const sessionManager = new SessionManager();
|
|
224
|
+
|
|
225
|
+
export const ready = sessionManager.ready;
|
|
226
|
+
export default sessionManager;
|
package/shared/modelConstants.js
CHANGED
|
@@ -65,3 +65,22 @@ export const CODEX_MODELS = {
|
|
|
65
65
|
|
|
66
66
|
DEFAULT: 'gpt-5.3-codex'
|
|
67
67
|
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Gemini Models
|
|
71
|
+
*/
|
|
72
|
+
export const GEMINI_MODELS = {
|
|
73
|
+
OPTIONS: [
|
|
74
|
+
{ value: 'gemini-3.1-pro-preview', label: 'Gemini 3.1 Pro Preview' },
|
|
75
|
+
{ value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' },
|
|
76
|
+
{ value: 'gemini-3-flash-preview', label: 'Gemini 3 Flash Preview' },
|
|
77
|
+
{ value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
|
|
78
|
+
{ value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
|
|
79
|
+
{ value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash Lite' },
|
|
80
|
+
{ value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash' },
|
|
81
|
+
{ value: 'gemini-2.0-pro-exp', label: 'Gemini 2.0 Pro Experimental' },
|
|
82
|
+
{ value: 'gemini-2.0-flash-thinking-exp', label: 'Gemini 2.0 Flash Thinking' }
|
|
83
|
+
],
|
|
84
|
+
|
|
85
|
+
DEFAULT: 'gemini-2.5-flash'
|
|
86
|
+
};
|