hedgequantx 2.5.23 → 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 CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ### Prop Futures Algo Trading CLI
8
8
 
9
- *Connect to 37+ prop firms and automate your futures trading*
9
+ *Connect to 38+ prop firms and automate your futures trading with AI supervision*
10
10
 
11
11
  [![Website](https://img.shields.io/badge/Website-hedgequantx.com-00D4AA?style=for-the-badge&logo=google-chrome&logoColor=white)](https://hedgequantx.com)
12
12
  [![npm version](https://img.shields.io/npm/v/hedgequantx?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/hedgequantx)
@@ -21,10 +21,11 @@
21
21
 
22
22
  [![Futures](https://img.shields.io/badge/Futures-Trading-F7931A?style=flat-square&logo=bitcoin&logoColor=white)](https://hedgequantx.com)
23
23
  [![Algo](https://img.shields.io/badge/Algo-Trading-00D4AA?style=flat-square&logo=probot&logoColor=white)](https://hedgequantx.com)
24
- [![Prop Firms](https://img.shields.io/badge/37+-Prop%20Firms-8B5CF6?style=flat-square&logo=building&logoColor=white)](https://hedgequantx.com)
24
+ [![AI Powered](https://img.shields.io/badge/AI-Powered-8B5CF6?style=flat-square&logo=openai&logoColor=white)](https://hedgequantx.com)
25
+ [![Prop Firms](https://img.shields.io/badge/38+-Prop%20Firms-8B5CF6?style=flat-square&logo=building&logoColor=white)](https://hedgequantx.com)
25
26
  [![Secure](https://img.shields.io/badge/AES--256-Encrypted-EF4444?style=flat-square&logo=shield&logoColor=white)](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 & Rithmic APIs |
38
- | **37+ Prop Firms** | TopStep, Apex, Bulenox, and more |
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
- | **Algo Trading** | Proprietary HQX Strategy |
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>v1.8.x (Current)</b></summary>
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: Tradovate integration |
198
- | :white_check_mark: Rithmic integration | :white_check_mark: Copy Trading mode | :hourglass: Telegram alerts |
199
- | :white_check_mark: 38+ prop firms | :white_check_mark: HQX Server | :hourglass: Multi-symbol trading |
200
- | :white_check_mark: Multi-account | :white_check_mark: Market hours check | :hourglass: Performance analytics |
201
- | :white_check_mark: Trailing SL & BE | :white_check_mark: Session summary | :hourglass: Trade journal export |
202
- | :white_check_mark: Encrypted sessions | :white_check_mark: Auto-update | :hourglass: Web dashboard |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.5.23",
3
+ "version": "2.5.25",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -10,7 +10,7 @@ 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');
13
+ const oauthAnthropic = require('../services/ai/oauth-anthropic');
14
14
 
15
15
  /**
16
16
  * Main AI Agent menu
@@ -117,7 +117,7 @@ const aiAgentMenu = async () => {
117
117
 
118
118
  switch (input) {
119
119
  case '+':
120
- return await showExistingTokens();
120
+ return await selectCategory();
121
121
  case 's':
122
122
  if (agentCount > 1) {
123
123
  return await selectActiveAgent();
@@ -366,178 +366,6 @@ const selectAgentToRemove = async () => {
366
366
  return await aiAgentMenu();
367
367
  };
368
368
 
369
- // Cache for scanned tokens (avoid multiple Keychain prompts)
370
- let cachedTokens = null;
371
- let cacheTimestamp = 0;
372
- const CACHE_TTL = 60000; // 1 minute cache
373
-
374
- /**
375
- * Show existing tokens found on the system
376
- */
377
- const showExistingTokens = async () => {
378
- const boxWidth = getLogoWidth();
379
- const W = boxWidth - 2;
380
-
381
- const makeLine = (content) => {
382
- const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
383
- const padding = W - plainLen;
384
- return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
385
- };
386
-
387
- // Check cache first
388
- const now = Date.now();
389
- let tokens;
390
-
391
- if (cachedTokens && (now - cacheTimestamp) < CACHE_TTL) {
392
- tokens = cachedTokens;
393
- } else {
394
- console.clear();
395
- displayBanner();
396
- drawBoxHeaderContinue('SCANNING FOR EXISTING SESSIONS...', boxWidth);
397
- console.log(makeLine(''));
398
- console.log(makeLine(chalk.gray('CHECKING VS CODE, CURSOR, CLAUDE CLI, OPENCODE...')));
399
- console.log(makeLine(''));
400
- drawBoxFooter(boxWidth);
401
-
402
- // Scan for tokens and cache
403
- tokens = tokenScanner.scanAllSources();
404
- cachedTokens = tokens;
405
- cacheTimestamp = now;
406
- }
407
-
408
- if (tokens.length === 0) {
409
- // No tokens found, go directly to category selection
410
- return await selectCategory();
411
- }
412
-
413
- // Show found tokens
414
- console.clear();
415
- displayBanner();
416
- drawBoxHeaderContinue('EXISTING SESSIONS FOUND', boxWidth);
417
-
418
- console.log(makeLine(chalk.green(`FOUND ${tokens.length} EXISTING SESSION(S)`)));
419
- console.log(makeLine(''));
420
-
421
- const formatted = tokenScanner.formatResults(tokens);
422
-
423
- for (const t of formatted) {
424
- const providerColor = t.provider.includes('CLAUDE') ? chalk.magenta :
425
- t.provider.includes('OPENAI') ? chalk.green :
426
- t.provider.includes('OPENROUTER') ? chalk.yellow : chalk.cyan;
427
-
428
- console.log(makeLine(
429
- chalk.white(`[${t.index}] `) +
430
- providerColor(t.provider) +
431
- chalk.gray(` (${t.type})`)
432
- ));
433
- console.log(makeLine(
434
- chalk.gray(` ${t.icon} ${t.source} - ${t.lastUsed}`)
435
- ));
436
- console.log(makeLine(
437
- chalk.gray(` TOKEN: ${t.tokenPreview}`)
438
- ));
439
- console.log(makeLine(''));
440
- }
441
-
442
- console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
443
- console.log(makeLine(chalk.cyan('[N] CONNECT NEW PROVIDER')));
444
- console.log(makeLine(chalk.gray('[<] BACK')));
445
-
446
- drawBoxFooter(boxWidth);
447
-
448
- const choice = await prompts.textInput(chalk.cyan('SELECT (1-' + tokens.length + '/N/<):'));
449
-
450
- if (choice === '<' || choice?.toLowerCase() === 'b') {
451
- return await aiAgentMenu();
452
- }
453
-
454
- if (choice?.toLowerCase() === 'n') {
455
- return await selectCategory();
456
- }
457
-
458
- const index = parseInt(choice) - 1;
459
- if (isNaN(index) || index < 0 || index >= tokens.length) {
460
- return await showExistingTokens();
461
- }
462
-
463
- // Use selected token
464
- const selectedToken = tokens[index];
465
-
466
- const spinner = ora({ text: 'VALIDATING TOKEN...', color: 'cyan' }).start();
467
-
468
- try {
469
- // Validate the token - include metadata from scanner
470
- const credentials = {
471
- apiKey: selectedToken.token,
472
- sessionKey: selectedToken.token,
473
- accessToken: selectedToken.token,
474
- fromKeychain: selectedToken.sourceId === 'secureStorage' || selectedToken.sourceId === 'keychain',
475
- subscriptionType: selectedToken.subscriptionType,
476
- refreshToken: selectedToken.refreshToken,
477
- expiresAt: selectedToken.expiresAt
478
- };
479
- const validation = await aiService.validateConnection(selectedToken.provider, selectedToken.type, credentials);
480
-
481
- if (!validation.valid) {
482
- spinner.fail(`TOKEN INVALID OR EXPIRED: ${validation.error}`);
483
- await prompts.waitForEnter();
484
- return await showExistingTokens();
485
- }
486
-
487
- // Get provider info
488
- const { getProvider } = require('../services/ai/providers');
489
- const provider = getProvider(selectedToken.provider);
490
-
491
- if (!provider) {
492
- spinner.fail('PROVIDER NOT SUPPORTED');
493
- await prompts.waitForEnter();
494
- return await showExistingTokens();
495
- }
496
-
497
- spinner.text = 'FETCHING AVAILABLE MODELS...';
498
-
499
- // Fetch models from API with the token
500
- const { fetchAnthropicModels, fetchOpenAIModels } = require('../services/ai/client');
501
-
502
- let models = null;
503
- if (selectedToken.provider === 'anthropic') {
504
- models = await fetchAnthropicModels(credentials.apiKey);
505
- } else {
506
- models = await fetchOpenAIModels(provider.endpoint, credentials.apiKey);
507
- }
508
-
509
- if (!models || models.length === 0) {
510
- spinner.fail('COULD NOT FETCH MODELS FROM API');
511
- await prompts.waitForEnter();
512
- return await showExistingTokens();
513
- }
514
-
515
- spinner.succeed(`FOUND ${models.length} MODELS`);
516
-
517
- // Let user select model
518
- const selectedModel = await selectModelFromList(models, provider.name);
519
- if (!selectedModel) {
520
- return await showExistingTokens();
521
- }
522
-
523
- // Add agent with selected model
524
- const agentName = `${provider.name} (${selectedToken.source})`;
525
- await aiService.addAgent(selectedToken.provider, 'api_key', credentials, selectedModel, agentName);
526
-
527
- console.log(chalk.green(`\n AGENT ADDED: ${provider.name}`));
528
- console.log(chalk.gray(` SOURCE: ${selectedToken.source}`));
529
- console.log(chalk.gray(` MODEL: ${selectedModel}`));
530
-
531
- await prompts.waitForEnter();
532
- return await aiAgentMenu();
533
-
534
- } catch (error) {
535
- spinner.fail(`CONNECTION FAILED: ${error.message}`);
536
- await prompts.waitForEnter();
537
- return await showExistingTokens();
538
- }
539
- };
540
-
541
369
  /**
542
370
  * Select provider category
543
371
  */
@@ -845,10 +673,137 @@ const getCredentialInstructions = (provider, option, field) => {
845
673
  return instructions[field] || { title: field.toUpperCase(), steps: [] };
846
674
  };
847
675
 
676
+ /**
677
+ * Setup OAuth connection for Anthropic Claude Pro/Max
678
+ */
679
+ const setupOAuthConnection = async (provider) => {
680
+ const boxWidth = getLogoWidth();
681
+ const W = boxWidth - 2;
682
+
683
+ const makeLine = (content) => {
684
+ const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
685
+ const padding = W - plainLen;
686
+ return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
687
+ };
688
+
689
+ console.clear();
690
+ displayBanner();
691
+ drawBoxHeaderContinue('CLAUDE PRO/MAX LOGIN', boxWidth);
692
+
693
+ console.log(makeLine(chalk.yellow('OAUTH AUTHENTICATION')));
694
+ console.log(makeLine(''));
695
+ console.log(makeLine(chalk.white('1. A BROWSER WINDOW WILL OPEN')));
696
+ console.log(makeLine(chalk.white('2. LOGIN WITH YOUR CLAUDE ACCOUNT')));
697
+ console.log(makeLine(chalk.white('3. COPY THE AUTHORIZATION CODE')));
698
+ console.log(makeLine(chalk.white('4. PASTE IT HERE')));
699
+ console.log(makeLine(''));
700
+ console.log(makeLine(chalk.gray('OPENING BROWSER IN 3 SECONDS...')));
701
+
702
+ drawBoxFooter(boxWidth);
703
+
704
+ // Generate OAuth URL
705
+ const { url, verifier } = oauthAnthropic.authorize('max');
706
+
707
+ // Wait a moment then open browser
708
+ await new Promise(resolve => setTimeout(resolve, 3000));
709
+ openBrowser(url);
710
+
711
+ // Redraw with code input
712
+ console.clear();
713
+ displayBanner();
714
+ drawBoxHeaderContinue('CLAUDE PRO/MAX LOGIN', boxWidth);
715
+
716
+ console.log(makeLine(chalk.green('BROWSER OPENED')));
717
+ console.log(makeLine(''));
718
+ console.log(makeLine(chalk.white('AFTER LOGGING IN, YOU WILL SEE A CODE')));
719
+ console.log(makeLine(chalk.white('COPY THE ENTIRE CODE AND PASTE IT BELOW')));
720
+ console.log(makeLine(''));
721
+ console.log(makeLine(chalk.gray('THE CODE LOOKS LIKE: abc123...#xyz789...')));
722
+ console.log(makeLine(''));
723
+ console.log(makeLine(chalk.gray('TYPE < TO CANCEL')));
724
+
725
+ drawBoxFooter(boxWidth);
726
+ console.log();
727
+
728
+ const code = await prompts.textInput(chalk.cyan('PASTE AUTHORIZATION CODE:'));
729
+
730
+ if (!code || code === '<') {
731
+ return await selectProviderOption(provider);
732
+ }
733
+
734
+ // Exchange code for tokens
735
+ const spinner = ora({ text: 'EXCHANGING CODE FOR TOKENS...', color: 'cyan' }).start();
736
+
737
+ const result = await oauthAnthropic.exchange(code.trim(), verifier);
738
+
739
+ if (result.type === 'failed') {
740
+ spinner.fail(`AUTHENTICATION FAILED: ${result.error || 'Invalid code'}`);
741
+ await prompts.waitForEnter();
742
+ return await selectProviderOption(provider);
743
+ }
744
+
745
+ spinner.text = 'FETCHING AVAILABLE MODELS...';
746
+
747
+ // Store OAuth credentials
748
+ const credentials = {
749
+ oauth: {
750
+ access: result.access,
751
+ refresh: result.refresh,
752
+ expires: result.expires
753
+ }
754
+ };
755
+
756
+ // Fetch models using OAuth token
757
+ const { fetchAnthropicModelsOAuth } = require('../services/ai/client');
758
+ const models = await fetchAnthropicModelsOAuth(result.access);
759
+
760
+ if (!models || models.length === 0) {
761
+ // Use default models if API doesn't return list
762
+ spinner.warn('COULD NOT FETCH MODEL LIST, USING DEFAULTS');
763
+ } else {
764
+ spinner.succeed(`FOUND ${models.length} MODELS`);
765
+ }
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
+
777
+ // Let user select model
778
+ const selectedModel = await selectModelFromList(models, 'CLAUDE PRO/MAX');
779
+ if (!selectedModel) {
780
+ return await selectProviderOption(provider);
781
+ }
782
+
783
+ // Add agent with OAuth credentials
784
+ try {
785
+ await aiService.addAgent('anthropic', 'oauth_max', credentials, selectedModel, 'Claude Pro/Max');
786
+
787
+ console.log(chalk.green('\n CONNECTED TO CLAUDE PRO/MAX'));
788
+ console.log(chalk.gray(` MODEL: ${selectedModel}`));
789
+ console.log(chalk.gray(' UNLIMITED USAGE WITH YOUR SUBSCRIPTION'));
790
+ } catch (error) {
791
+ console.log(chalk.red(`\n FAILED TO SAVE: ${error.message}`));
792
+ }
793
+
794
+ await prompts.waitForEnter();
795
+ return await aiAgentMenu();
796
+ };
797
+
848
798
  /**
849
799
  * Setup connection with credentials
850
800
  */
851
801
  const setupConnection = async (provider, option) => {
802
+ // Handle OAuth flow separately
803
+ if (option.authType === 'oauth') {
804
+ return await setupOAuthConnection(provider);
805
+ }
806
+
852
807
  const boxWidth = getLogoWidth();
853
808
  const W = boxWidth - 2;
854
809
 
@@ -1047,13 +1002,24 @@ const selectModel = async (agent) => {
1047
1002
  drawBoxFooter(boxWidth);
1048
1003
 
1049
1004
  // Fetch models from real API
1050
- const { fetchAnthropicModels, fetchOpenAIModels } = require('../services/ai/client');
1005
+ const { fetchAnthropicModels, fetchAnthropicModelsOAuth, fetchOpenAIModels } = require('../services/ai/client');
1051
1006
 
1052
1007
  let models = null;
1053
1008
  const agentCredentials = aiService.getAgentCredentials(agent.id);
1054
1009
 
1055
1010
  if (agent.providerId === 'anthropic') {
1056
- models = await fetchAnthropicModels(agentCredentials?.apiKey);
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
+ }
1057
1023
  } else {
1058
1024
  // OpenAI-compatible providers
1059
1025
  const endpoint = agentCredentials?.endpoint || agent.provider?.endpoint;
@@ -105,8 +105,32 @@ const callOpenAICompatible = async (agent, prompt, systemPrompt) => {
105
105
  }
106
106
  };
107
107
 
108
+ /**
109
+ * Get valid OAuth token (refresh if needed)
110
+ * @param {Object} credentials - Agent credentials with oauth data
111
+ * @returns {Promise<string|null>} Valid access token or null
112
+ */
113
+ const getValidOAuthToken = async (credentials) => {
114
+ if (!credentials?.oauth) return null;
115
+
116
+ const oauthAnthropic = require('./oauth-anthropic');
117
+ const validToken = await oauthAnthropic.getValidToken(credentials.oauth);
118
+
119
+ if (!validToken) return null;
120
+
121
+ // If token was refreshed, we should update storage (handled by caller)
122
+ if (validToken.refreshed) {
123
+ credentials.oauth.access = validToken.access;
124
+ credentials.oauth.refresh = validToken.refresh;
125
+ credentials.oauth.expires = validToken.expires;
126
+ }
127
+
128
+ return validToken.access;
129
+ };
130
+
108
131
  /**
109
132
  * Call Anthropic Claude API
133
+ * Supports both API key and OAuth authentication
110
134
  * @param {Object} agent - Agent configuration
111
135
  * @param {string} prompt - User prompt
112
136
  * @param {string} systemPrompt - System prompt
@@ -116,19 +140,31 @@ const callAnthropic = async (agent, prompt, systemPrompt) => {
116
140
  const provider = getProvider('anthropic');
117
141
  if (!provider) return null;
118
142
 
119
- const apiKey = agent.credentials?.apiKey;
120
143
  const model = agent.model || provider.defaultModel;
121
-
122
- if (!apiKey) return null;
123
-
124
144
  const url = `${provider.endpoint}/messages`;
125
145
 
126
- const headers = {
146
+ // Determine authentication method
147
+ const isOAuth = agent.credentials?.oauth?.refresh;
148
+ let headers = {
127
149
  'Content-Type': 'application/json',
128
- 'x-api-key': apiKey,
129
150
  'anthropic-version': '2023-06-01'
130
151
  };
131
152
 
153
+ if (isOAuth) {
154
+ // OAuth Bearer token authentication
155
+ const accessToken = await getValidOAuthToken(agent.credentials);
156
+ if (!accessToken) return null;
157
+
158
+ headers['Authorization'] = `Bearer ${accessToken}`;
159
+ headers['anthropic-beta'] = 'oauth-2025-04-20,interleaved-thinking-2025-05-14';
160
+ } else {
161
+ // Standard API key authentication
162
+ const apiKey = agent.credentials?.apiKey;
163
+ if (!apiKey) return null;
164
+
165
+ headers['x-api-key'] = apiKey;
166
+ }
167
+
132
168
  const body = {
133
169
  model,
134
170
  max_tokens: 500,
@@ -273,7 +309,7 @@ Analyze and provide recommendation.`;
273
309
  };
274
310
 
275
311
  /**
276
- * Fetch available models from Anthropic API
312
+ * Fetch available models from Anthropic API (API Key auth)
277
313
  * @param {string} apiKey - API key
278
314
  * @returns {Promise<Array|null>} Array of model IDs or null on error
279
315
  *
@@ -300,6 +336,37 @@ const fetchAnthropicModels = async (apiKey) => {
300
336
  }
301
337
  };
302
338
 
339
+ /**
340
+ * Fetch available models from Anthropic API (OAuth auth)
341
+ * @param {string} accessToken - OAuth access token
342
+ * @returns {Promise<Array|null>} Array of model IDs or null on error
343
+ *
344
+ * Data source: https://api.anthropic.com/v1/models (GET with Bearer token)
345
+ */
346
+ const fetchAnthropicModelsOAuth = async (accessToken) => {
347
+ if (!accessToken) return null;
348
+
349
+ const url = 'https://api.anthropic.com/v1/models';
350
+
351
+ const headers = {
352
+ 'Authorization': `Bearer ${accessToken}`,
353
+ 'anthropic-version': '2023-06-01',
354
+ 'anthropic-beta': 'oauth-2025-04-20'
355
+ };
356
+
357
+ try {
358
+ const response = await makeRequest(url, { method: 'GET', headers, timeout: 10000 });
359
+ if (response.data && Array.isArray(response.data)) {
360
+ const models = response.data.map(m => m.id).filter(Boolean);
361
+ if (models.length > 0) return models;
362
+ }
363
+ return null;
364
+ } catch (error) {
365
+ // OAuth token may not support /models endpoint
366
+ return null;
367
+ }
368
+ };
369
+
303
370
  /**
304
371
  * Fetch available models from OpenAI-compatible API
305
372
  * @param {string} endpoint - API endpoint
@@ -339,5 +406,7 @@ module.exports = {
339
406
  callAnthropic,
340
407
  callGemini,
341
408
  fetchAnthropicModels,
342
- fetchOpenAIModels
409
+ fetchAnthropicModelsOAuth,
410
+ fetchOpenAIModels,
411
+ getValidOAuthToken
343
412
  };