jbai-cli 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -4
- package/bin/jbai-codex.js +1 -1
- package/bin/jbai-opencode.js +0 -15
- package/bin/jbai.js +49 -44
- package/bin/test-models.js +261 -0
- package/lib/config.js +52 -57
- package/package.json +3 -7
- package/bin/jbai-aider.js +0 -94
package/README.md
CHANGED
|
@@ -35,9 +35,10 @@ Expected output:
|
|
|
35
35
|
```
|
|
36
36
|
Testing JetBrains AI Platform (staging)
|
|
37
37
|
|
|
38
|
-
1. OpenAI Proxy (
|
|
39
|
-
2.
|
|
40
|
-
3.
|
|
38
|
+
1. OpenAI Proxy (Chat): ✅ Working
|
|
39
|
+
2. OpenAI Proxy (Codex /responses): ✅ Working
|
|
40
|
+
3. Anthropic Proxy (Claude): ✅ Working
|
|
41
|
+
4. Google Proxy (Gemini): ✅ Working
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
## Usage
|
|
@@ -103,7 +104,7 @@ Each tool has a sensible default, but you can specify any available model:
|
|
|
103
104
|
jbai-claude --model claude-opus-4-1-20250805
|
|
104
105
|
|
|
105
106
|
# Codex with GPT-5
|
|
106
|
-
jbai-codex --model gpt-5-
|
|
107
|
+
jbai-codex --model gpt-5.2-codex
|
|
107
108
|
|
|
108
109
|
# Aider with Gemini Pro
|
|
109
110
|
jbai-aider --model gemini/gemini-2.5-pro
|
|
@@ -126,10 +127,19 @@ jbai-aider --model gemini/gemini-2.5-pro
|
|
|
126
127
|
| `gpt-4o-2024-11-20` | Default |
|
|
127
128
|
| `gpt-5-2025-08-07` | Latest |
|
|
128
129
|
| `gpt-5.1-2025-11-13` | |
|
|
130
|
+
| `gpt-5.2-2025-12-11` | |
|
|
129
131
|
| `gpt-5-mini-2025-08-07` | Fast |
|
|
130
132
|
| `o3-2025-04-16` | Reasoning |
|
|
131
133
|
| `o3-mini-2025-01-31` | |
|
|
132
134
|
|
|
135
|
+
**Codex (OpenAI Responses)** - Use with Codex CLI: `jbai-codex --model <model>`
|
|
136
|
+
| Model | Notes |
|
|
137
|
+
|-------|-------|
|
|
138
|
+
| `gpt-5.2-codex` | Default, coding-optimized |
|
|
139
|
+
| `gpt-5.1-codex` | |
|
|
140
|
+
| `gpt-5.1-codex-mini` | Faster |
|
|
141
|
+
| `gpt-5.1-codex-max` | |
|
|
142
|
+
|
|
133
143
|
**Gemini (Google)** - Use with Aider: `jbai-aider --model gemini/<model>`
|
|
134
144
|
| Model | Notes |
|
|
135
145
|
|-------|-------|
|
package/bin/jbai-codex.js
CHANGED
|
@@ -59,7 +59,7 @@ const hasModel = args.includes('--model');
|
|
|
59
59
|
const finalArgs = ['-c', `model_provider=${providerName}`];
|
|
60
60
|
|
|
61
61
|
if (!hasModel) {
|
|
62
|
-
finalArgs.push('--model', config.MODELS.openai.default);
|
|
62
|
+
finalArgs.push('--model', config.MODELS.codex?.default || config.MODELS.openai.default);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// Add super mode flags (full-auto)
|
package/bin/jbai-opencode.js
CHANGED
|
@@ -80,21 +80,6 @@ config.MODELS.openai.available.forEach(model => {
|
|
|
80
80
|
};
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
// Add other provider models (DeepSeek, Mistral, Qwen, XAI, Meta) via OpenAI endpoint
|
|
84
|
-
const otherModels = [
|
|
85
|
-
...config.MODELS.other.deepseek,
|
|
86
|
-
...config.MODELS.other.mistral,
|
|
87
|
-
...config.MODELS.other.qwen,
|
|
88
|
-
...config.MODELS.other.xai,
|
|
89
|
-
...config.MODELS.other.meta
|
|
90
|
-
];
|
|
91
|
-
otherModels.forEach(model => {
|
|
92
|
-
opencodeConfig.provider[providerName].models[model] = {
|
|
93
|
-
name: model,
|
|
94
|
-
limit: { context: 128000, output: 8192 }
|
|
95
|
-
};
|
|
96
|
-
});
|
|
97
|
-
|
|
98
83
|
// Add JetBrains Anthropic provider for Claude models
|
|
99
84
|
opencodeConfig.provider[anthropicProviderName] = {
|
|
100
85
|
npm: '@ai-sdk/anthropic',
|
package/bin/jbai.js
CHANGED
|
@@ -18,12 +18,6 @@ const TOOLS = {
|
|
|
18
18
|
install: 'npm install -g @openai/codex',
|
|
19
19
|
check: 'codex --version'
|
|
20
20
|
},
|
|
21
|
-
aider: {
|
|
22
|
-
name: 'Aider',
|
|
23
|
-
command: 'aider',
|
|
24
|
-
install: 'pipx install aider-chat',
|
|
25
|
-
check: 'aider --version'
|
|
26
|
-
},
|
|
27
21
|
gemini: {
|
|
28
22
|
name: 'Gemini CLI',
|
|
29
23
|
command: 'gemini',
|
|
@@ -47,10 +41,10 @@ COMMANDS:
|
|
|
47
41
|
jbai token Show token status
|
|
48
42
|
jbai token set Set token interactively
|
|
49
43
|
jbai token refresh Refresh expired token
|
|
50
|
-
jbai test Test
|
|
44
|
+
jbai test Test API endpoints (incl. Codex /responses)
|
|
51
45
|
jbai env [staging|production] Switch environment
|
|
52
46
|
jbai models List available models
|
|
53
|
-
jbai install Install all AI tools (claude, codex,
|
|
47
|
+
jbai install Install all AI tools (claude, codex, gemini, opencode)
|
|
54
48
|
jbai install claude Install specific tool
|
|
55
49
|
jbai doctor Check which tools are installed
|
|
56
50
|
jbai help Show this help
|
|
@@ -58,7 +52,6 @@ COMMANDS:
|
|
|
58
52
|
TOOL WRAPPERS:
|
|
59
53
|
jbai-claude Launch Claude Code with JetBrains AI
|
|
60
54
|
jbai-codex Launch Codex CLI with JetBrains AI
|
|
61
|
-
jbai-aider Launch Aider with JetBrains AI
|
|
62
55
|
jbai-gemini Launch Gemini CLI with JetBrains AI
|
|
63
56
|
jbai-opencode Launch OpenCode with JetBrains AI
|
|
64
57
|
|
|
@@ -66,13 +59,13 @@ SUPER MODE:
|
|
|
66
59
|
Add --super (or --yolo or -s) to skip confirmations:
|
|
67
60
|
jbai-claude --super # Skip permission prompts
|
|
68
61
|
jbai-codex --super # Full auto mode
|
|
69
|
-
jbai-
|
|
62
|
+
jbai-gemini --super # Auto-confirm changes
|
|
70
63
|
|
|
71
64
|
EXAMPLES:
|
|
72
65
|
jbai token set # Set your token
|
|
73
66
|
jbai-claude # Start Claude Code
|
|
74
67
|
jbai-codex exec "explain code" # Run Codex task
|
|
75
|
-
jbai-
|
|
68
|
+
jbai-gemini # Start Gemini CLI
|
|
76
69
|
|
|
77
70
|
TOKEN:
|
|
78
71
|
Get token: ${config.getEndpoints().tokenUrl}
|
|
@@ -146,40 +139,61 @@ async function testEndpoints() {
|
|
|
146
139
|
console.log(`Testing JetBrains AI Platform (${config.getEnvironment()})\n`);
|
|
147
140
|
|
|
148
141
|
// Test OpenAI
|
|
149
|
-
process.stdout.write('1. OpenAI Proxy (
|
|
142
|
+
process.stdout.write('1. OpenAI Proxy (Chat): ');
|
|
150
143
|
try {
|
|
151
144
|
const result = await httpPost(
|
|
152
145
|
`${endpoints.openai}/chat/completions`,
|
|
153
|
-
{ model:
|
|
146
|
+
{ model: config.MODELS.openai.default, messages: [{ role: 'user', content: 'Say OK' }], max_tokens: 5 },
|
|
147
|
+
{ 'Grazie-Authenticate-JWT': token }
|
|
148
|
+
);
|
|
149
|
+
const ok = result.statusCode === 200 && Array.isArray(result.json?.choices);
|
|
150
|
+
console.log(ok ? '✅ Working' : `❌ Failed (${result.statusCode})`);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
console.log(`❌ ${e.message}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Test Codex (Responses)
|
|
156
|
+
process.stdout.write('2. OpenAI Proxy (Codex /responses): ');
|
|
157
|
+
try {
|
|
158
|
+
const result = await httpPost(
|
|
159
|
+
`${endpoints.openai}/responses`,
|
|
160
|
+
{ model: config.MODELS.codex.default, input: 'Say OK', max_output_tokens: 64 },
|
|
154
161
|
{ 'Grazie-Authenticate-JWT': token }
|
|
155
162
|
);
|
|
156
|
-
|
|
163
|
+
const outputText = result.json?.output
|
|
164
|
+
?.find(o => o.type === 'message')
|
|
165
|
+
?.content?.find(c => c.type === 'output_text')
|
|
166
|
+
?.text;
|
|
167
|
+
const ok = result.statusCode === 200 && typeof outputText === 'string';
|
|
168
|
+
console.log(ok ? '✅ Working' : `❌ Failed (${result.statusCode})`);
|
|
157
169
|
} catch (e) {
|
|
158
170
|
console.log(`❌ ${e.message}`);
|
|
159
171
|
}
|
|
160
172
|
|
|
161
173
|
// Test Anthropic
|
|
162
|
-
process.stdout.write('
|
|
174
|
+
process.stdout.write('3. Anthropic Proxy (Claude): ');
|
|
163
175
|
try {
|
|
164
176
|
const result = await httpPost(
|
|
165
177
|
`${endpoints.anthropic}/messages`,
|
|
166
178
|
{ model: 'claude-sonnet-4-5-20250929', messages: [{ role: 'user', content: 'Say OK' }], max_tokens: 10 },
|
|
167
179
|
{ 'Grazie-Authenticate-JWT': token, 'anthropic-version': '2023-06-01' }
|
|
168
180
|
);
|
|
169
|
-
|
|
181
|
+
const ok = result.statusCode === 200 && Array.isArray(result.json?.content);
|
|
182
|
+
console.log(ok ? '✅ Working' : `❌ Failed (${result.statusCode})`);
|
|
170
183
|
} catch (e) {
|
|
171
184
|
console.log(`❌ ${e.message}`);
|
|
172
185
|
}
|
|
173
186
|
|
|
174
187
|
// Test Google
|
|
175
|
-
process.stdout.write('
|
|
188
|
+
process.stdout.write('4. Google Proxy (Gemini): ');
|
|
176
189
|
try {
|
|
177
190
|
const result = await httpPost(
|
|
178
191
|
`${endpoints.google}/v1/projects/default/locations/default/publishers/google/models/gemini-2.5-flash:generateContent`,
|
|
179
192
|
{ contents: [{ role: 'user', parts: [{ text: 'Say OK' }] }] },
|
|
180
193
|
{ 'Grazie-Authenticate-JWT': token }
|
|
181
194
|
);
|
|
182
|
-
|
|
195
|
+
const ok = result.statusCode === 200 && Array.isArray(result.json?.candidates);
|
|
196
|
+
console.log(ok ? '✅ Working' : `❌ Failed (${result.statusCode})`);
|
|
183
197
|
} catch (e) {
|
|
184
198
|
console.log(`❌ ${e.message}`);
|
|
185
199
|
}
|
|
@@ -201,14 +215,16 @@ function httpPost(url, body, headers) {
|
|
|
201
215
|
...headers
|
|
202
216
|
}
|
|
203
217
|
}, (res) => {
|
|
204
|
-
let
|
|
205
|
-
res.on('data', chunk =>
|
|
218
|
+
let rawBody = '';
|
|
219
|
+
res.on('data', chunk => rawBody += chunk);
|
|
206
220
|
res.on('end', () => {
|
|
221
|
+
let json = null;
|
|
207
222
|
try {
|
|
208
|
-
|
|
223
|
+
json = JSON.parse(rawBody);
|
|
209
224
|
} catch {
|
|
210
|
-
|
|
225
|
+
// Some proxy errors are plain text.
|
|
211
226
|
}
|
|
227
|
+
resolve({ statusCode: res.statusCode, json, rawBody });
|
|
212
228
|
});
|
|
213
229
|
});
|
|
214
230
|
|
|
@@ -227,43 +243,32 @@ function showModels() {
|
|
|
227
243
|
console.log(` - ${m}${def}`);
|
|
228
244
|
});
|
|
229
245
|
|
|
230
|
-
console.log('\nGPT (OpenAI) - jbai-
|
|
246
|
+
console.log('\nGPT (OpenAI Chat) - jbai-aider, jbai-opencode:');
|
|
231
247
|
config.MODELS.openai.available.forEach((m) => {
|
|
232
248
|
const def = m === config.MODELS.openai.default ? ' (default)' : '';
|
|
233
249
|
console.log(` - ${m}${def}`);
|
|
234
250
|
});
|
|
235
251
|
|
|
252
|
+
console.log('\nCodex (OpenAI Responses) - jbai-codex:');
|
|
253
|
+
config.MODELS.codex.available.forEach((m) => {
|
|
254
|
+
const def = m === config.MODELS.codex.default ? ' (default)' : '';
|
|
255
|
+
console.log(` - ${m}${def}`);
|
|
256
|
+
});
|
|
257
|
+
|
|
236
258
|
console.log('\nGemini (Google) - jbai-gemini:');
|
|
237
259
|
config.MODELS.gemini.available.forEach((m) => {
|
|
238
260
|
const def = m === config.MODELS.gemini.default ? ' (default)' : '';
|
|
239
261
|
console.log(` - ${m}${def}`);
|
|
240
262
|
});
|
|
241
263
|
|
|
242
|
-
console.log('\nDeepSeek - jbai-aider, jbai-opencode:');
|
|
243
|
-
config.MODELS.other.deepseek.forEach((m) => console.log(` - ${m}`));
|
|
244
|
-
|
|
245
|
-
console.log('\nMistral - jbai-aider, jbai-opencode:');
|
|
246
|
-
config.MODELS.other.mistral.forEach((m) => console.log(` - ${m}`));
|
|
247
|
-
|
|
248
|
-
console.log('\nQwen - jbai-aider, jbai-opencode:');
|
|
249
|
-
config.MODELS.other.qwen.forEach((m) => console.log(` - ${m}`));
|
|
250
|
-
|
|
251
|
-
console.log('\nXAI (Grok) - jbai-aider, jbai-opencode:');
|
|
252
|
-
config.MODELS.other.xai.forEach((m) => console.log(` - ${m}`));
|
|
253
|
-
|
|
254
|
-
console.log('\nMeta (Llama) - jbai-aider, jbai-opencode:');
|
|
255
|
-
config.MODELS.other.meta.forEach((m) => console.log(` - ${m}`));
|
|
256
|
-
|
|
257
264
|
// Count total
|
|
258
265
|
const total = config.MODELS.claude.available.length +
|
|
259
266
|
config.MODELS.openai.available.length +
|
|
260
|
-
config.MODELS.
|
|
261
|
-
config.MODELS.
|
|
262
|
-
config.MODELS.other.mistral.length +
|
|
263
|
-
config.MODELS.other.qwen.length +
|
|
264
|
-
config.MODELS.other.xai.length +
|
|
265
|
-
config.MODELS.other.meta.length;
|
|
267
|
+
config.MODELS.codex.available.length +
|
|
268
|
+
config.MODELS.gemini.available.length;
|
|
266
269
|
console.log(`\nTotal: ${total} models`);
|
|
270
|
+
console.log('\nNote: Other providers (DeepSeek, Mistral, Qwen, XAI, Meta) are available');
|
|
271
|
+
console.log('via Grazie native API but not via OpenAI-compatible CLI tools.');
|
|
267
272
|
}
|
|
268
273
|
|
|
269
274
|
function setEnvironment(env) {
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* E2E Model Compatibility Testing Script
|
|
5
|
+
* Tests all configured Grazie models against the API endpoints
|
|
6
|
+
* to identify which models are valid and which return 404 or errors.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const https = require('https');
|
|
10
|
+
const config = require('../lib/config');
|
|
11
|
+
|
|
12
|
+
const token = config.getToken();
|
|
13
|
+
if (!token) {
|
|
14
|
+
console.error('❌ No token found. Run: jbai token set');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const endpoints = config.getEndpoints();
|
|
19
|
+
|
|
20
|
+
// Colors for terminal output
|
|
21
|
+
const colors = {
|
|
22
|
+
reset: '\x1b[0m',
|
|
23
|
+
green: '\x1b[32m',
|
|
24
|
+
red: '\x1b[31m',
|
|
25
|
+
yellow: '\x1b[33m',
|
|
26
|
+
cyan: '\x1b[36m',
|
|
27
|
+
dim: '\x1b[2m'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function httpPost(url, body, headers) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const urlObj = new URL(url);
|
|
33
|
+
const data = JSON.stringify(body);
|
|
34
|
+
|
|
35
|
+
const req = https.request({
|
|
36
|
+
hostname: urlObj.hostname,
|
|
37
|
+
port: 443,
|
|
38
|
+
path: urlObj.pathname + urlObj.search,
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'Content-Length': Buffer.byteLength(data),
|
|
43
|
+
...headers
|
|
44
|
+
},
|
|
45
|
+
timeout: 30000
|
|
46
|
+
}, (res) => {
|
|
47
|
+
let body = '';
|
|
48
|
+
res.on('data', chunk => body += chunk);
|
|
49
|
+
res.on('end', () => {
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(body);
|
|
52
|
+
resolve({ status: res.statusCode, data: parsed });
|
|
53
|
+
} catch {
|
|
54
|
+
resolve({ status: res.statusCode, data: body, raw: true });
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
req.on('error', (e) => reject(e));
|
|
60
|
+
req.on('timeout', () => {
|
|
61
|
+
req.destroy();
|
|
62
|
+
reject(new Error('Timeout'));
|
|
63
|
+
});
|
|
64
|
+
req.write(data);
|
|
65
|
+
req.end();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function testOpenAIModel(model) {
|
|
70
|
+
try {
|
|
71
|
+
// GPT-5.x and O-series models require max_completion_tokens instead of max_tokens
|
|
72
|
+
const needsCompletionTokens = model.startsWith('gpt-5') || model.startsWith('o1') ||
|
|
73
|
+
model.startsWith('o3') || model.startsWith('o4');
|
|
74
|
+
|
|
75
|
+
const bodyParams = needsCompletionTokens
|
|
76
|
+
? { max_completion_tokens: 500 } // O-series needs more tokens for reasoning
|
|
77
|
+
: { max_tokens: 5 };
|
|
78
|
+
|
|
79
|
+
const result = await httpPost(
|
|
80
|
+
`${endpoints.openai}/chat/completions`,
|
|
81
|
+
{
|
|
82
|
+
model: model,
|
|
83
|
+
messages: [{ role: 'user', content: 'Say OK' }],
|
|
84
|
+
...bodyParams
|
|
85
|
+
},
|
|
86
|
+
{ 'Grazie-Authenticate-JWT': token }
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (result.status === 200 && result.data.choices) {
|
|
90
|
+
return { success: true, message: 'OK' };
|
|
91
|
+
} else if (result.status === 404) {
|
|
92
|
+
return { success: false, message: `404 Not Found`, error: result.data.error?.message || 'Model not found' };
|
|
93
|
+
} else if (result.status === 400) {
|
|
94
|
+
return { success: false, message: `400 Bad Request`, error: result.data.error?.message || 'Invalid request' };
|
|
95
|
+
} else {
|
|
96
|
+
return { success: false, message: `Status ${result.status}`, error: result.data.error?.message || JSON.stringify(result.data).substring(0, 100) };
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
return { success: false, message: 'Error', error: e.message };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function testAnthropicModel(model) {
|
|
104
|
+
try {
|
|
105
|
+
const result = await httpPost(
|
|
106
|
+
`${endpoints.anthropic}/messages`,
|
|
107
|
+
{
|
|
108
|
+
model: model,
|
|
109
|
+
messages: [{ role: 'user', content: 'Say OK' }],
|
|
110
|
+
max_tokens: 10
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
'Grazie-Authenticate-JWT': token,
|
|
114
|
+
'anthropic-version': '2023-06-01'
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (result.status === 200 && result.data.content) {
|
|
119
|
+
return { success: true, message: 'OK' };
|
|
120
|
+
} else if (result.status === 404) {
|
|
121
|
+
return { success: false, message: `404 Not Found`, error: result.data.error?.message || 'Model not found' };
|
|
122
|
+
} else if (result.status === 400) {
|
|
123
|
+
return { success: false, message: `400 Bad Request`, error: result.data.error?.message || 'Invalid request' };
|
|
124
|
+
} else {
|
|
125
|
+
return { success: false, message: `Status ${result.status}`, error: result.data.error?.message || JSON.stringify(result.data).substring(0, 100) };
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
return { success: false, message: 'Error', error: e.message };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function testGeminiModel(model) {
|
|
133
|
+
try {
|
|
134
|
+
// Gemini API format via Vertex AI proxy
|
|
135
|
+
const result = await httpPost(
|
|
136
|
+
`${endpoints.google}/v1/projects/default/locations/default/publishers/google/models/${model}:generateContent`,
|
|
137
|
+
{
|
|
138
|
+
contents: [{ role: 'user', parts: [{ text: 'Say OK' }] }]
|
|
139
|
+
},
|
|
140
|
+
{ 'Grazie-Authenticate-JWT': token }
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if (result.status === 200 && result.data.candidates) {
|
|
144
|
+
return { success: true, message: 'OK' };
|
|
145
|
+
} else if (result.status === 404) {
|
|
146
|
+
return { success: false, message: `404 Not Found`, error: result.data.error?.message || 'Model not found' };
|
|
147
|
+
} else if (result.status === 400) {
|
|
148
|
+
return { success: false, message: `400 Bad Request`, error: result.data.error?.message || 'Invalid request' };
|
|
149
|
+
} else {
|
|
150
|
+
return { success: false, message: `Status ${result.status}`, error: result.data.error?.message || JSON.stringify(result.data).substring(0, 100) };
|
|
151
|
+
}
|
|
152
|
+
} catch (e) {
|
|
153
|
+
return { success: false, message: 'Error', error: e.message };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function runTests() {
|
|
158
|
+
console.log(`\n${'='.repeat(70)}`);
|
|
159
|
+
console.log(`${colors.cyan}JBAI-CLI E2E MODEL COMPATIBILITY TESTING${colors.reset}`);
|
|
160
|
+
console.log(`Environment: ${config.getEnvironment()}`);
|
|
161
|
+
console.log(`${'='.repeat(70)}\n`);
|
|
162
|
+
|
|
163
|
+
const results = {
|
|
164
|
+
openai: { working: [], failing: [] },
|
|
165
|
+
anthropic: { working: [], failing: [] },
|
|
166
|
+
gemini: { working: [], failing: [] }
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Test Claude models (Anthropic)
|
|
170
|
+
console.log(`${colors.cyan}Testing Claude models (jbai-claude)...${colors.reset}`);
|
|
171
|
+
console.log('-'.repeat(50));
|
|
172
|
+
for (const model of config.MODELS.claude.available) {
|
|
173
|
+
process.stdout.write(` ${model.padEnd(35)} `);
|
|
174
|
+
const result = await testAnthropicModel(model);
|
|
175
|
+
if (result.success) {
|
|
176
|
+
console.log(`${colors.green}✓ ${result.message}${colors.reset}`);
|
|
177
|
+
results.anthropic.working.push(model);
|
|
178
|
+
} else {
|
|
179
|
+
console.log(`${colors.red}✗ ${result.message}${colors.reset}`);
|
|
180
|
+
console.log(` ${colors.dim}${result.error}${colors.reset}`);
|
|
181
|
+
results.anthropic.failing.push({ model, error: result.error });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Test OpenAI models (Codex, OpenCode)
|
|
186
|
+
console.log(`\n${colors.cyan}Testing OpenAI/GPT models (jbai-codex, jbai-opencode)...${colors.reset}`);
|
|
187
|
+
console.log('-'.repeat(50));
|
|
188
|
+
for (const model of config.MODELS.openai.available) {
|
|
189
|
+
process.stdout.write(` ${model.padEnd(35)} `);
|
|
190
|
+
const result = await testOpenAIModel(model);
|
|
191
|
+
if (result.success) {
|
|
192
|
+
console.log(`${colors.green}✓ ${result.message}${colors.reset}`);
|
|
193
|
+
results.openai.working.push(model);
|
|
194
|
+
} else {
|
|
195
|
+
console.log(`${colors.red}✗ ${result.message}${colors.reset}`);
|
|
196
|
+
console.log(` ${colors.dim}${result.error}${colors.reset}`);
|
|
197
|
+
results.openai.failing.push({ model, error: result.error });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Test Gemini models
|
|
202
|
+
console.log(`\n${colors.cyan}Testing Gemini models (jbai-gemini)...${colors.reset}`);
|
|
203
|
+
console.log('-'.repeat(50));
|
|
204
|
+
for (const model of config.MODELS.gemini.available) {
|
|
205
|
+
process.stdout.write(` ${model.padEnd(35)} `);
|
|
206
|
+
const result = await testGeminiModel(model);
|
|
207
|
+
if (result.success) {
|
|
208
|
+
console.log(`${colors.green}✓ ${result.message}${colors.reset}`);
|
|
209
|
+
results.gemini.working.push(model);
|
|
210
|
+
} else {
|
|
211
|
+
console.log(`${colors.red}✗ ${result.message}${colors.reset}`);
|
|
212
|
+
console.log(` ${colors.dim}${result.error}${colors.reset}`);
|
|
213
|
+
results.gemini.failing.push({ model, error: result.error });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Summary
|
|
218
|
+
console.log(`\n${'='.repeat(70)}`);
|
|
219
|
+
console.log(`${colors.cyan}SUMMARY${colors.reset}`);
|
|
220
|
+
console.log(`${'='.repeat(70)}`);
|
|
221
|
+
|
|
222
|
+
const totalWorking = results.anthropic.working.length + results.openai.working.length +
|
|
223
|
+
results.gemini.working.length;
|
|
224
|
+
const totalFailing = results.anthropic.failing.length + results.openai.failing.length +
|
|
225
|
+
results.gemini.failing.length;
|
|
226
|
+
|
|
227
|
+
console.log(`\n${colors.green}Working Models: ${totalWorking}${colors.reset}`);
|
|
228
|
+
console.log(`${colors.red}Failing Models: ${totalFailing}${colors.reset}`);
|
|
229
|
+
|
|
230
|
+
console.log(`\n${colors.cyan}By Provider:${colors.reset}`);
|
|
231
|
+
console.log(` Claude (Anthropic): ${colors.green}${results.anthropic.working.length}${colors.reset} working, ${colors.red}${results.anthropic.failing.length}${colors.reset} failing`);
|
|
232
|
+
console.log(` OpenAI/GPT: ${colors.green}${results.openai.working.length}${colors.reset} working, ${colors.red}${results.openai.failing.length}${colors.reset} failing`);
|
|
233
|
+
console.log(` Gemini (Google): ${colors.green}${results.gemini.working.length}${colors.reset} working, ${colors.red}${results.gemini.failing.length}${colors.reset} failing`);
|
|
234
|
+
|
|
235
|
+
if (totalFailing > 0) {
|
|
236
|
+
console.log(`\n${colors.red}FAILING MODELS TO REMOVE:${colors.reset}`);
|
|
237
|
+
|
|
238
|
+
if (results.anthropic.failing.length > 0) {
|
|
239
|
+
console.log(`\n Claude models:`);
|
|
240
|
+
results.anthropic.failing.forEach(f => console.log(` - ${f.model}`));
|
|
241
|
+
}
|
|
242
|
+
if (results.openai.failing.length > 0) {
|
|
243
|
+
console.log(`\n OpenAI models:`);
|
|
244
|
+
results.openai.failing.forEach(f => console.log(` - ${f.model}`));
|
|
245
|
+
}
|
|
246
|
+
if (results.gemini.failing.length > 0) {
|
|
247
|
+
console.log(`\n Gemini models:`);
|
|
248
|
+
results.gemini.failing.forEach(f => console.log(` - ${f.model}`));
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
console.log(`\n${colors.green}All models are working correctly!${colors.reset}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(`\n${'='.repeat(70)}\n`);
|
|
255
|
+
|
|
256
|
+
// Return results for programmatic use
|
|
257
|
+
return results;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Run tests
|
|
261
|
+
runTests().catch(console.error);
|
package/lib/config.js
CHANGED
|
@@ -26,91 +26,91 @@ const ENDPOINTS = {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
// All models available from JetBrains AI Platform (Grazie)
|
|
29
|
-
// Model names
|
|
29
|
+
// Model names must match EXACTLY what the Grazie API accepts
|
|
30
|
+
// Run 'node bin/test-models.js' to verify model availability
|
|
30
31
|
const MODELS = {
|
|
31
32
|
claude: {
|
|
32
33
|
default: 'claude-sonnet-4-5-20250929',
|
|
33
34
|
available: [
|
|
34
|
-
// Claude 4.
|
|
35
|
-
'claude-opus-4-5-
|
|
35
|
+
// Claude 4.5 series (latest)
|
|
36
|
+
'claude-opus-4-5-20251101',
|
|
36
37
|
'claude-sonnet-4-5-20250929',
|
|
37
|
-
'claude-haiku-4-5-
|
|
38
|
+
'claude-haiku-4-5-20251001',
|
|
39
|
+
// Claude 4.x series
|
|
38
40
|
'claude-opus-4-1-20250805',
|
|
39
41
|
'claude-opus-4-20250514',
|
|
40
42
|
'claude-sonnet-4-20250514',
|
|
41
43
|
// Claude 3.x series
|
|
42
44
|
'claude-3-7-sonnet-20250219',
|
|
43
|
-
'claude-3-5-
|
|
44
|
-
'claude-3-5-haiku-20241022',
|
|
45
|
-
'claude-3-haiku-20240307'
|
|
45
|
+
'claude-3-5-haiku-20241022'
|
|
46
46
|
]
|
|
47
47
|
},
|
|
48
48
|
openai: {
|
|
49
|
-
|
|
49
|
+
// Chat/Completions models (used by Aider/OpenCode)
|
|
50
|
+
// Keep in sync with the OpenAI proxy's advertised list.
|
|
51
|
+
default: 'gpt-4o-2024-11-20',
|
|
50
52
|
available: [
|
|
51
|
-
// GPT-5.x series (latest)
|
|
52
|
-
'gpt-5',
|
|
53
|
-
'gpt-5.1',
|
|
53
|
+
// GPT-5.x series (latest) - require date-versioned names
|
|
54
|
+
'gpt-5.2-2025-12-11',
|
|
54
55
|
'gpt-5.2',
|
|
55
|
-
'gpt-5-
|
|
56
|
-
'gpt-5-
|
|
56
|
+
'gpt-5.1-2025-11-13',
|
|
57
|
+
'gpt-5-2025-08-07',
|
|
58
|
+
'gpt-5-mini-2025-08-07',
|
|
59
|
+
'gpt-5-nano-2025-08-07',
|
|
60
|
+
// GPT-4.1 series
|
|
61
|
+
'gpt-4.1-2025-04-14',
|
|
62
|
+
'gpt-4.1-mini-2025-04-14',
|
|
63
|
+
'gpt-4.1-nano-2025-04-14',
|
|
64
|
+
// GPT-4o/4-turbo
|
|
65
|
+
'gpt-4o-2024-11-20',
|
|
66
|
+
'gpt-4o-mini-2024-07-18',
|
|
67
|
+
'gpt-4-turbo-2024-04-09',
|
|
68
|
+
'gpt-4-0613',
|
|
69
|
+
// O-series (reasoning) - use max_completion_tokens instead of max_tokens
|
|
70
|
+
'o4-mini-2025-04-16',
|
|
71
|
+
'o3-2025-04-16',
|
|
72
|
+
'o3-mini-2025-01-31',
|
|
73
|
+
'o1-2024-12-17',
|
|
74
|
+
// Legacy
|
|
75
|
+
'gpt-3.5-turbo-0125'
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
// Codex CLI uses the OpenAI "responses" API (wire_api = "responses")
|
|
79
|
+
codex: {
|
|
80
|
+
default: 'gpt-5.2-codex',
|
|
81
|
+
available: [
|
|
57
82
|
'gpt-5-codex',
|
|
58
83
|
'gpt-5.1-codex',
|
|
59
|
-
'gpt-5.1-codex-max',
|
|
60
84
|
'gpt-5.1-codex-mini',
|
|
61
|
-
'gpt-5.
|
|
62
|
-
'gpt-5.2-
|
|
63
|
-
// GPT-4.x series
|
|
64
|
-
'gpt-4.1',
|
|
65
|
-
'gpt-4.1-mini',
|
|
66
|
-
'gpt-4.1-nano',
|
|
67
|
-
'gpt-4o',
|
|
68
|
-
'gpt-4o-mini',
|
|
69
|
-
'gpt-4-turbo',
|
|
70
|
-
'gpt-4',
|
|
71
|
-
// O-series (reasoning)
|
|
72
|
-
'o1',
|
|
73
|
-
'o3',
|
|
74
|
-
'o3-mini',
|
|
75
|
-
'o4-mini'
|
|
85
|
+
'gpt-5.1-codex-max',
|
|
86
|
+
'gpt-5.2-codex'
|
|
76
87
|
]
|
|
77
88
|
},
|
|
78
89
|
gemini: {
|
|
79
90
|
default: 'gemini-2.5-flash',
|
|
80
91
|
available: [
|
|
81
|
-
// Gemini 3.x (
|
|
82
|
-
'gemini-3
|
|
83
|
-
'gemini-3
|
|
84
|
-
// Gemini 2.
|
|
92
|
+
// Gemini 3.x (preview)
|
|
93
|
+
'gemini-3-pro-preview',
|
|
94
|
+
'gemini-3-flash-preview',
|
|
95
|
+
// Gemini 2.5
|
|
85
96
|
'gemini-2.5-pro',
|
|
86
97
|
'gemini-2.5-flash',
|
|
87
98
|
'gemini-2.5-flash-lite',
|
|
88
|
-
|
|
89
|
-
'gemini-2.0-flash-
|
|
99
|
+
// Gemini 2.0
|
|
100
|
+
'gemini-2.0-flash-001',
|
|
101
|
+
'gemini-2.0-flash-lite-001'
|
|
90
102
|
]
|
|
91
|
-
},
|
|
92
|
-
// Other providers available via Grazie
|
|
93
|
-
other: {
|
|
94
|
-
deepseek: ['deepseek-r1'],
|
|
95
|
-
mistral: ['mistral-large', 'mistral-small', 'mistral-codestral', 'mistral-open-7b', 'mixtral-open-8x7b', 'mixtral-open-8x22b'],
|
|
96
|
-
qwen: ['qwen-max', 'qwen-max-longcontext', 'qwen-plus', 'qwen-turbo'],
|
|
97
|
-
xai: ['xai-grok-4', 'xai-grok-4-fast', 'xai-grok-4-1-fast', 'xai-grok-code-fast-1'],
|
|
98
|
-
meta: ['grazie-chat-llama-v3-8b-instruct']
|
|
99
103
|
}
|
|
104
|
+
// NOTE: Other providers (DeepSeek, Mistral, Qwen, XAI, Meta) are available
|
|
105
|
+
// via Grazie native Chat API but NOT via the OpenAI-compatible proxy.
|
|
106
|
+
// They are not supported by CLI tools that use OpenAI API format.
|
|
100
107
|
};
|
|
101
108
|
|
|
102
|
-
// All models for tools that support multiple providers (
|
|
109
|
+
// All models for tools that support multiple providers (OpenCode, Codex)
|
|
103
110
|
const ALL_MODELS = {
|
|
104
111
|
openai: MODELS.openai.available,
|
|
105
112
|
anthropic: MODELS.claude.available,
|
|
106
|
-
gemini: MODELS.gemini.available
|
|
107
|
-
other: [
|
|
108
|
-
...MODELS.other.deepseek,
|
|
109
|
-
...MODELS.other.mistral,
|
|
110
|
-
...MODELS.other.qwen,
|
|
111
|
-
...MODELS.other.xai,
|
|
112
|
-
...MODELS.other.meta
|
|
113
|
-
]
|
|
113
|
+
gemini: MODELS.gemini.available
|
|
114
114
|
};
|
|
115
115
|
|
|
116
116
|
function ensureConfigDir() {
|
|
@@ -190,11 +190,6 @@ const TOOLS = {
|
|
|
190
190
|
command: 'codex',
|
|
191
191
|
install: 'npm install -g @openai/codex'
|
|
192
192
|
},
|
|
193
|
-
aider: {
|
|
194
|
-
name: 'Aider',
|
|
195
|
-
command: 'aider',
|
|
196
|
-
install: 'brew install aider'
|
|
197
|
-
},
|
|
198
193
|
gemini: {
|
|
199
194
|
name: 'Gemini CLI',
|
|
200
195
|
command: 'gemini',
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jbai-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "CLI wrappers to use AI coding tools (Claude Code, Codex,
|
|
3
|
+
"version": "1.5.1",
|
|
4
|
+
"description": "CLI wrappers to use AI coding tools (Claude Code, Codex, Gemini CLI, OpenCode) with JetBrains AI Platform",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jetbrains",
|
|
7
7
|
"ai",
|
|
8
8
|
"claude",
|
|
9
9
|
"codex",
|
|
10
|
-
"aider",
|
|
11
10
|
"gemini",
|
|
12
11
|
"opencode",
|
|
13
12
|
"cli",
|
|
@@ -29,7 +28,6 @@
|
|
|
29
28
|
"jbai": "./bin/jbai.js",
|
|
30
29
|
"jbai-claude": "./bin/jbai-claude.js",
|
|
31
30
|
"jbai-codex": "./bin/jbai-codex.js",
|
|
32
|
-
"jbai-aider": "./bin/jbai-aider.js",
|
|
33
31
|
"jbai-gemini": "./bin/jbai-gemini.js",
|
|
34
32
|
"jbai-opencode": "./bin/jbai-opencode.js"
|
|
35
33
|
},
|
|
@@ -41,9 +39,7 @@
|
|
|
41
39
|
"engines": {
|
|
42
40
|
"node": ">=18.0.0"
|
|
43
41
|
},
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"yaml": "^2.7.1"
|
|
46
|
-
},
|
|
42
|
+
"dependencies": {},
|
|
47
43
|
"scripts": {
|
|
48
44
|
"postinstall": "node lib/postinstall.js",
|
|
49
45
|
"test": "node bin/jbai.js test"
|
package/bin/jbai-aider.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const { spawn } = require('child_process');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
const yaml = require('yaml');
|
|
8
|
-
const config = require('../lib/config');
|
|
9
|
-
|
|
10
|
-
const token = config.getToken();
|
|
11
|
-
if (!token) {
|
|
12
|
-
console.error('❌ No token found. Run: jbai token set');
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (config.isTokenExpired(token)) {
|
|
17
|
-
console.error('⚠️ Token expired. Run: jbai token refresh');
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const endpoints = config.getEndpoints();
|
|
22
|
-
const environment = config.getEnvironment();
|
|
23
|
-
let args = process.argv.slice(2);
|
|
24
|
-
|
|
25
|
-
// Check for super mode (--super, --yolo, -s)
|
|
26
|
-
const superFlags = ['--super', '--yolo', '-s'];
|
|
27
|
-
const superMode = args.some(a => superFlags.includes(a));
|
|
28
|
-
args = args.filter(a => !superFlags.includes(a));
|
|
29
|
-
|
|
30
|
-
// Create model settings file with extra headers for JetBrains authentication
|
|
31
|
-
const configDir = path.join(os.homedir(), '.jbai');
|
|
32
|
-
const modelSettingsFile = path.join(configDir, 'aider-model-settings.yml');
|
|
33
|
-
|
|
34
|
-
// Ensure config directory exists
|
|
35
|
-
if (!fs.existsSync(configDir)) {
|
|
36
|
-
fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Create model settings with extra_headers for authentication
|
|
40
|
-
// Use aider/extra_params for global settings that apply to all models
|
|
41
|
-
const modelSettings = [
|
|
42
|
-
{
|
|
43
|
-
name: 'aider/extra_params',
|
|
44
|
-
extra_params: {
|
|
45
|
-
extra_headers: {
|
|
46
|
-
'Grazie-Authenticate-JWT': token
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
// Write model settings file
|
|
53
|
-
fs.writeFileSync(modelSettingsFile, yaml.stringify(modelSettings), { mode: 0o600 });
|
|
54
|
-
|
|
55
|
-
// Build aider arguments
|
|
56
|
-
// Note: --no-stream is required because JetBrains proxy has stream_options issues
|
|
57
|
-
const hasModel = args.includes('--model');
|
|
58
|
-
const aiderArgs = [
|
|
59
|
-
'--openai-api-base', endpoints.openai,
|
|
60
|
-
'--openai-api-key', 'placeholder',
|
|
61
|
-
'--model-settings-file', modelSettingsFile,
|
|
62
|
-
'--no-stream'
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
if (!hasModel) {
|
|
66
|
-
aiderArgs.push('--model', config.MODELS.openai.default);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Add super mode flags (auto-confirm all)
|
|
70
|
-
if (superMode) {
|
|
71
|
-
aiderArgs.push('--yes');
|
|
72
|
-
console.log('🚀 Super mode: --yes (auto-confirm) enabled');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
aiderArgs.push(...args);
|
|
76
|
-
|
|
77
|
-
const child = spawn('aider', aiderArgs, {
|
|
78
|
-
stdio: 'inherit',
|
|
79
|
-
env: process.env
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
child.on('error', (err) => {
|
|
83
|
-
if (err.code === 'ENOENT') {
|
|
84
|
-
const tool = config.TOOLS.aider;
|
|
85
|
-
console.error(`❌ ${tool.name} not found.\n`);
|
|
86
|
-
console.error(`Install with: ${tool.install}`);
|
|
87
|
-
console.error(`Or run: jbai install aider`);
|
|
88
|
-
} else {
|
|
89
|
-
console.error(`Error: ${err.message}`);
|
|
90
|
-
}
|
|
91
|
-
process.exit(1);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
child.on('exit', (code) => process.exit(code || 0));
|