hedgequantx 2.6.163 → 2.7.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 +15 -88
- package/bin/cli.js +0 -11
- package/dist/lib/api.jsc +0 -0
- package/dist/lib/api2.jsc +0 -0
- package/dist/lib/core.jsc +0 -0
- package/dist/lib/core2.jsc +0 -0
- package/dist/lib/data.js +1 -1
- package/dist/lib/data.jsc +0 -0
- package/dist/lib/data2.jsc +0 -0
- package/dist/lib/decoder.jsc +0 -0
- package/dist/lib/m/mod1.jsc +0 -0
- package/dist/lib/m/mod2.jsc +0 -0
- package/dist/lib/n/r1.jsc +0 -0
- package/dist/lib/n/r2.jsc +0 -0
- package/dist/lib/n/r3.jsc +0 -0
- package/dist/lib/n/r4.jsc +0 -0
- package/dist/lib/n/r5.jsc +0 -0
- package/dist/lib/n/r6.jsc +0 -0
- package/dist/lib/n/r7.jsc +0 -0
- package/dist/lib/o/util1.jsc +0 -0
- package/dist/lib/o/util2.jsc +0 -0
- package/package.json +8 -5
- package/src/app.js +40 -162
- package/src/config/constants.js +31 -33
- package/src/config/propfirms.js +13 -217
- package/src/config/settings.js +0 -43
- package/src/lib/api.js +198 -0
- package/src/lib/api2.js +353 -0
- package/src/lib/core.js +539 -0
- package/src/lib/core2.js +341 -0
- package/src/lib/data.js +555 -0
- package/src/lib/data2.js +492 -0
- package/src/lib/decoder.js +599 -0
- package/src/lib/m/s1.js +804 -0
- package/src/lib/m/s2.js +34 -0
- package/src/lib/n/r1.js +454 -0
- package/src/lib/n/r2.js +514 -0
- package/src/lib/n/r3.js +631 -0
- package/src/lib/n/r4.js +401 -0
- package/src/lib/n/r5.js +335 -0
- package/src/lib/n/r6.js +425 -0
- package/src/lib/n/r7.js +530 -0
- package/src/lib/o/l1.js +44 -0
- package/src/lib/o/l2.js +427 -0
- package/src/lib/python-bridge.js +206 -0
- package/src/menus/connect.js +14 -176
- package/src/menus/dashboard.js +65 -110
- package/src/pages/accounts.js +18 -18
- package/src/pages/algo/copy-trading.js +210 -240
- package/src/pages/algo/index.js +41 -104
- package/src/pages/algo/one-account.js +386 -33
- package/src/pages/algo/ui.js +312 -151
- package/src/pages/orders.js +3 -3
- package/src/pages/positions.js +3 -3
- package/src/pages/stats/chart.js +74 -0
- package/src/pages/stats/display.js +228 -0
- package/src/pages/stats/index.js +236 -0
- package/src/pages/stats/metrics.js +213 -0
- package/src/pages/user.js +6 -6
- package/src/services/hqx-server/constants.js +55 -0
- package/src/services/hqx-server/index.js +401 -0
- package/src/services/hqx-server/latency.js +81 -0
- package/src/services/index.js +12 -3
- package/src/services/rithmic/accounts.js +7 -32
- package/src/services/rithmic/connection.js +1 -204
- package/src/services/rithmic/contracts.js +116 -99
- package/src/services/rithmic/handlers.js +21 -196
- package/src/services/rithmic/index.js +63 -120
- package/src/services/rithmic/market.js +31 -0
- package/src/services/rithmic/orders.js +5 -111
- package/src/services/rithmic/protobuf.js +384 -138
- package/src/services/session.js +22 -173
- package/src/ui/box.js +10 -18
- package/src/ui/index.js +1 -3
- package/src/ui/menu.js +1 -1
- package/src/utils/prompts.js +2 -2
- package/dist/lib/m/s1.js +0 -1
- package/src/menus/ai-agent-connect.js +0 -181
- package/src/menus/ai-agent-models.js +0 -219
- package/src/menus/ai-agent-oauth.js +0 -292
- package/src/menus/ai-agent-ui.js +0 -141
- package/src/menus/ai-agent.js +0 -484
- package/src/pages/algo/algo-config.js +0 -195
- package/src/pages/algo/algo-multi.js +0 -801
- package/src/pages/algo/algo-utils.js +0 -58
- package/src/pages/algo/copy-engine.js +0 -449
- package/src/pages/algo/custom-strategy.js +0 -459
- package/src/pages/algo/logger.js +0 -245
- package/src/pages/algo/smart-logs-data.js +0 -218
- package/src/pages/algo/smart-logs.js +0 -387
- package/src/pages/algo/ui-constants.js +0 -144
- package/src/pages/algo/ui-summary.js +0 -184
- package/src/pages/stats-calculations.js +0 -191
- package/src/pages/stats-ui.js +0 -381
- package/src/pages/stats.js +0 -339
- package/src/services/ai/client-analysis.js +0 -194
- package/src/services/ai/client-models.js +0 -333
- package/src/services/ai/client.js +0 -343
- package/src/services/ai/index.js +0 -384
- package/src/services/ai/oauth-anthropic.js +0 -265
- package/src/services/ai/oauth-gemini.js +0 -223
- package/src/services/ai/oauth-iflow.js +0 -269
- package/src/services/ai/oauth-openai.js +0 -233
- package/src/services/ai/oauth-qwen.js +0 -279
- package/src/services/ai/providers/direct-providers.js +0 -323
- package/src/services/ai/providers/index.js +0 -62
- package/src/services/ai/providers/other-providers.js +0 -104
- package/src/services/ai/proxy-install.js +0 -249
- package/src/services/ai/proxy-manager.js +0 -494
- package/src/services/ai/proxy-remote.js +0 -161
- package/src/services/ai/strategy-supervisor.js +0 -1312
- package/src/services/ai/supervisor-data.js +0 -195
- package/src/services/ai/supervisor-optimize.js +0 -215
- package/src/services/ai/supervisor-sync.js +0 -178
- package/src/services/ai/supervisor-utils.js +0 -158
- package/src/services/ai/supervisor.js +0 -484
- package/src/services/ai/validation.js +0 -250
- package/src/services/hqx-server-events.js +0 -110
- package/src/services/hqx-server-handlers.js +0 -217
- package/src/services/hqx-server-latency.js +0 -136
- package/src/services/hqx-server.js +0 -403
- package/src/services/position-constants.js +0 -28
- package/src/services/position-exit-logic.js +0 -174
- package/src/services/position-manager.js +0 -438
- package/src/services/position-momentum.js +0 -206
- package/src/services/projectx/accounts.js +0 -142
- package/src/services/projectx/index.js +0 -443
- package/src/services/projectx/market.js +0 -172
- package/src/services/projectx/stats.js +0 -110
- package/src/services/projectx/trading.js +0 -180
- package/src/services/rithmic/latency-tracker.js +0 -182
- package/src/services/rithmic/market-data-decoders.js +0 -229
- package/src/services/rithmic/market-data.js +0 -272
- package/src/services/rithmic/orders-fast.js +0 -246
- package/src/services/rithmic/proto-decoders.js +0 -403
- package/src/services/rithmic/specs.js +0 -146
- package/src/services/rithmic/trade-history.js +0 -254
- package/src/services/session-history.js +0 -475
- package/src/services/strategy/hft-signal-calc.js +0 -147
- package/src/services/strategy/hft-tick.js +0 -407
- package/src/services/strategy/recovery-math.js +0 -402
- package/src/services/tradovate/constants.js +0 -109
- package/src/services/tradovate/index.js +0 -392
- package/src/services/tradovate/market.js +0 -47
- package/src/services/tradovate/orders.js +0 -145
- package/src/services/tradovate/websocket.js +0 -97
package/src/services/ai/index.js
DELETED
|
@@ -1,384 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Service Manager
|
|
3
|
-
* Manages multiple AI provider connections
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { getProviders, getProvider } = require('./providers');
|
|
7
|
-
const { storage } = require('../session');
|
|
8
|
-
const AISupervisor = require('./supervisor');
|
|
9
|
-
const StrategySupervisor = require('./strategy-supervisor');
|
|
10
|
-
const { validateConnection } = require('./validation');
|
|
11
|
-
|
|
12
|
-
// In-memory cache of connections
|
|
13
|
-
let connectionsCache = null;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Get AI settings from storage
|
|
17
|
-
*/
|
|
18
|
-
const getAISettings = () => {
|
|
19
|
-
try {
|
|
20
|
-
const sessions = storage.load();
|
|
21
|
-
const aiSettings = sessions.find(s => s.type === 'ai') || { type: 'ai', agents: [] };
|
|
22
|
-
|
|
23
|
-
// Migrate old single-agent format to multi-agent
|
|
24
|
-
if (aiSettings.provider && !aiSettings.agents) {
|
|
25
|
-
return {
|
|
26
|
-
type: 'ai',
|
|
27
|
-
agents: [{
|
|
28
|
-
id: generateAgentId(),
|
|
29
|
-
provider: aiSettings.provider,
|
|
30
|
-
option: aiSettings.option,
|
|
31
|
-
credentials: aiSettings.credentials,
|
|
32
|
-
model: aiSettings.model,
|
|
33
|
-
name: getProvider(aiSettings.provider)?.name || 'AI Agent',
|
|
34
|
-
createdAt: Date.now()
|
|
35
|
-
}],
|
|
36
|
-
activeAgentId: null
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
return aiSettings;
|
|
40
|
-
} catch {
|
|
41
|
-
return { type: 'ai', agents: [] };
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Save AI settings to storage
|
|
47
|
-
*/
|
|
48
|
-
const saveAISettings = (aiSettings) => {
|
|
49
|
-
try {
|
|
50
|
-
const sessions = storage.load();
|
|
51
|
-
const otherSessions = sessions.filter(s => s.type !== 'ai');
|
|
52
|
-
|
|
53
|
-
aiSettings.type = 'ai';
|
|
54
|
-
otherSessions.push(aiSettings);
|
|
55
|
-
|
|
56
|
-
storage.save(otherSessions);
|
|
57
|
-
connectionsCache = null; // Invalidate cache
|
|
58
|
-
} catch (e) {
|
|
59
|
-
// Silent fail
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Generate unique agent ID
|
|
65
|
-
*/
|
|
66
|
-
const generateAgentId = () => {
|
|
67
|
-
return 'agent_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Get all connected agents
|
|
72
|
-
* IMPORTANT: Includes credentials for API calls in StrategySupervisor
|
|
73
|
-
*/
|
|
74
|
-
const getAgents = () => {
|
|
75
|
-
const aiSettings = getAISettings();
|
|
76
|
-
const agents = aiSettings.agents || [];
|
|
77
|
-
|
|
78
|
-
return agents.map(agent => {
|
|
79
|
-
const provider = getProvider(agent.provider);
|
|
80
|
-
return {
|
|
81
|
-
id: agent.id,
|
|
82
|
-
name: agent.name || provider?.name || 'Unknown',
|
|
83
|
-
provider: provider,
|
|
84
|
-
providerId: agent.provider,
|
|
85
|
-
option: agent.option,
|
|
86
|
-
model: agent.model || provider?.defaultModel,
|
|
87
|
-
credentials: agent.credentials, // CRITICAL: Required for API calls
|
|
88
|
-
createdAt: agent.createdAt,
|
|
89
|
-
isActive: agent.id === aiSettings.activeAgentId
|
|
90
|
-
};
|
|
91
|
-
}).filter(a => a.provider); // Filter out invalid providers
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Get agent count
|
|
96
|
-
*/
|
|
97
|
-
const getAgentCount = () => {
|
|
98
|
-
const aiSettings = getAISettings();
|
|
99
|
-
return (aiSettings.agents || []).length;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Check if any AI is connected
|
|
104
|
-
*/
|
|
105
|
-
const isConnected = () => {
|
|
106
|
-
return getAgentCount() > 0;
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Get active agent (or first agent if none active)
|
|
111
|
-
*/
|
|
112
|
-
const getActiveAgent = () => {
|
|
113
|
-
const aiSettings = getAISettings();
|
|
114
|
-
const agents = aiSettings.agents || [];
|
|
115
|
-
|
|
116
|
-
if (agents.length === 0) return null;
|
|
117
|
-
|
|
118
|
-
// Find active agent or use first one
|
|
119
|
-
const activeId = aiSettings.activeAgentId;
|
|
120
|
-
const agent = activeId
|
|
121
|
-
? agents.find(a => a.id === activeId)
|
|
122
|
-
: agents[0];
|
|
123
|
-
|
|
124
|
-
if (!agent) return null;
|
|
125
|
-
|
|
126
|
-
const provider = getProvider(agent.provider);
|
|
127
|
-
if (!provider) return null;
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
id: agent.id,
|
|
131
|
-
name: agent.name || provider.name,
|
|
132
|
-
provider: provider,
|
|
133
|
-
providerId: agent.provider,
|
|
134
|
-
option: agent.option,
|
|
135
|
-
model: agent.model || provider.defaultModel,
|
|
136
|
-
credentials: agent.credentials,
|
|
137
|
-
connected: true
|
|
138
|
-
};
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Get agent by ID
|
|
143
|
-
*/
|
|
144
|
-
const getAgent = (agentId) => {
|
|
145
|
-
const aiSettings = getAISettings();
|
|
146
|
-
const agents = aiSettings.agents || [];
|
|
147
|
-
const agent = agents.find(a => a.id === agentId);
|
|
148
|
-
|
|
149
|
-
if (!agent) return null;
|
|
150
|
-
|
|
151
|
-
const provider = getProvider(agent.provider);
|
|
152
|
-
if (!provider) return null;
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
id: agent.id,
|
|
156
|
-
name: agent.name || provider.name,
|
|
157
|
-
provider: provider,
|
|
158
|
-
providerId: agent.provider,
|
|
159
|
-
option: agent.option,
|
|
160
|
-
model: agent.model || provider.defaultModel,
|
|
161
|
-
credentials: agent.credentials,
|
|
162
|
-
connected: true
|
|
163
|
-
};
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Get agent credentials by ID
|
|
168
|
-
* @param {string} agentId - Agent ID
|
|
169
|
-
* @returns {Object|null} Credentials object or null
|
|
170
|
-
*/
|
|
171
|
-
const getAgentCredentials = (agentId) => {
|
|
172
|
-
const aiSettings = getAISettings();
|
|
173
|
-
const agents = aiSettings.agents || [];
|
|
174
|
-
const agent = agents.find(a => a.id === agentId);
|
|
175
|
-
|
|
176
|
-
if (!agent) return null;
|
|
177
|
-
return agent.credentials || null;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Set active agent
|
|
182
|
-
*/
|
|
183
|
-
const setActiveAgent = (agentId) => {
|
|
184
|
-
const aiSettings = getAISettings();
|
|
185
|
-
const agents = aiSettings.agents || [];
|
|
186
|
-
|
|
187
|
-
if (!agents.find(a => a.id === agentId)) {
|
|
188
|
-
throw new Error('Agent not found');
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
aiSettings.activeAgentId = agentId;
|
|
192
|
-
saveAISettings(aiSettings);
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Add a new agent (connect to provider)
|
|
197
|
-
*/
|
|
198
|
-
const addAgent = async (providerId, optionId, credentials, model = null, customName = null) => {
|
|
199
|
-
const provider = getProvider(providerId);
|
|
200
|
-
if (!provider) {
|
|
201
|
-
throw new Error('Invalid provider');
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const option = provider.options.find(o => o.id === optionId);
|
|
205
|
-
if (!option) {
|
|
206
|
-
throw new Error('Invalid option');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const aiSettings = getAISettings();
|
|
210
|
-
if (!aiSettings.agents) {
|
|
211
|
-
aiSettings.agents = [];
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Create new agent
|
|
215
|
-
const agentId = generateAgentId();
|
|
216
|
-
const newAgent = {
|
|
217
|
-
id: agentId,
|
|
218
|
-
provider: providerId,
|
|
219
|
-
option: optionId,
|
|
220
|
-
credentials: credentials,
|
|
221
|
-
model: model || provider.defaultModel,
|
|
222
|
-
name: customName || provider.name,
|
|
223
|
-
createdAt: Date.now()
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
aiSettings.agents.push(newAgent);
|
|
227
|
-
|
|
228
|
-
// Set as active if first agent
|
|
229
|
-
if (aiSettings.agents.length === 1) {
|
|
230
|
-
aiSettings.activeAgentId = agentId;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
saveAISettings(aiSettings);
|
|
234
|
-
|
|
235
|
-
// Get the full agent object
|
|
236
|
-
const agent = getAgent(agentId);
|
|
237
|
-
|
|
238
|
-
// Notify StrategySupervisor if algo is running
|
|
239
|
-
// This ensures new agents are immediately connected to live trading
|
|
240
|
-
try {
|
|
241
|
-
StrategySupervisor.addAgent(agent);
|
|
242
|
-
} catch (e) {
|
|
243
|
-
// Supervisor might not be active - that's OK
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return agent;
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Remove an agent
|
|
251
|
-
*/
|
|
252
|
-
const removeAgent = (agentId) => {
|
|
253
|
-
const aiSettings = getAISettings();
|
|
254
|
-
const agents = aiSettings.agents || [];
|
|
255
|
-
|
|
256
|
-
const index = agents.findIndex(a => a.id === agentId);
|
|
257
|
-
if (index === -1) {
|
|
258
|
-
throw new Error('Agent not found');
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
agents.splice(index, 1);
|
|
262
|
-
aiSettings.agents = agents;
|
|
263
|
-
|
|
264
|
-
// Stop AI supervision for this agent
|
|
265
|
-
AISupervisor.stop(agentId);
|
|
266
|
-
|
|
267
|
-
// Notify StrategySupervisor to remove agent from live trading
|
|
268
|
-
try {
|
|
269
|
-
StrategySupervisor.removeAgent(agentId);
|
|
270
|
-
} catch (e) {
|
|
271
|
-
// Supervisor might not be active - that's OK
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// If removed agent was active, set new active
|
|
275
|
-
if (aiSettings.activeAgentId === agentId) {
|
|
276
|
-
aiSettings.activeAgentId = agents.length > 0 ? agents[0].id : null;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
saveAISettings(aiSettings);
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Update agent settings
|
|
284
|
-
*/
|
|
285
|
-
const updateAgent = (agentId, updates) => {
|
|
286
|
-
const aiSettings = getAISettings();
|
|
287
|
-
const agents = aiSettings.agents || [];
|
|
288
|
-
|
|
289
|
-
const agent = agents.find(a => a.id === agentId);
|
|
290
|
-
if (!agent) {
|
|
291
|
-
throw new Error('Agent not found');
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Apply updates
|
|
295
|
-
if (updates.name) agent.name = updates.name;
|
|
296
|
-
if (updates.model) agent.model = updates.model;
|
|
297
|
-
if (updates.credentials) agent.credentials = updates.credentials;
|
|
298
|
-
|
|
299
|
-
saveAISettings(aiSettings);
|
|
300
|
-
return getAgent(agentId);
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Disconnect all agents
|
|
305
|
-
*/
|
|
306
|
-
const disconnectAll = () => {
|
|
307
|
-
// Stop all AI supervision sessions
|
|
308
|
-
AISupervisor.stopAll();
|
|
309
|
-
|
|
310
|
-
// Refresh StrategySupervisor to clear agents
|
|
311
|
-
try {
|
|
312
|
-
StrategySupervisor.refreshAgents();
|
|
313
|
-
} catch (e) {
|
|
314
|
-
// Supervisor might not be active
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
saveAISettings({ agents: [] });
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Legacy: Get current connection (returns active agent)
|
|
322
|
-
* @deprecated Use getActiveAgent() instead
|
|
323
|
-
*/
|
|
324
|
-
const getConnection = () => {
|
|
325
|
-
return getActiveAgent();
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Legacy: Connect to a provider (adds new agent)
|
|
330
|
-
* @deprecated Use addAgent() instead
|
|
331
|
-
*/
|
|
332
|
-
const connect = async (providerId, optionId, credentials, model = null) => {
|
|
333
|
-
return addAgent(providerId, optionId, credentials, model);
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Legacy: Disconnect (removes all agents)
|
|
338
|
-
* @deprecated Use removeAgent() or disconnectAll() instead
|
|
339
|
-
*/
|
|
340
|
-
const disconnect = () => {
|
|
341
|
-
disconnectAll();
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Get credentials for active agent
|
|
346
|
-
*/
|
|
347
|
-
const getCredentials = () => {
|
|
348
|
-
const agent = getActiveAgent();
|
|
349
|
-
return agent?.credentials || null;
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
// validateConnection imported from ./validation
|
|
353
|
-
|
|
354
|
-
module.exports = {
|
|
355
|
-
// Provider info
|
|
356
|
-
getProviders,
|
|
357
|
-
getProvider,
|
|
358
|
-
|
|
359
|
-
// Multi-agent API
|
|
360
|
-
getAgents,
|
|
361
|
-
getAgentCount,
|
|
362
|
-
getAgent,
|
|
363
|
-
getAgentCredentials,
|
|
364
|
-
getActiveAgent,
|
|
365
|
-
setActiveAgent,
|
|
366
|
-
addAgent,
|
|
367
|
-
removeAgent,
|
|
368
|
-
updateAgent,
|
|
369
|
-
disconnectAll,
|
|
370
|
-
|
|
371
|
-
// Legacy API (for backwards compatibility)
|
|
372
|
-
isConnected,
|
|
373
|
-
getConnection,
|
|
374
|
-
connect,
|
|
375
|
-
disconnect,
|
|
376
|
-
getCredentials,
|
|
377
|
-
|
|
378
|
-
// Validation
|
|
379
|
-
validateConnection,
|
|
380
|
-
|
|
381
|
-
// Settings
|
|
382
|
-
getAISettings,
|
|
383
|
-
saveAISettings
|
|
384
|
-
};
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Anthropic OAuth Authentication
|
|
3
|
-
*
|
|
4
|
-
* Implements OAuth 2.0 with PKCE for Anthropic Claude Pro/Max plans.
|
|
5
|
-
* Based on the public OAuth flow used by OpenCode.
|
|
6
|
-
*
|
|
7
|
-
* Data source: Anthropic OAuth API (https://console.anthropic.com/v1/oauth/token)
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const crypto = require('crypto');
|
|
11
|
-
const https = require('https');
|
|
12
|
-
|
|
13
|
-
// Public OAuth Client ID (same as OpenCode - registered with Anthropic)
|
|
14
|
-
const CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
|
|
15
|
-
const REDIRECT_URI = 'https://console.anthropic.com/oauth/code/callback';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Generate PKCE code verifier and challenge
|
|
19
|
-
* @returns {Object} { verifier: string, challenge: string }
|
|
20
|
-
*/
|
|
21
|
-
const generatePKCE = () => {
|
|
22
|
-
// Generate a random 32-byte code verifier (base64url encoded)
|
|
23
|
-
const verifier = crypto.randomBytes(32)
|
|
24
|
-
.toString('base64')
|
|
25
|
-
.replace(/\+/g, '-')
|
|
26
|
-
.replace(/\//g, '_')
|
|
27
|
-
.replace(/=/g, '');
|
|
28
|
-
|
|
29
|
-
// Generate SHA256 hash of verifier, then base64url encode it
|
|
30
|
-
const challenge = crypto.createHash('sha256')
|
|
31
|
-
.update(verifier)
|
|
32
|
-
.digest('base64')
|
|
33
|
-
.replace(/\+/g, '-')
|
|
34
|
-
.replace(/\//g, '_')
|
|
35
|
-
.replace(/=/g, '');
|
|
36
|
-
|
|
37
|
-
return { verifier, challenge };
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Generate OAuth authorization URL
|
|
42
|
-
* @param {'max' | 'console'} mode - 'max' for Claude Pro/Max, 'console' for API key creation
|
|
43
|
-
* @returns {Object} { url: string, verifier: string }
|
|
44
|
-
*/
|
|
45
|
-
const authorize = (mode = 'max') => {
|
|
46
|
-
const pkce = generatePKCE();
|
|
47
|
-
|
|
48
|
-
const baseUrl = mode === 'max'
|
|
49
|
-
? 'https://claude.ai/oauth/authorize'
|
|
50
|
-
: 'https://console.anthropic.com/oauth/authorize';
|
|
51
|
-
|
|
52
|
-
const url = new URL(baseUrl);
|
|
53
|
-
url.searchParams.set('code', 'true');
|
|
54
|
-
url.searchParams.set('client_id', CLIENT_ID);
|
|
55
|
-
url.searchParams.set('response_type', 'code');
|
|
56
|
-
url.searchParams.set('redirect_uri', REDIRECT_URI);
|
|
57
|
-
url.searchParams.set('scope', 'org:create_api_key user:profile user:inference');
|
|
58
|
-
url.searchParams.set('code_challenge', pkce.challenge);
|
|
59
|
-
url.searchParams.set('code_challenge_method', 'S256');
|
|
60
|
-
url.searchParams.set('state', pkce.verifier);
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
url: url.toString(),
|
|
64
|
-
verifier: pkce.verifier
|
|
65
|
-
};
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Make HTTPS request
|
|
70
|
-
* @param {string} url - Full URL
|
|
71
|
-
* @param {Object} options - Request options
|
|
72
|
-
* @returns {Promise<Object>} Response JSON
|
|
73
|
-
*/
|
|
74
|
-
const makeRequest = (url, options) => {
|
|
75
|
-
return new Promise((resolve, reject) => {
|
|
76
|
-
const req = https.request(url, {
|
|
77
|
-
method: options.method || 'POST',
|
|
78
|
-
headers: options.headers || {}
|
|
79
|
-
}, (res) => {
|
|
80
|
-
let data = '';
|
|
81
|
-
res.on('data', chunk => data += chunk);
|
|
82
|
-
res.on('end', () => {
|
|
83
|
-
try {
|
|
84
|
-
const json = JSON.parse(data);
|
|
85
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
86
|
-
resolve(json);
|
|
87
|
-
} else {
|
|
88
|
-
reject(new Error(json.error?.message || `HTTP ${res.statusCode}: ${data}`));
|
|
89
|
-
}
|
|
90
|
-
} catch (e) {
|
|
91
|
-
reject(new Error(`Invalid JSON response: ${data.substring(0, 200)}`));
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
req.on('error', reject);
|
|
97
|
-
|
|
98
|
-
if (options.body) {
|
|
99
|
-
req.write(JSON.stringify(options.body));
|
|
100
|
-
}
|
|
101
|
-
req.end();
|
|
102
|
-
});
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Exchange authorization code for tokens
|
|
107
|
-
* @param {string} code - Authorization code from callback (format: code#state)
|
|
108
|
-
* @param {string} verifier - PKCE code verifier
|
|
109
|
-
* @returns {Promise<Object>} { type: 'success', access: string, refresh: string, expires: number } or { type: 'failed' }
|
|
110
|
-
*
|
|
111
|
-
* Data source: https://console.anthropic.com/v1/oauth/token (POST)
|
|
112
|
-
*/
|
|
113
|
-
const exchange = async (code, verifier) => {
|
|
114
|
-
try {
|
|
115
|
-
// Code format from callback: "authorization_code#state"
|
|
116
|
-
const splits = code.split('#');
|
|
117
|
-
const authCode = splits[0];
|
|
118
|
-
const state = splits[1] || '';
|
|
119
|
-
|
|
120
|
-
const response = await makeRequest('https://console.anthropic.com/v1/oauth/token', {
|
|
121
|
-
method: 'POST',
|
|
122
|
-
headers: {
|
|
123
|
-
'Content-Type': 'application/json'
|
|
124
|
-
},
|
|
125
|
-
body: {
|
|
126
|
-
code: authCode,
|
|
127
|
-
state: state,
|
|
128
|
-
grant_type: 'authorization_code',
|
|
129
|
-
client_id: CLIENT_ID,
|
|
130
|
-
redirect_uri: REDIRECT_URI,
|
|
131
|
-
code_verifier: verifier
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
type: 'success',
|
|
137
|
-
access: response.access_token,
|
|
138
|
-
refresh: response.refresh_token,
|
|
139
|
-
expires: Date.now() + (response.expires_in * 1000)
|
|
140
|
-
};
|
|
141
|
-
} catch (error) {
|
|
142
|
-
return {
|
|
143
|
-
type: 'failed',
|
|
144
|
-
error: error.message
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Refresh access token using refresh token
|
|
151
|
-
* @param {string} refreshToken - The refresh token
|
|
152
|
-
* @returns {Promise<Object>} { type: 'success', access: string, refresh: string, expires: number } or { type: 'failed' }
|
|
153
|
-
*
|
|
154
|
-
* Data source: https://console.anthropic.com/v1/oauth/token (POST)
|
|
155
|
-
*/
|
|
156
|
-
const refreshToken = async (refreshToken) => {
|
|
157
|
-
try {
|
|
158
|
-
const response = await makeRequest('https://console.anthropic.com/v1/oauth/token', {
|
|
159
|
-
method: 'POST',
|
|
160
|
-
headers: {
|
|
161
|
-
'Content-Type': 'application/json'
|
|
162
|
-
},
|
|
163
|
-
body: {
|
|
164
|
-
grant_type: 'refresh_token',
|
|
165
|
-
refresh_token: refreshToken,
|
|
166
|
-
client_id: CLIENT_ID
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
type: 'success',
|
|
172
|
-
access: response.access_token,
|
|
173
|
-
refresh: response.refresh_token,
|
|
174
|
-
expires: Date.now() + (response.expires_in * 1000)
|
|
175
|
-
};
|
|
176
|
-
} catch (error) {
|
|
177
|
-
return {
|
|
178
|
-
type: 'failed',
|
|
179
|
-
error: error.message
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Create an API key using OAuth token (for console mode)
|
|
186
|
-
* @param {string} accessToken - The access token
|
|
187
|
-
* @returns {Promise<Object>} { type: 'success', key: string } or { type: 'failed' }
|
|
188
|
-
*
|
|
189
|
-
* Data source: https://api.anthropic.com/api/oauth/claude_cli/create_api_key (POST)
|
|
190
|
-
*/
|
|
191
|
-
const createApiKey = async (accessToken) => {
|
|
192
|
-
try {
|
|
193
|
-
const response = await makeRequest('https://api.anthropic.com/api/oauth/claude_cli/create_api_key', {
|
|
194
|
-
method: 'POST',
|
|
195
|
-
headers: {
|
|
196
|
-
'Content-Type': 'application/json',
|
|
197
|
-
'Authorization': `Bearer ${accessToken}`
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
return {
|
|
202
|
-
type: 'success',
|
|
203
|
-
key: response.raw_key
|
|
204
|
-
};
|
|
205
|
-
} catch (error) {
|
|
206
|
-
return {
|
|
207
|
-
type: 'failed',
|
|
208
|
-
error: error.message
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Get valid access token (refresh if expired)
|
|
215
|
-
* @param {Object} oauthData - OAuth data { access, refresh, expires }
|
|
216
|
-
* @returns {Promise<Object>} { access: string, refresh: string, expires: number, refreshed: boolean }
|
|
217
|
-
*/
|
|
218
|
-
const getValidToken = async (oauthData) => {
|
|
219
|
-
if (!oauthData || !oauthData.refresh) {
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Check if token is expired or will expire in the next 5 minutes
|
|
224
|
-
const expirationBuffer = 5 * 60 * 1000; // 5 minutes
|
|
225
|
-
if (oauthData.expires && oauthData.expires > Date.now() + expirationBuffer) {
|
|
226
|
-
return {
|
|
227
|
-
...oauthData,
|
|
228
|
-
refreshed: false
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Token expired or about to expire, refresh it
|
|
233
|
-
const result = await refreshToken(oauthData.refresh);
|
|
234
|
-
if (result.type === 'success') {
|
|
235
|
-
return {
|
|
236
|
-
access: result.access,
|
|
237
|
-
refresh: result.refresh,
|
|
238
|
-
expires: result.expires,
|
|
239
|
-
refreshed: true
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return null;
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Check if credentials are OAuth tokens
|
|
248
|
-
* @param {Object} credentials - Agent credentials
|
|
249
|
-
* @returns {boolean}
|
|
250
|
-
*/
|
|
251
|
-
const isOAuthCredentials = (credentials) => {
|
|
252
|
-
return credentials && credentials.oauth && credentials.oauth.refresh;
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
module.exports = {
|
|
256
|
-
CLIENT_ID,
|
|
257
|
-
REDIRECT_URI,
|
|
258
|
-
generatePKCE,
|
|
259
|
-
authorize,
|
|
260
|
-
exchange,
|
|
261
|
-
refreshToken,
|
|
262
|
-
createApiKey,
|
|
263
|
-
getValidToken,
|
|
264
|
-
isOAuthCredentials
|
|
265
|
-
};
|