hedgequantx 2.9.32 → 2.9.33
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/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -77,7 +77,10 @@ const refreshStats = async () => {
|
|
|
77
77
|
|
|
78
78
|
try {
|
|
79
79
|
const allAccounts = await connections.getAllAccounts();
|
|
80
|
-
|
|
80
|
+
// Filter active accounts: status === 0 (ProjectX) OR status === 'active' (Rithmic) OR no status
|
|
81
|
+
const activeAccounts = allAccounts.filter(acc =>
|
|
82
|
+
acc.status === 0 || acc.status === 'active' || acc.status === undefined || acc.status === null
|
|
83
|
+
);
|
|
81
84
|
|
|
82
85
|
let totalBalance = null;
|
|
83
86
|
let totalPnl = null;
|
|
@@ -113,50 +113,75 @@ const testApiKeyConnection = async (agent) => {
|
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
115
|
* Test a single agent connection and response format
|
|
116
|
+
* Includes retry logic for non-deterministic agents (like MiniMax)
|
|
116
117
|
* @param {Object} agent - Agent config { id, provider, modelId, connectionType, apiKey, ... }
|
|
118
|
+
* @param {number} maxRetries - Maximum retry attempts (default 2)
|
|
117
119
|
* @returns {Promise<Object>} { success, latency, formatValid, error }
|
|
118
120
|
*/
|
|
119
|
-
const testAgentConnection = async (agent) => {
|
|
120
|
-
|
|
121
|
+
const testAgentConnection = async (agent, maxRetries = 2) => {
|
|
122
|
+
let lastResult = null;
|
|
121
123
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (agent.connectionType === 'apikey') {
|
|
125
|
-
return await testApiKeyConnection(agent);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// CLIProxy connection
|
|
129
|
-
const result = await cliproxy.chat(agent.provider, agent.modelId, TEST_PROMPT, AGENT_TIMEOUT);
|
|
130
|
-
const latency = Date.now() - startTime;
|
|
124
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
125
|
+
const startTime = Date.now();
|
|
131
126
|
|
|
132
|
-
|
|
133
|
-
|
|
127
|
+
try {
|
|
128
|
+
// Route based on connection type
|
|
129
|
+
if (agent.connectionType === 'apikey') {
|
|
130
|
+
lastResult = await testApiKeyConnection(agent);
|
|
131
|
+
} else {
|
|
132
|
+
// CLIProxy connection
|
|
133
|
+
const result = await cliproxy.chat(agent.provider, agent.modelId, TEST_PROMPT, AGENT_TIMEOUT);
|
|
134
|
+
const latency = Date.now() - startTime;
|
|
135
|
+
|
|
136
|
+
if (!result.success) {
|
|
137
|
+
lastResult = {
|
|
138
|
+
success: false,
|
|
139
|
+
latency,
|
|
140
|
+
formatValid: false,
|
|
141
|
+
error: result.error || 'No response from agent'
|
|
142
|
+
};
|
|
143
|
+
} else {
|
|
144
|
+
// Validate response format
|
|
145
|
+
const formatResult = validateResponseFormat(result.content);
|
|
146
|
+
|
|
147
|
+
lastResult = {
|
|
148
|
+
success: formatResult.valid,
|
|
149
|
+
latency,
|
|
150
|
+
formatValid: formatResult.valid,
|
|
151
|
+
error: formatResult.valid ? null : formatResult.error,
|
|
152
|
+
response: result.content
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// If successful, return immediately
|
|
158
|
+
if (lastResult.success) {
|
|
159
|
+
return lastResult;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Only retry on format errors (not connection errors)
|
|
163
|
+
if (lastResult.error && !lastResult.error.includes('valid JSON') &&
|
|
164
|
+
!lastResult.error.includes('Missing') && !lastResult.error.includes('Invalid')) {
|
|
165
|
+
// Connection error - don't retry
|
|
166
|
+
return lastResult;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Wait a bit before retry
|
|
170
|
+
if (attempt < maxRetries) {
|
|
171
|
+
await new Promise(r => setTimeout(r, 500));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
} catch (error) {
|
|
175
|
+
lastResult = {
|
|
134
176
|
success: false,
|
|
135
|
-
latency,
|
|
177
|
+
latency: Date.now() - startTime,
|
|
136
178
|
formatValid: false,
|
|
137
|
-
error:
|
|
179
|
+
error: error.message || 'Connection failed'
|
|
138
180
|
};
|
|
139
181
|
}
|
|
140
|
-
|
|
141
|
-
// Validate response format
|
|
142
|
-
const formatResult = validateResponseFormat(result.content);
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
success: formatResult.valid,
|
|
146
|
-
latency,
|
|
147
|
-
formatValid: formatResult.valid,
|
|
148
|
-
error: formatResult.valid ? null : formatResult.error,
|
|
149
|
-
response: result.content
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
} catch (error) {
|
|
153
|
-
return {
|
|
154
|
-
success: false,
|
|
155
|
-
latency: Date.now() - startTime,
|
|
156
|
-
formatValid: false,
|
|
157
|
-
error: error.message || 'Connection failed'
|
|
158
|
-
};
|
|
159
182
|
}
|
|
183
|
+
|
|
184
|
+
return lastResult;
|
|
160
185
|
};
|
|
161
186
|
|
|
162
187
|
/**
|
|
@@ -37,8 +37,13 @@ const extractJSON = (text) => {
|
|
|
37
37
|
} catch (e) { /* continue */ }
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
// Remove <think>...</think> tags (MiniMax)
|
|
41
|
-
let cleaned = text
|
|
40
|
+
// Remove <think>...</think> tags (MiniMax) - multiple patterns
|
|
41
|
+
let cleaned = text
|
|
42
|
+
.replace(/<think>[\s\S]*?<\/think>/gi, '')
|
|
43
|
+
.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '')
|
|
44
|
+
.replace(/<reasoning>[\s\S]*?<\/reasoning>/gi, '')
|
|
45
|
+
.replace(/<analysis>[\s\S]*?<\/analysis>/gi, '')
|
|
46
|
+
.trim();
|
|
42
47
|
|
|
43
48
|
// Try to parse cleaned text
|
|
44
49
|
try {
|
|
@@ -50,14 +55,37 @@ const extractJSON = (text) => {
|
|
|
50
55
|
if (startIdx !== -1) {
|
|
51
56
|
let depth = 0;
|
|
52
57
|
let endIdx = -1;
|
|
58
|
+
let inString = false;
|
|
59
|
+
let escapeNext = false;
|
|
60
|
+
|
|
53
61
|
for (let i = startIdx; i < cleaned.length; i++) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
const char = cleaned[i];
|
|
63
|
+
|
|
64
|
+
if (escapeNext) {
|
|
65
|
+
escapeNext = false;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (char === '\\') {
|
|
70
|
+
escapeNext = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (char === '"') {
|
|
75
|
+
inString = !inString;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!inString) {
|
|
80
|
+
if (char === '{') depth++;
|
|
81
|
+
if (char === '}') depth--;
|
|
82
|
+
if (depth === 0) {
|
|
83
|
+
endIdx = i;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
59
86
|
}
|
|
60
87
|
}
|
|
88
|
+
|
|
61
89
|
if (endIdx !== -1) {
|
|
62
90
|
try {
|
|
63
91
|
return JSON.parse(cleaned.substring(startIdx, endIdx + 1));
|
|
@@ -65,6 +93,19 @@ const extractJSON = (text) => {
|
|
|
65
93
|
}
|
|
66
94
|
}
|
|
67
95
|
|
|
96
|
+
// Try to extract just decision/confidence/reason pattern
|
|
97
|
+
const decisionMatch = cleaned.match(/"decision"\s*:\s*"(approve|reject|modify)"/i);
|
|
98
|
+
const confidenceMatch = cleaned.match(/"confidence"\s*:\s*(\d+)/);
|
|
99
|
+
const reasonMatch = cleaned.match(/"reason"\s*:\s*"([^"]+)"/);
|
|
100
|
+
|
|
101
|
+
if (decisionMatch && confidenceMatch) {
|
|
102
|
+
return {
|
|
103
|
+
decision: decisionMatch[1].toLowerCase(),
|
|
104
|
+
confidence: parseInt(confidenceMatch[1]),
|
|
105
|
+
reason: reasonMatch ? reasonMatch[1] : 'Extracted from partial response'
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
68
109
|
// Last resort: try to find JSON object pattern with "decision"
|
|
69
110
|
const jsonMatch = cleaned.match(/\{[^{}]*"decision"[^{}]*\}/);
|
|
70
111
|
if (jsonMatch) {
|