@axhub/genie 0.1.5 → 0.1.7

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.
@@ -9,8 +9,10 @@ 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 { queryGemini } from '../gemini-cli.js';
13
+ import { queryOpencode } from '../opencode-cli.js';
12
14
  import { Octokit } from '@octokit/rest';
13
- import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
15
+ import { CODEX_MODELS, GEMINI_MODELS, OPENCODE_MODELS } from '../../shared/modelConstants.js';
14
16
  import { IS_PLATFORM } from '../constants/config.js';
15
17
 
16
18
  const router = express.Router();
@@ -633,7 +635,7 @@ class ResponseCollector {
633
635
  /**
634
636
  * POST /api/agent
635
637
  *
636
- * Trigger an AI agent (Claude or Cursor) to work on a project.
638
+ * Trigger an AI agent (Claude, Cursor, Codex, or Gemini) to work on a project.
637
639
  * Supports automatic GitHub branch and pull request creation after successful completion.
638
640
  *
639
641
  * ================================================================================================
@@ -661,7 +663,7 @@ class ResponseCollector {
661
663
  * @param {string} sessionId - (Optional) Existing session ID to resume.
662
664
  * If provided, the request continues that session instead of creating a new one.
663
665
  *
664
- * @param {string} provider - (Optional) AI provider to use. Options: 'claude' | 'cursor'
666
+ * @param {string} provider - (Optional) AI provider to use. Options: 'claude' | 'cursor' | 'codex' | 'gemini'
665
667
  * Default: 'claude'
666
668
  *
667
669
  * @param {boolean} stream - (Optional) Enable Server-Sent Events (SSE) streaming for real-time updates.
@@ -677,6 +679,7 @@ class ResponseCollector {
677
679
  * 'gpt-5.1-codex', 'gpt-5.1-codex-high', 'gpt-5.1-codex-max',
678
680
  * 'gpt-5.1-codex-max-high', 'opus-4.1', 'grok', and thinking variants
679
681
  * Codex models: 'gpt-5.2' (default), 'gpt-5.1-codex-max', 'o3', 'o4-mini'
682
+ * Gemini models: 'gemini-2.5-pro' (default), 'gemini-2.5-flash', 'gemini-2.5-flash-lite'
680
683
  *
681
684
  * @param {boolean} cleanup - (Optional) Auto-cleanup project directory after completion.
682
685
  * Default: true
@@ -779,7 +782,7 @@ class ResponseCollector {
779
782
  * Input Validations (400 Bad Request):
780
783
  * - Either githubUrl OR projectPath must be provided (not neither)
781
784
  * - message must be non-empty string
782
- * - provider must be 'claude' or 'cursor'
785
+ * - provider must be 'claude', 'cursor', 'codex', or 'gemini'
783
786
  * - createBranch/createPR requires githubUrl OR projectPath (not neither)
784
787
  * - branchName must pass Git naming rules (if provided)
785
788
  *
@@ -872,7 +875,7 @@ router.post('/', validateExternalApiKey, async (req, res) => {
872
875
 
873
876
  // Parse stream and cleanup as booleans (handle string "true"/"false" from curl)
874
877
  const stream = req.body.stream === undefined ? true : (req.body.stream === true || req.body.stream === 'true');
875
- const cleanup = req.body.cleanup === undefined ? true : (req.body.cleanup === true || req.body.cleanup === 'true');
878
+ const requestedCleanup = req.body.cleanup === undefined ? true : (req.body.cleanup === true || req.body.cleanup === 'true');
876
879
 
877
880
  // If branchName is provided, automatically enable createBranch
878
881
  const createBranch = branchName ? true : (req.body.createBranch === true || req.body.createBranch === 'true');
@@ -881,10 +884,13 @@ router.post('/', validateExternalApiKey, async (req, res) => {
881
884
  const normalizedMessage = typeof message === 'string' ? message.trim() : '';
882
885
  const requestedOpenOnly = openOnly === true || openOnly === 'true';
883
886
  const isOpenOnly = requestedOpenOnly || (!normalizedMessage && !!normalizedSessionId);
887
+ // Keep openOnly requests backward-compatible with existing docs/examples:
888
+ // cleanup is irrelevant when we only generate session navigation.
889
+ const cleanup = isOpenOnly ? false : requestedCleanup;
884
890
 
885
891
  // Validate inputs
886
- if (!['claude', 'cursor', 'codex'].includes(provider)) {
887
- return res.status(400).json({ error: 'provider must be "claude", "cursor", or "codex"' });
892
+ if (!['claude', 'cursor', 'codex', 'gemini', 'opencode'].includes(provider)) {
893
+ return res.status(400).json({ error: 'provider must be "claude", "cursor", "codex", "gemini", or "opencode"' });
888
894
  }
889
895
 
890
896
  if (sessionId !== undefined && (typeof sessionId !== 'string' || !sessionId.trim())) {
@@ -911,10 +917,6 @@ router.post('/', validateExternalApiKey, async (req, res) => {
911
917
  return res.status(400).json({ error: 'createBranch and createPR are not supported when openOnly=true' });
912
918
  }
913
919
 
914
- if (isOpenOnly && cleanup) {
915
- return res.status(400).json({ error: 'cleanup is not supported when openOnly=true' });
916
- }
917
-
918
920
  // Validate GitHub branch/PR creation requirements
919
921
  // Allow branch/PR creation with projectPath as long as it has a GitHub remote
920
922
  if ((createBranch || createPR) && !githubUrl && !projectPath) {
@@ -1062,6 +1064,28 @@ router.post('/', validateExternalApiKey, async (req, res) => {
1062
1064
  model: model || CODEX_MODELS.DEFAULT,
1063
1065
  permissionMode: 'bypassPermissions'
1064
1066
  }, writer);
1067
+ } else if (provider === 'gemini') {
1068
+ console.log('💎 Starting Gemini CLI session');
1069
+
1070
+ await queryGemini(normalizedMessage, {
1071
+ projectPath: finalProjectPath,
1072
+ cwd: finalProjectPath,
1073
+ sessionId: normalizedSessionId,
1074
+ resume: !!normalizedSessionId,
1075
+ model: model || GEMINI_MODELS.DEFAULT,
1076
+ permissionMode: 'bypassPermissions'
1077
+ }, writer);
1078
+ } else if (provider === 'opencode') {
1079
+ console.log('🧠 Starting OpenCode CLI session');
1080
+
1081
+ await queryOpencode(normalizedMessage, {
1082
+ projectPath: finalProjectPath,
1083
+ cwd: finalProjectPath,
1084
+ sessionId: normalizedSessionId,
1085
+ resume: !!normalizedSessionId,
1086
+ model: model || OPENCODE_MODELS.DEFAULT,
1087
+ permissionMode: 'bypassPermissions'
1088
+ }, writer);
1065
1089
  }
1066
1090
 
1067
1091
  // Handle GitHub branch and PR creation after successful agent completion
@@ -5,6 +5,172 @@ import path from 'path';
5
5
  import os from 'os';
6
6
 
7
7
  const router = express.Router();
8
+ const INSTALLATION_CACHE_TTL_MS = 12 * 60 * 60 * 1000;
9
+ const SUPPORTED_PROVIDER_COMMANDS = {
10
+ claude: 'claude',
11
+ cursor: 'cursor-agent',
12
+ codex: 'codex',
13
+ gemini: 'gemini',
14
+ opencode: 'opencode'
15
+ };
16
+ const installationStatusCache = new Map(); // provider -> status
17
+ const installationStatusInFlight = new Map(); // provider -> Promise
18
+
19
+ function getLocatorCommand() {
20
+ return process.platform === 'win32' ? 'where' : 'which';
21
+ }
22
+
23
+ function resolveCommandPath(command) {
24
+ return new Promise((resolve) => {
25
+ let childProcess;
26
+ let stdout = '';
27
+ let stderr = '';
28
+
29
+ try {
30
+ childProcess = spawn(getLocatorCommand(), [command], {
31
+ stdio: ['ignore', 'pipe', 'pipe']
32
+ });
33
+ } catch (error) {
34
+ resolve({
35
+ found: false,
36
+ resolvedPath: null,
37
+ reason: `Failed to run command locator: ${error.message}`
38
+ });
39
+ return;
40
+ }
41
+
42
+ childProcess.stdout.on('data', (data) => {
43
+ stdout += data.toString();
44
+ });
45
+
46
+ childProcess.stderr.on('data', (data) => {
47
+ stderr += data.toString();
48
+ });
49
+
50
+ childProcess.on('close', (code) => {
51
+ const lines = stdout
52
+ .split(/\r?\n/)
53
+ .map(line => line.trim())
54
+ .filter(Boolean);
55
+ const resolvedPath = lines[0] || null;
56
+
57
+ if (code === 0 && resolvedPath) {
58
+ resolve({
59
+ found: true,
60
+ resolvedPath,
61
+ reason: null
62
+ });
63
+ return;
64
+ }
65
+
66
+ resolve({
67
+ found: false,
68
+ resolvedPath: null,
69
+ reason: `${command} not found in PATH${stderr?.trim() ? ` (${stderr.trim()})` : ''}`
70
+ });
71
+ });
72
+
73
+ childProcess.on('error', (error) => {
74
+ resolve({
75
+ found: false,
76
+ resolvedPath: null,
77
+ reason: `Failed to detect ${command}: ${error.message}`
78
+ });
79
+ });
80
+ });
81
+ }
82
+
83
+ function buildInstallationStatus(provider, command, installed, resolvedPath, reason, cacheHit = false) {
84
+ return {
85
+ success: true,
86
+ provider,
87
+ command,
88
+ installed,
89
+ reason,
90
+ resolvedPath,
91
+ checkedAt: new Date().toISOString(),
92
+ cacheHit,
93
+ ttlMs: INSTALLATION_CACHE_TTL_MS
94
+ };
95
+ }
96
+
97
+ async function detectProviderInstallationStatus(provider) {
98
+ const command = SUPPORTED_PROVIDER_COMMANDS[provider];
99
+ if (!command) {
100
+ throw new Error(`Unsupported provider: ${provider}`);
101
+ }
102
+
103
+ const now = Date.now();
104
+ const cached = installationStatusCache.get(provider);
105
+ if (cached && cached.expiresAt > now) {
106
+ return {
107
+ ...cached.status,
108
+ cacheHit: true
109
+ };
110
+ }
111
+
112
+ if (installationStatusInFlight.has(provider)) {
113
+ return installationStatusInFlight.get(provider);
114
+ }
115
+
116
+ const detectionPromise = (async () => {
117
+ const commandCheck = await resolveCommandPath(command);
118
+ const installed = commandCheck.found;
119
+ const status = buildInstallationStatus(
120
+ provider,
121
+ command,
122
+ installed,
123
+ commandCheck.resolvedPath,
124
+ commandCheck.reason,
125
+ false
126
+ );
127
+
128
+ if (installed) {
129
+ installationStatusCache.set(provider, {
130
+ status,
131
+ expiresAt: now + INSTALLATION_CACHE_TTL_MS
132
+ });
133
+ } else {
134
+ installationStatusCache.delete(provider);
135
+ }
136
+
137
+ return status;
138
+ })();
139
+
140
+ installationStatusInFlight.set(provider, detectionPromise);
141
+
142
+ try {
143
+ return await detectionPromise;
144
+ } finally {
145
+ installationStatusInFlight.delete(provider);
146
+ }
147
+ }
148
+
149
+ router.get('/providers/:provider/installation-status', async (req, res) => {
150
+ try {
151
+ const provider = String(req.params.provider || '').toLowerCase();
152
+ if (!Object.prototype.hasOwnProperty.call(SUPPORTED_PROVIDER_COMMANDS, provider)) {
153
+ return res.status(400).json({
154
+ success: false,
155
+ error: `Unsupported provider: ${provider}`
156
+ });
157
+ }
158
+
159
+ const forceRefresh = String(req.query.force || '').toLowerCase() === 'true';
160
+ if (forceRefresh) {
161
+ installationStatusCache.delete(provider);
162
+ }
163
+
164
+ const status = await detectProviderInstallationStatus(provider);
165
+ return res.json(status);
166
+ } catch (error) {
167
+ console.error('Error checking provider installation status:', error);
168
+ return res.status(500).json({
169
+ success: false,
170
+ error: error.message
171
+ });
172
+ }
173
+ });
8
174
 
9
175
  router.get('/claude/status', async (req, res) => {
10
176
  try {
@@ -74,6 +240,26 @@ router.get('/codex/status', async (req, res) => {
74
240
  }
75
241
  });
76
242
 
243
+ router.get('/gemini/status', async (req, res) => {
244
+ try {
245
+ const result = await checkGeminiCredentials();
246
+
247
+ res.json({
248
+ authenticated: result.authenticated,
249
+ email: result.email,
250
+ error: result.error
251
+ });
252
+
253
+ } catch (error) {
254
+ console.error('Error checking Gemini auth status:', error);
255
+ res.status(500).json({
256
+ authenticated: false,
257
+ email: null,
258
+ error: error.message
259
+ });
260
+ }
261
+ });
262
+
77
263
  async function checkClaudeCredentials() {
78
264
  try {
79
265
  const credPath = path.join(os.homedir(), '.claude', '.credentials.json');
@@ -260,4 +446,89 @@ async function checkCodexCredentials() {
260
446
  }
261
447
  }
262
448
 
449
+ async function checkGeminiCredentials() {
450
+ try {
451
+ const geminiDir = path.join(os.homedir(), '.gemini');
452
+ const oauthCredPath = path.join(geminiDir, 'oauth_creds.json');
453
+ const content = await fs.readFile(oauthCredPath, 'utf8');
454
+ const creds = JSON.parse(content);
455
+
456
+ if (creds?.access_token || creds?.refresh_token || creds?.token) {
457
+ return {
458
+ authenticated: true,
459
+ email: creds?.email || 'Authenticated'
460
+ };
461
+ }
462
+
463
+ return {
464
+ authenticated: false,
465
+ email: null,
466
+ error: 'No valid Gemini credentials found'
467
+ };
468
+ } catch (error) {
469
+ if (error.code === 'ENOENT') {
470
+ return {
471
+ authenticated: false,
472
+ email: null,
473
+ error: 'Gemini not configured'
474
+ };
475
+ }
476
+
477
+ return {
478
+ authenticated: false,
479
+ email: null,
480
+ error: error.message
481
+ };
482
+ }
483
+ }
484
+
485
+ // OpenCode status helper
486
+ async function checkOpencodeCredentials() {
487
+ try {
488
+ const candidates = [
489
+ path.join(os.homedir(), '.config', 'opencode', 'auth.json'),
490
+ path.join(os.homedir(), '.opencode', 'auth.json')
491
+ ];
492
+ let anyFileAccessible = false;
493
+ for (const p of candidates) {
494
+ try {
495
+ await fs.access(p);
496
+ anyFileAccessible = true;
497
+ const raw = await fs.readFile(p, 'utf8');
498
+ const data = JSON.parse(raw);
499
+ const token = data?.access_token || data?.token || data?.api_key;
500
+ if (token) {
501
+ const email = data.email || 'Authenticated';
502
+ return { authenticated: true, email, error: null };
503
+ }
504
+ // else try next candidate
505
+ } catch (err) {
506
+ // ignore and try next
507
+ continue;
508
+ }
509
+ }
510
+ if (anyFileAccessible) {
511
+ return { authenticated: false, email: null, error: 'Invalid OpenCode credentials format' };
512
+ }
513
+ return { authenticated: false, email: null, error: 'OpenCode not configured' };
514
+ } catch (error) {
515
+ return { authenticated: false, email: null, error: 'OpenCode not configured' };
516
+ }
517
+ }
518
+
519
+ // Route: GET /opencode/status
520
+ router.get('/opencode/status', async (req, res) => {
521
+ try {
522
+ const result = await checkOpencodeCredentials();
523
+ res.json({
524
+ authenticated: result.authenticated,
525
+ email: result.email,
526
+ error: result.error
527
+ });
528
+ } catch (error) {
529
+ console.error('Error checking OpenCode auth status:', error);
530
+ res.status(500).json({ authenticated: false, email: null, error: error.message });
531
+ }
532
+ });
533
+
263
534
  export default router;
@@ -4,7 +4,8 @@ import path from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import os from 'os';
6
6
  import matter from 'gray-matter';
7
- import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
7
+ import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS, GEMINI_MODELS, OPENCODE_MODELS } from '../../shared/modelConstants.js';
8
+ import { listOpencodeModels } from '../opencode-cli.js';
8
9
 
9
10
  const __filename = fileURLToPath(import.meta.url);
10
11
  const __dirname = path.dirname(__filename);
@@ -187,11 +188,35 @@ Custom commands can be created in:
187
188
  const availableModels = {
188
189
  claude: CLAUDE_MODELS.OPTIONS.map(o => o.value),
189
190
  cursor: CURSOR_MODELS.OPTIONS.map(o => o.value),
190
- codex: CODEX_MODELS.OPTIONS.map(o => o.value)
191
+ codex: CODEX_MODELS.OPTIONS.map(o => o.value),
192
+ gemini: GEMINI_MODELS.OPTIONS.map(o => o.value),
193
+ opencode: OPENCODE_MODELS.OPTIONS.map(o => o.value)
191
194
  };
192
195
 
193
196
  const currentProvider = context?.provider || 'claude';
194
- const currentModel = context?.model || CLAUDE_MODELS.DEFAULT;
197
+ const providerDefaults = {
198
+ claude: CLAUDE_MODELS.DEFAULT,
199
+ cursor: CURSOR_MODELS.DEFAULT,
200
+ codex: CODEX_MODELS.DEFAULT,
201
+ gemini: GEMINI_MODELS.DEFAULT,
202
+ opencode: OPENCODE_MODELS.DEFAULT
203
+ };
204
+ const currentModel = context?.model || providerDefaults[currentProvider] || CLAUDE_MODELS.DEFAULT;
205
+ let opencodeDiscovery = null;
206
+
207
+ if (context?.projectPath) {
208
+ try {
209
+ opencodeDiscovery = await listOpencodeModels({
210
+ projectPath: context?.projectPath,
211
+ cwd: context?.projectPath
212
+ });
213
+ if (opencodeDiscovery.models.length > 0) {
214
+ availableModels.opencode = opencodeDiscovery.models;
215
+ }
216
+ } catch (error) {
217
+ console.warn('Failed to query OpenCode runtime models:', error.message);
218
+ }
219
+ }
195
220
 
196
221
  return {
197
222
  type: 'builtin',
@@ -202,6 +227,7 @@ Custom commands can be created in:
202
227
  model: currentModel
203
228
  },
204
229
  available: availableModels,
230
+ opencodeDiscovery,
205
231
  message: args.length > 0
206
232
  ? `Switching to model: ${args[0]}`
207
233
  : `Current model: ${currentModel}`
@@ -6,6 +6,7 @@ import { promises as fs } from 'fs';
6
6
  import { extractProjectDirectory } from '../projects.js';
7
7
  import { queryClaudeSDK } from '../claude-sdk.js';
8
8
  import { spawnCursor } from '../cursor-cli.js';
9
+ import { queryGemini } from '../gemini-cli.js';
9
10
 
10
11
  const router = express.Router();
11
12
  const execAsync = promisify(exec);
@@ -519,8 +520,8 @@ router.post('/generate-commit-message', async (req, res) => {
519
520
  }
520
521
 
521
522
  // Validate provider
522
- if (!['claude', 'cursor'].includes(provider)) {
523
- return res.status(400).json({ error: 'provider must be "claude" or "cursor"' });
523
+ if (!['claude', 'cursor', 'gemini'].includes(provider)) {
524
+ return res.status(400).json({ error: 'provider must be "claude", "cursor", or "gemini"' });
524
525
  }
525
526
 
526
527
  try {
@@ -573,10 +574,10 @@ router.post('/generate-commit-message', async (req, res) => {
573
574
  });
574
575
 
575
576
  /**
576
- * Generates a commit message using AI (Claude SDK or Cursor CLI)
577
+ * Generates a commit message using AI (Claude SDK, Cursor CLI, or Gemini CLI)
577
578
  * @param {Array<string>} files - List of changed files
578
579
  * @param {string} diffContext - Git diff content
579
- * @param {string} provider - 'claude' or 'cursor'
580
+ * @param {string} provider - 'claude', 'cursor', or 'gemini'
580
581
  * @param {string} projectPath - Project directory path
581
582
  * @returns {Promise<string>} Generated commit message
582
583
  */
@@ -630,6 +631,9 @@ Generate the commit message:`;
630
631
  console.log('✅ Cursor output:', parsed.output.substring(0, 100));
631
632
  responseText += parsed.output;
632
633
  }
634
+ else if (parsed.type === 'claude-response' && parsed.data?.type === 'content_block_delta' && parsed.data?.delta?.text) {
635
+ responseText += parsed.data.delta.text;
636
+ }
633
637
  // Also handle direct text messages
634
638
  else if (parsed.type === 'text' && parsed.text) {
635
639
  console.log('✅ Direct text:', parsed.text.substring(0, 100));
@@ -658,6 +662,12 @@ Generate the commit message:`;
658
662
  cwd: projectPath,
659
663
  skipPermissions: true
660
664
  }, writer);
