hedgequantx 2.5.24 → 2.5.25
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 +87 -14
- package/package.json +1 -1
- package/src/menus/ai-agent.js +25 -185
- package/src/services/ai/client.js +3 -2
- package/src/services/ai/index.js +10 -40
- package/src/services/ai/providers/index.js +2 -11
- package/src/services/ai/token-scanner.js +0 -1414
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
### Prop Futures Algo Trading CLI
|
|
8
8
|
|
|
9
|
-
*Connect to
|
|
9
|
+
*Connect to 38+ prop firms and automate your futures trading with AI supervision*
|
|
10
10
|
|
|
11
11
|
[](https://hedgequantx.com)
|
|
12
12
|
[](https://www.npmjs.com/package/hedgequantx)
|
|
@@ -21,10 +21,11 @@
|
|
|
21
21
|
|
|
22
22
|
[](https://hedgequantx.com)
|
|
23
23
|
[](https://hedgequantx.com)
|
|
24
|
-
[](https://hedgequantx.com)
|
|
25
|
+
[](https://hedgequantx.com)
|
|
25
26
|
[](https://hedgequantx.com)
|
|
26
27
|
|
|
27
|
-
[Website](https://hedgequantx.com) | [Installation](#installation) | [Features](#features) | [Algo Trading](#algo-trading) | [Discord](https://discord.gg/UBKCERctZu)
|
|
28
|
+
[Website](https://hedgequantx.com) | [Installation](#installation) | [Features](#features) | [AI Integration](#ai-integration) | [Algo Trading](#algo-trading) | [Discord](https://discord.gg/UBKCERctZu)
|
|
28
29
|
|
|
29
30
|
</div>
|
|
30
31
|
|
|
@@ -34,12 +35,13 @@
|
|
|
34
35
|
|
|
35
36
|
| Feature | Description |
|
|
36
37
|
|---------|-------------|
|
|
37
|
-
| **Multi-Platform** | ProjectX &
|
|
38
|
-
| **
|
|
38
|
+
| **Multi-Platform** | ProjectX, Rithmic & Tradovate APIs |
|
|
39
|
+
| **38+ Prop Firms** | TopStep, Apex, Bulenox, and more |
|
|
39
40
|
| **Multi-Account** | Connect multiple accounts simultaneously |
|
|
40
41
|
| **Real-Time Stats** | Balance, P&L, positions, orders |
|
|
41
42
|
| **Algo Trading** | One Account & Copy Trading modes |
|
|
42
|
-
| **
|
|
43
|
+
| **AI Supervision** | Claude, GPT, Gemini, and 15+ providers |
|
|
44
|
+
| **Claude Pro/Max OAuth** | Login with your Claude subscription |
|
|
43
45
|
| **Market Hours** | Auto-blocks when market closed |
|
|
44
46
|
| **Local Execution** | Direct API trading, no server needed |
|
|
45
47
|
| **Secure Storage** | AES-256-GCM encrypted sessions |
|
|
@@ -95,6 +97,55 @@ hqx --version
|
|
|
95
97
|
|
|
96
98
|
---
|
|
97
99
|
|
|
100
|
+
## AI Integration
|
|
101
|
+
|
|
102
|
+
HedgeQuantX integrates with leading AI providers for intelligent trading supervision.
|
|
103
|
+
|
|
104
|
+
### Supported AI Providers
|
|
105
|
+
|
|
106
|
+
| Provider | Authentication | Models |
|
|
107
|
+
|----------|---------------|--------|
|
|
108
|
+
| **Anthropic Claude** | OAuth (Pro/Max) or API Key | Claude 4, Sonnet, Haiku, Opus |
|
|
109
|
+
| **OpenAI** | API Key | GPT-4o, GPT-4, GPT-3.5 |
|
|
110
|
+
| **Google Gemini** | API Key | Gemini Pro, Gemini Flash |
|
|
111
|
+
| **OpenRouter** | API Key | 100+ models (unified API) |
|
|
112
|
+
| **DeepSeek** | API Key | DeepSeek Chat, Coder |
|
|
113
|
+
| **Groq** | API Key | Llama, Mixtral (fast inference) |
|
|
114
|
+
| **xAI** | API Key | Grok |
|
|
115
|
+
| **Mistral** | API Key | Mistral Large, Medium, Small |
|
|
116
|
+
| **Perplexity** | API Key | Sonar models |
|
|
117
|
+
| **Together AI** | API Key | Open source models |
|
|
118
|
+
| **Ollama** | Local | Any local model |
|
|
119
|
+
| **LM Studio** | Local | Any local model |
|
|
120
|
+
|
|
121
|
+
### Claude Pro/Max OAuth Login
|
|
122
|
+
|
|
123
|
+
If you have a Claude Pro or Max subscription, you can login directly without an API key:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
1. AI Agent Menu -> [+] Add Agent
|
|
127
|
+
2. Select DIRECT PROVIDERS -> CLAUDE (ANTHROPIC)
|
|
128
|
+
3. Choose "CLAUDE PRO/MAX (OAUTH)"
|
|
129
|
+
4. Browser opens -> Login to claude.ai
|
|
130
|
+
5. Copy the authorization code
|
|
131
|
+
6. Paste in terminal -> Connected!
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Benefits:
|
|
135
|
+
- No API key needed
|
|
136
|
+
- Use your existing subscription
|
|
137
|
+
- Unlimited usage with your plan
|
|
138
|
+
- Tokens auto-refresh
|
|
139
|
+
|
|
140
|
+
### AI Features
|
|
141
|
+
|
|
142
|
+
- **Trading Analysis**: Real-time position and risk analysis
|
|
143
|
+
- **Multi-Agent Support**: Connect multiple AI providers simultaneously
|
|
144
|
+
- **Auto Token Scanner**: Detects existing AI sessions from VS Code, Cursor, Claude CLI
|
|
145
|
+
- **Token Auto-Refresh**: OAuth tokens refresh automatically when expired
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
98
149
|
## Algo Trading
|
|
99
150
|
|
|
100
151
|
### One Account Mode
|
|
@@ -107,12 +158,18 @@ Trade on a single account with HQX algo strategy.
|
|
|
107
158
|
|
|
108
159
|
### Copy Trading Mode
|
|
109
160
|
|
|
110
|
-
Mirror trades from Lead to Follower accounts.
|
|
161
|
+
Mirror trades from Lead to Follower accounts with real execution.
|
|
111
162
|
|
|
112
163
|
<div align="center">
|
|
113
164
|
<img src="assets/copy-trading.png" alt="HQX Copy Trading" width="800">
|
|
114
165
|
</div>
|
|
115
166
|
|
|
167
|
+
Features:
|
|
168
|
+
- Real order execution via API
|
|
169
|
+
- Position synchronization
|
|
170
|
+
- Multi-account support
|
|
171
|
+
- Configurable lot multiplier
|
|
172
|
+
|
|
116
173
|
---
|
|
117
174
|
|
|
118
175
|
## Supported Prop Firms
|
|
@@ -151,13 +208,27 @@ Mirror trades from Lead to Follower accounts.
|
|
|
151
208
|
| Rate Limiting | API protection |
|
|
152
209
|
| File Permissions | 0600 (owner only) |
|
|
153
210
|
| Credentials | Never stored in plain text |
|
|
211
|
+
| OAuth Tokens | Secure PKCE flow |
|
|
154
212
|
|
|
155
213
|
---
|
|
156
214
|
|
|
157
215
|
## Changelog
|
|
158
216
|
|
|
159
217
|
<details>
|
|
160
|
-
<summary><b>
|
|
218
|
+
<summary><b>v2.5.x (Current)</b></summary>
|
|
219
|
+
|
|
220
|
+
- **AI Integration**: Multi-provider AI supervision
|
|
221
|
+
- **Claude OAuth**: Login with Pro/Max subscription (no API key needed)
|
|
222
|
+
- **Token Scanner**: Auto-detect AI sessions from VS Code, Cursor, Claude CLI
|
|
223
|
+
- **Real API Models**: Fetch models from provider APIs (no hardcoded lists)
|
|
224
|
+
- **Copy Trading**: Real order execution via API
|
|
225
|
+
- **Multi-Agent**: Connect multiple AI providers simultaneously
|
|
226
|
+
- **Auto Token Refresh**: OAuth tokens refresh automatically
|
|
227
|
+
|
|
228
|
+
</details>
|
|
229
|
+
|
|
230
|
+
<details>
|
|
231
|
+
<summary><b>v1.8.x</b></summary>
|
|
161
232
|
|
|
162
233
|
- Separate UI for One Account and Copy Trading
|
|
163
234
|
- Market hours validation
|
|
@@ -194,12 +265,14 @@ Mirror trades from Lead to Follower accounts.
|
|
|
194
265
|
|
|
195
266
|
| Done | Done | Coming Soon |
|
|
196
267
|
|------|------|-------------|
|
|
197
|
-
| :white_check_mark: ProjectX integration | :white_check_mark: One Account mode | :hourglass:
|
|
198
|
-
| :white_check_mark: Rithmic integration | :white_check_mark: Copy Trading mode | :hourglass:
|
|
199
|
-
| :white_check_mark: 38+ prop firms | :white_check_mark: HQX Server | :hourglass:
|
|
200
|
-
| :white_check_mark: Multi-account | :white_check_mark: Market hours check | :hourglass:
|
|
201
|
-
| :white_check_mark: Trailing SL & BE | :white_check_mark: Session summary | :hourglass:
|
|
202
|
-
| :white_check_mark: Encrypted sessions | :white_check_mark: Auto-update | :hourglass:
|
|
268
|
+
| :white_check_mark: ProjectX integration | :white_check_mark: One Account mode | :hourglass: Telegram alerts |
|
|
269
|
+
| :white_check_mark: Rithmic integration | :white_check_mark: Copy Trading mode | :hourglass: Multi-symbol trading |
|
|
270
|
+
| :white_check_mark: 38+ prop firms | :white_check_mark: HQX Server | :hourglass: Performance analytics |
|
|
271
|
+
| :white_check_mark: Multi-account | :white_check_mark: Market hours check | :hourglass: Trade journal export |
|
|
272
|
+
| :white_check_mark: Trailing SL & BE | :white_check_mark: Session summary | :hourglass: Web dashboard |
|
|
273
|
+
| :white_check_mark: Encrypted sessions | :white_check_mark: Auto-update | :hourglass: Advanced AI strategies |
|
|
274
|
+
| :white_check_mark: AI Integration | :white_check_mark: Claude OAuth | :hourglass: Voice commands |
|
|
275
|
+
| :white_check_mark: Multi-AI Agents | :white_check_mark: Token Scanner | |
|
|
203
276
|
|
|
204
277
|
---
|
|
205
278
|
|
package/package.json
CHANGED
package/src/menus/ai-agent.js
CHANGED
|
@@ -10,7 +10,6 @@ const { getLogoWidth, drawBoxHeader, drawBoxHeaderContinue, drawBoxFooter, displ
|
|
|
10
10
|
const { prompts } = require('../utils');
|
|
11
11
|
const aiService = require('../services/ai');
|
|
12
12
|
const { getCategories, getProvidersByCategory } = require('../services/ai/providers');
|
|
13
|
-
const tokenScanner = require('../services/ai/token-scanner');
|
|
14
13
|
const oauthAnthropic = require('../services/ai/oauth-anthropic');
|
|
15
14
|
|
|
16
15
|
/**
|
|
@@ -118,7 +117,7 @@ const aiAgentMenu = async () => {
|
|
|
118
117
|
|
|
119
118
|
switch (input) {
|
|
120
119
|
case '+':
|
|
121
|
-
return await
|
|
120
|
+
return await selectCategory();
|
|
122
121
|
case 's':
|
|
123
122
|
if (agentCount > 1) {
|
|
124
123
|
return await selectActiveAgent();
|
|
@@ -367,178 +366,6 @@ const selectAgentToRemove = async () => {
|
|
|
367
366
|
return await aiAgentMenu();
|
|
368
367
|
};
|
|
369
368
|
|
|
370
|
-
// Cache for scanned tokens (avoid multiple Keychain prompts)
|
|
371
|
-
let cachedTokens = null;
|
|
372
|
-
let cacheTimestamp = 0;
|
|
373
|
-
const CACHE_TTL = 60000; // 1 minute cache
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Show existing tokens found on the system
|
|
377
|
-
*/
|
|
378
|
-
const showExistingTokens = async () => {
|
|
379
|
-
const boxWidth = getLogoWidth();
|
|
380
|
-
const W = boxWidth - 2;
|
|
381
|
-
|
|
382
|
-
const makeLine = (content) => {
|
|
383
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
384
|
-
const padding = W - plainLen;
|
|
385
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
// Check cache first
|
|
389
|
-
const now = Date.now();
|
|
390
|
-
let tokens;
|
|
391
|
-
|
|
392
|
-
if (cachedTokens && (now - cacheTimestamp) < CACHE_TTL) {
|
|
393
|
-
tokens = cachedTokens;
|
|
394
|
-
} else {
|
|
395
|
-
console.clear();
|
|
396
|
-
displayBanner();
|
|
397
|
-
drawBoxHeaderContinue('SCANNING FOR EXISTING SESSIONS...', boxWidth);
|
|
398
|
-
console.log(makeLine(''));
|
|
399
|
-
console.log(makeLine(chalk.gray('CHECKING VS CODE, CURSOR, CLAUDE CLI, OPENCODE...')));
|
|
400
|
-
console.log(makeLine(''));
|
|
401
|
-
drawBoxFooter(boxWidth);
|
|
402
|
-
|
|
403
|
-
// Scan for tokens and cache
|
|
404
|
-
tokens = tokenScanner.scanAllSources();
|
|
405
|
-
cachedTokens = tokens;
|
|
406
|
-
cacheTimestamp = now;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (tokens.length === 0) {
|
|
410
|
-
// No tokens found, go directly to category selection
|
|
411
|
-
return await selectCategory();
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Show found tokens
|
|
415
|
-
console.clear();
|
|
416
|
-
displayBanner();
|
|
417
|
-
drawBoxHeaderContinue('EXISTING SESSIONS FOUND', boxWidth);
|
|
418
|
-
|
|
419
|
-
console.log(makeLine(chalk.green(`FOUND ${tokens.length} EXISTING SESSION(S)`)));
|
|
420
|
-
console.log(makeLine(''));
|
|
421
|
-
|
|
422
|
-
const formatted = tokenScanner.formatResults(tokens);
|
|
423
|
-
|
|
424
|
-
for (const t of formatted) {
|
|
425
|
-
const providerColor = t.provider.includes('CLAUDE') ? chalk.magenta :
|
|
426
|
-
t.provider.includes('OPENAI') ? chalk.green :
|
|
427
|
-
t.provider.includes('OPENROUTER') ? chalk.yellow : chalk.cyan;
|
|
428
|
-
|
|
429
|
-
console.log(makeLine(
|
|
430
|
-
chalk.white(`[${t.index}] `) +
|
|
431
|
-
providerColor(t.provider) +
|
|
432
|
-
chalk.gray(` (${t.type})`)
|
|
433
|
-
));
|
|
434
|
-
console.log(makeLine(
|
|
435
|
-
chalk.gray(` ${t.icon} ${t.source} - ${t.lastUsed}`)
|
|
436
|
-
));
|
|
437
|
-
console.log(makeLine(
|
|
438
|
-
chalk.gray(` TOKEN: ${t.tokenPreview}`)
|
|
439
|
-
));
|
|
440
|
-
console.log(makeLine(''));
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
444
|
-
console.log(makeLine(chalk.cyan('[N] CONNECT NEW PROVIDER')));
|
|
445
|
-
console.log(makeLine(chalk.gray('[<] BACK')));
|
|
446
|
-
|
|
447
|
-
drawBoxFooter(boxWidth);
|
|
448
|
-
|
|
449
|
-
const choice = await prompts.textInput(chalk.cyan('SELECT (1-' + tokens.length + '/N/<):'));
|
|
450
|
-
|
|
451
|
-
if (choice === '<' || choice?.toLowerCase() === 'b') {
|
|
452
|
-
return await aiAgentMenu();
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (choice?.toLowerCase() === 'n') {
|
|
456
|
-
return await selectCategory();
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const index = parseInt(choice) - 1;
|
|
460
|
-
if (isNaN(index) || index < 0 || index >= tokens.length) {
|
|
461
|
-
return await showExistingTokens();
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Use selected token
|
|
465
|
-
const selectedToken = tokens[index];
|
|
466
|
-
|
|
467
|
-
const spinner = ora({ text: 'VALIDATING TOKEN...', color: 'cyan' }).start();
|
|
468
|
-
|
|
469
|
-
try {
|
|
470
|
-
// Validate the token - include metadata from scanner
|
|
471
|
-
const credentials = {
|
|
472
|
-
apiKey: selectedToken.token,
|
|
473
|
-
sessionKey: selectedToken.token,
|
|
474
|
-
accessToken: selectedToken.token,
|
|
475
|
-
fromKeychain: selectedToken.sourceId === 'secureStorage' || selectedToken.sourceId === 'keychain',
|
|
476
|
-
subscriptionType: selectedToken.subscriptionType,
|
|
477
|
-
refreshToken: selectedToken.refreshToken,
|
|
478
|
-
expiresAt: selectedToken.expiresAt
|
|
479
|
-
};
|
|
480
|
-
const validation = await aiService.validateConnection(selectedToken.provider, selectedToken.type, credentials);
|
|
481
|
-
|
|
482
|
-
if (!validation.valid) {
|
|
483
|
-
spinner.fail(`TOKEN INVALID OR EXPIRED: ${validation.error}`);
|
|
484
|
-
await prompts.waitForEnter();
|
|
485
|
-
return await showExistingTokens();
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Get provider info
|
|
489
|
-
const { getProvider } = require('../services/ai/providers');
|
|
490
|
-
const provider = getProvider(selectedToken.provider);
|
|
491
|
-
|
|
492
|
-
if (!provider) {
|
|
493
|
-
spinner.fail('PROVIDER NOT SUPPORTED');
|
|
494
|
-
await prompts.waitForEnter();
|
|
495
|
-
return await showExistingTokens();
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
spinner.text = 'FETCHING AVAILABLE MODELS...';
|
|
499
|
-
|
|
500
|
-
// Fetch models from API with the token
|
|
501
|
-
const { fetchAnthropicModels, fetchOpenAIModels } = require('../services/ai/client');
|
|
502
|
-
|
|
503
|
-
let models = null;
|
|
504
|
-
if (selectedToken.provider === 'anthropic') {
|
|
505
|
-
models = await fetchAnthropicModels(credentials.apiKey);
|
|
506
|
-
} else {
|
|
507
|
-
models = await fetchOpenAIModels(provider.endpoint, credentials.apiKey);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
if (!models || models.length === 0) {
|
|
511
|
-
spinner.fail('COULD NOT FETCH MODELS FROM API');
|
|
512
|
-
await prompts.waitForEnter();
|
|
513
|
-
return await showExistingTokens();
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
spinner.succeed(`FOUND ${models.length} MODELS`);
|
|
517
|
-
|
|
518
|
-
// Let user select model
|
|
519
|
-
const selectedModel = await selectModelFromList(models, provider.name);
|
|
520
|
-
if (!selectedModel) {
|
|
521
|
-
return await showExistingTokens();
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// Add agent with selected model
|
|
525
|
-
const agentName = `${provider.name} (${selectedToken.source})`;
|
|
526
|
-
await aiService.addAgent(selectedToken.provider, 'api_key', credentials, selectedModel, agentName);
|
|
527
|
-
|
|
528
|
-
console.log(chalk.green(`\n AGENT ADDED: ${provider.name}`));
|
|
529
|
-
console.log(chalk.gray(` SOURCE: ${selectedToken.source}`));
|
|
530
|
-
console.log(chalk.gray(` MODEL: ${selectedModel}`));
|
|
531
|
-
|
|
532
|
-
await prompts.waitForEnter();
|
|
533
|
-
return await aiAgentMenu();
|
|
534
|
-
|
|
535
|
-
} catch (error) {
|
|
536
|
-
spinner.fail(`CONNECTION FAILED: ${error.message}`);
|
|
537
|
-
await prompts.waitForEnter();
|
|
538
|
-
return await showExistingTokens();
|
|
539
|
-
}
|
|
540
|
-
};
|
|
541
|
-
|
|
542
369
|
/**
|
|
543
370
|
* Select provider category
|
|
544
371
|
*/
|
|
@@ -937,16 +764,18 @@ const setupOAuthConnection = async (provider) => {
|
|
|
937
764
|
spinner.succeed(`FOUND ${models.length} MODELS`);
|
|
938
765
|
}
|
|
939
766
|
|
|
767
|
+
if (!models || models.length === 0) {
|
|
768
|
+
spinner.fail('COULD NOT FETCH MODELS FROM API');
|
|
769
|
+
console.log(chalk.gray(' OAuth authentication may not support model listing.'));
|
|
770
|
+
console.log(chalk.gray(' Please use API KEY authentication instead.'));
|
|
771
|
+
await prompts.waitForEnter();
|
|
772
|
+
return await selectProviderOption(provider);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
spinner.succeed(`FOUND ${models.length} MODELS`);
|
|
776
|
+
|
|
940
777
|
// Let user select model
|
|
941
|
-
const
|
|
942
|
-
'claude-sonnet-4-20250514',
|
|
943
|
-
'claude-sonnet-4-5-20250514',
|
|
944
|
-
'claude-3-5-sonnet-20241022',
|
|
945
|
-
'claude-3-5-haiku-20241022',
|
|
946
|
-
'claude-3-opus-20240229'
|
|
947
|
-
];
|
|
948
|
-
|
|
949
|
-
const selectedModel = await selectModelFromList(availableModels, 'CLAUDE PRO/MAX');
|
|
778
|
+
const selectedModel = await selectModelFromList(models, 'CLAUDE PRO/MAX');
|
|
950
779
|
if (!selectedModel) {
|
|
951
780
|
return await selectProviderOption(provider);
|
|
952
781
|
}
|
|
@@ -1173,13 +1002,24 @@ const selectModel = async (agent) => {
|
|
|
1173
1002
|
drawBoxFooter(boxWidth);
|
|
1174
1003
|
|
|
1175
1004
|
// Fetch models from real API
|
|
1176
|
-
const { fetchAnthropicModels, fetchOpenAIModels } = require('../services/ai/client');
|
|
1005
|
+
const { fetchAnthropicModels, fetchAnthropicModelsOAuth, fetchOpenAIModels } = require('../services/ai/client');
|
|
1177
1006
|
|
|
1178
1007
|
let models = null;
|
|
1179
1008
|
const agentCredentials = aiService.getAgentCredentials(agent.id);
|
|
1180
1009
|
|
|
1181
1010
|
if (agent.providerId === 'anthropic') {
|
|
1182
|
-
|
|
1011
|
+
// Check if OAuth credentials or OAuth-like token (sk-ant-oat...)
|
|
1012
|
+
const token = agentCredentials?.apiKey || agentCredentials?.accessToken || agentCredentials?.sessionKey;
|
|
1013
|
+
const isOAuthToken = agentCredentials?.oauth?.access || (token && token.startsWith('sk-ant-oat'));
|
|
1014
|
+
|
|
1015
|
+
if (isOAuthToken) {
|
|
1016
|
+
// Use OAuth endpoint with Bearer token
|
|
1017
|
+
const accessToken = agentCredentials?.oauth?.access || token;
|
|
1018
|
+
models = await fetchAnthropicModelsOAuth(accessToken);
|
|
1019
|
+
} else {
|
|
1020
|
+
// Standard API key
|
|
1021
|
+
models = await fetchAnthropicModels(token);
|
|
1022
|
+
}
|
|
1183
1023
|
} else {
|
|
1184
1024
|
// OpenAI-compatible providers
|
|
1185
1025
|
const endpoint = agentCredentials?.endpoint || agent.provider?.endpoint;
|
|
@@ -357,11 +357,12 @@ const fetchAnthropicModelsOAuth = async (accessToken) => {
|
|
|
357
357
|
try {
|
|
358
358
|
const response = await makeRequest(url, { method: 'GET', headers, timeout: 10000 });
|
|
359
359
|
if (response.data && Array.isArray(response.data)) {
|
|
360
|
-
|
|
360
|
+
const models = response.data.map(m => m.id).filter(Boolean);
|
|
361
|
+
if (models.length > 0) return models;
|
|
361
362
|
}
|
|
362
363
|
return null;
|
|
363
364
|
} catch (error) {
|
|
364
|
-
// OAuth may not support /models endpoint
|
|
365
|
+
// OAuth token may not support /models endpoint
|
|
365
366
|
return null;
|
|
366
367
|
}
|
|
367
368
|
};
|
package/src/services/ai/index.js
CHANGED
|
@@ -374,58 +374,28 @@ const validateConnection = async (providerId, optionId, credentials) => {
|
|
|
374
374
|
const validateAnthropic = async (credentials) => {
|
|
375
375
|
try {
|
|
376
376
|
const token = credentials.apiKey || credentials.sessionKey || credentials.accessToken;
|
|
377
|
+
if (!token) return { valid: false, error: 'No API key provided' };
|
|
377
378
|
|
|
378
|
-
//
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
if (isOAuthToken) {
|
|
382
|
-
// OAuth tokens (from Claude Max/Pro subscription) cannot be validated via public API
|
|
383
|
-
// They use a different authentication flow through claude.ai
|
|
384
|
-
// Trust them if they have the correct format (sk-ant-oatXX-...)
|
|
385
|
-
if (token.length > 50 && /^sk-ant-oat\d{2}-[a-zA-Z0-9_-]+$/.test(token)) {
|
|
386
|
-
return {
|
|
387
|
-
valid: true,
|
|
388
|
-
tokenType: 'oauth',
|
|
389
|
-
subscriptionType: credentials.subscriptionType || 'max',
|
|
390
|
-
trusted: credentials.fromKeychain || false
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return { valid: false, error: 'Invalid OAuth token format' };
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Standard API key validation (sk-ant-api...)
|
|
398
|
-
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
399
|
-
method: 'POST',
|
|
379
|
+
// Validate by fetching models from API - this proves the token works
|
|
380
|
+
const response = await fetch('https://api.anthropic.com/v1/models', {
|
|
381
|
+
method: 'GET',
|
|
400
382
|
headers: {
|
|
401
|
-
'Content-Type': 'application/json',
|
|
402
383
|
'x-api-key': token,
|
|
403
384
|
'anthropic-version': '2023-06-01'
|
|
404
|
-
}
|
|
405
|
-
body: JSON.stringify({
|
|
406
|
-
model: 'claude-sonnet-4-5-20250929',
|
|
407
|
-
max_tokens: 10,
|
|
408
|
-
messages: [{ role: 'user', content: 'Hi' }]
|
|
409
|
-
})
|
|
385
|
+
}
|
|
410
386
|
});
|
|
411
387
|
|
|
412
388
|
if (response.ok) {
|
|
413
|
-
|
|
389
|
+
const data = await response.json();
|
|
390
|
+
if (data.data && Array.isArray(data.data) && data.data.length > 0) {
|
|
391
|
+
return { valid: true, tokenType: 'api_key' };
|
|
392
|
+
}
|
|
393
|
+
return { valid: false, error: 'API returned no models' };
|
|
414
394
|
}
|
|
415
395
|
|
|
416
396
|
const error = await response.json();
|
|
417
397
|
return { valid: false, error: error.error?.message || 'Invalid API key' };
|
|
418
398
|
} catch (e) {
|
|
419
|
-
// Network error - if it's an OAuth token, still accept it (can't validate anyway)
|
|
420
|
-
const token = credentials.apiKey || credentials.sessionKey || credentials.accessToken;
|
|
421
|
-
if (token && token.startsWith('sk-ant-oat') && token.length > 50) {
|
|
422
|
-
return {
|
|
423
|
-
valid: true,
|
|
424
|
-
tokenType: 'oauth',
|
|
425
|
-
subscriptionType: credentials.subscriptionType || 'max',
|
|
426
|
-
warning: 'Could not validate online (network error), but token format is valid'
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
399
|
return { valid: false, error: e.message };
|
|
430
400
|
}
|
|
431
401
|
};
|
|
@@ -10,17 +10,8 @@ const PROVIDERS = {
|
|
|
10
10
|
name: 'OPENROUTER (RECOMMENDED)',
|
|
11
11
|
description: '1 API key for 100+ models',
|
|
12
12
|
category: 'unified',
|
|
13
|
-
models: [
|
|
14
|
-
|
|
15
|
-
'anthropic/claude-3-opus',
|
|
16
|
-
'openai/gpt-4o',
|
|
17
|
-
'openai/gpt-4-turbo',
|
|
18
|
-
'google/gemini-pro-1.5',
|
|
19
|
-
'meta-llama/llama-3-70b',
|
|
20
|
-
'mistralai/mistral-large',
|
|
21
|
-
'deepseek/deepseek-chat'
|
|
22
|
-
],
|
|
23
|
-
defaultModel: 'anthropic/claude-sonnet-4',
|
|
13
|
+
models: [], // Fetched from API at runtime
|
|
14
|
+
defaultModel: null, // Will use first model from API
|
|
24
15
|
options: [
|
|
25
16
|
{
|
|
26
17
|
id: 'api_key',
|