665
+ } else if (provider === 'gemini') {
666
+ await queryGemini(prompt, {
667
+ cwd: projectPath,
668
+ projectPath,
669
+ permissionMode: 'bypassPermissions'
670
+ }, writer);
661
671
  }
662
672
 
663
673
  console.log('📊 Total response text collected:', responseText.length, 'characters');
@@ -1125,4 +1135,4 @@ router.post('/delete-untracked', async (req, res) => {
1125
1135
  }
1126
1136
  });
1127
1137
 
1128
- export default router;
1138
+ export default router;
@@ -0,0 +1,72 @@
1
+ import express from 'express';
2
+ import {
3
+ getOpencodeSessions,
4
+ getOpencodeSessionMessages,
5
+ deleteOpencodeSession
6
+ } from '../projects.js';
7
+ import { listOpencodeModels } from '../opencode-cli.js';
8
+
9
+ const router = express.Router();
10
+
11
+ router.get('/models', async (req, res) => {
12
+ try {
13
+ const { projectPath } = req.query;
14
+
15
+ if (!projectPath) {
16
+ return res.status(400).json({ success: false, error: 'projectPath query parameter required' });
17
+ }
18
+
19
+ const discovery = await listOpencodeModels({ projectPath, cwd: projectPath });
20
+ res.json({ success: true, ...discovery });
21
+ } catch (error) {
22
+ console.error('Error fetching OpenCode models:', error);
23
+ res.status(500).json({ success: false, error: error.message });
24
+ }
25
+ });
26
+
27
+ router.get('/sessions', async (req, res) => {
28
+ try {
29
+ const { projectPath } = req.query;
30
+
31
+ if (!projectPath) {
32
+ return res.status(400).json({ success: false, error: 'projectPath query parameter required' });
33
+ }
34
+
35
+ const sessions = await getOpencodeSessions(projectPath);
36
+ res.json({ success: true, sessions });
37
+ } catch (error) {
38
+ console.error('Error fetching OpenCode sessions:', error);
39
+ res.status(500).json({ success: false, error: error.message });
40
+ }
41
+ });
42
+
43
+ router.get('/sessions/:sessionId/messages', async (req, res) => {
44
+ try {
45
+ const { sessionId } = req.params;
46
+ const { limit, offset } = req.query;
47
+
48
+ const result = await getOpencodeSessionMessages(
49
+ sessionId,
50
+ limit ? parseInt(limit, 10) : null,
51
+ offset ? parseInt(offset, 10) : 0
52
+ );
53
+
54
+ res.json({ success: true, ...result });
55
+ } catch (error) {
56
+ console.error('Error fetching OpenCode session messages:', error);
57
+ res.status(500).json({ success: false, error: error.message });
58
+ }
59
+ });
60
+
61
+ router.delete('/sessions/:sessionId', async (req, res) => {
62
+ try {
63
+ const { sessionId } = req.params;
64
+ await deleteOpencodeSession(sessionId);
65
+ res.json({ success: true });
66
+ } catch (error) {
67
+ console.error(`Error deleting OpenCode session ${req.params.sessionId}:`, error);
68
+ res.status(500).json({ success: false, error: error.message });
69
+ }
70
+ });
71
+
72
+ export default router;
@@ -28,26 +28,35 @@ export const CLAUDE_MODELS = {
28
28
  */
29
29
  export const CURSOR_MODELS = {
30
30
  OPTIONS: [
31
- { value: 'gpt-5.2-high', label: 'GPT-5.2 High' },
32
- { value: 'gemini-3-pro', label: 'Gemini 3 Pro' },
33
- { value: 'opus-4.5-thinking', label: 'Claude 4.5 Opus (Thinking)' },
34
- { value: 'gpt-5.2', label: 'GPT-5.2' },
35
- { value: 'gpt-5.1', label: 'GPT-5.1' },
36
- { value: 'gpt-5.1-high', label: 'GPT-5.1 High' },
37
- { value: 'composer-1', label: 'Composer 1' },
38
31
  { value: 'auto', label: 'Auto' },
39
- { value: 'sonnet-4.5', label: 'Claude 4.5 Sonnet' },
40
- { value: 'sonnet-4.5-thinking', label: 'Claude 4.5 Sonnet (Thinking)' },
32
+ { value: 'sonnet-4', label: 'Claude 4 Sonnet' },
33
+ { value: 'sonnet-4-1m', label: 'Claude 4 Sonnet 1M' },
34
+ { value: 'haiku-4.5', label: 'Claude 4.5 Haiku' },
41
35
  { value: 'opus-4.5', label: 'Claude 4.5 Opus' },
36
+ { value: 'sonnet-4.5', label: 'Claude 4.5 Sonnet' },
37
+ { value: 'opus-4.6', label: 'Claude 4.6 Opus' },
38
+ { value: 'opus-4.6-fast', label: 'Claude 4.6 Opus (Fast mode)' },
39
+ { value: 'composer-1', label: 'Composer 1' },
40
+ { value: 'composer-1.5', label: 'Composer 1.5' },
41
+ { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
42
+ { value: 'gemini-3-flash', label: 'Gemini 3 Flash' },
43
+ { value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' },
44
+ { value: 'gemini-3-pro', label: 'Gemini 3 Pro' },
45
+ { value: 'gemini-3-pro-image-preview', label: 'Gemini 3 Pro Image Preview' },
46
+ { value: 'gpt-5', label: 'GPT-5' },
47
+ { value: 'gpt-5-fast', label: 'GPT-5 Fast' },
48
+ { value: 'gpt-5-mini', label: 'GPT-5 Mini' },
49
+ { value: 'gpt-5-codex', label: 'GPT-5-Codex' },
42
50
  { value: 'gpt-5.1-codex', label: 'GPT-5.1 Codex' },
43
- { value: 'gpt-5.1-codex-high', label: 'GPT-5.1 Codex High' },
44
51
  { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
45
- { value: 'gpt-5.1-codex-max-high', label: 'GPT-5.1 Codex Max High' },
46
- { value: 'opus-4.1', label: 'Claude 4.1 Opus' },
47
- { value: 'grok', label: 'Grok' }
52
+ { value: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini' },
53
+ { value: 'gpt-5.2', label: 'GPT-5.2' },
54
+ { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' },
55
+ { value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex' },
56
+ { value: 'grok-code', label: 'Grok Code' }
48
57
  ],
49
58
 
50
- DEFAULT: 'gpt-5'
59
+ DEFAULT: 'auto'
51
60
  };
52
61
 
53
62
  /**
@@ -55,11 +64,47 @@ export const CURSOR_MODELS = {
55
64
  */
56
65
  export const CODEX_MODELS = {
57
66
  OPTIONS: [
58
- { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' },
59
- { value: 'gpt-5.2', label: 'GPT-5.2' },
67
+ { value: 'gpt-5', label: 'GPT-5' },
68
+ { value: 'gpt-5-codex', label: 'GPT-5 Codex' },
69
+ { value: 'gpt-5-codex-mini', label: 'GPT-5 Codex Mini' },
70
+ { value: 'gpt-5.1', label: 'GPT-5.1' },
71
+ { value: 'gpt-5.1-codex', label: 'GPT-5.1 Codex' },
60
72
  { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
61
- { value: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini' }
73
+ { value: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini' },
74
+ { value: 'gpt-5.2', label: 'GPT-5.2' },
75
+ { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' },
76
+ { value: 'gpt-5.2-high', label: 'GPT-5.2 High' },
77
+ { value: 'gpt-5.2-low', label: 'GPT-5.2 Low' },
78
+ { value: 'gpt-5.2-medium', label: 'GPT-5.2 Medium' },
79
+ { value: 'gpt-5.2-xhigh', label: 'GPT-5.2 XHigh' },
80
+ { value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex' },
81
+ { value: 'gpt-5.3-codex-high', label: 'GPT-5.3 Codex High' },
82
+ { value: 'gpt-5.3-codex-low', label: 'GPT-5.3 Codex Low' },
83
+ { value: 'gpt-5.3-codex-medium', label: 'GPT-5.3 Codex Medium' },
84
+ { value: 'gpt-5.3-codex-xhigh', label: 'GPT-5.3 Codex XHigh' }
62
85
  ],
63
86
 
64
87
  DEFAULT: 'gpt-5.2-codex'
65
88
  };
89
+
90
+ export const GEMINI_MODELS = {
91
+ OPTIONS: [
92
+ { value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
93
+ { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
94
+ { value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' },
95
+ { value: 'gemini-3-flash-preview', label: 'Gemini 3 Flash Preview' }
96
+ ],
97
+
98
+ DEFAULT: 'gemini-3-pro-preview'
99
+ };
100
+
101
+ /**
102
+ * OpenCode (OPENCODE) Models
103
+ */
104
+ export const OPENCODE_MODELS = {
105
+ OPTIONS: [
106
+ { value: 'kimi-k2.5', label: 'Kimi K2.5' }
107
+ ],
108
+
109
+ DEFAULT: 'kimi-k2.5'
110
+ };