@pgnio/helios-mcp 0.2.8 → 0.4.0
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 +19 -2
- package/dist/chunk-5LIHYXEF.mjs +4274 -0
- package/dist/chunk-LPDQG4WK.mjs +4274 -0
- package/dist/cli.js +2292 -1370
- package/dist/cli.mjs +8 -4
- package/dist/index.d.mts +1020 -141
- package/dist/index.d.ts +1020 -141
- package/dist/index.js +2245 -992
- package/dist/index.mjs +73 -3
- package/docs/ai-agent-indicators/00_columns.txt +3 -1
- package/docs/ai-agent-mcp/04_trading/01_tools.txt +2 -1
- package/package.json +9 -16
- package/dist/chunk-52XY4KRG.mjs +0 -3038
- package/dist/chunk-7EDI4KLD.mjs +0 -3042
- package/dist/chunk-EIAUO3XE.mjs +0 -2955
- package/dist/chunk-NCF7ASJB.mjs +0 -2986
- package/dist/chunk-NWGWXYTS.mjs +0 -2964
- package/dist/chunk-UHFLF3UG.mjs +0 -3051
- package/dist/chunk-UYCUR7QV.mjs +0 -2994
- package/dist/chunk-V453JYRN.mjs +0 -3051
- package/dist/chunk-WLCFIWFP.mjs +0 -3048
- package/dist/chunk-XE6HRID3.mjs +0 -2992
package/dist/index.js
CHANGED
|
@@ -32,6 +32,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
32
32
|
// src/index.ts
|
|
33
33
|
var index_exports = {};
|
|
34
34
|
__export(index_exports, {
|
|
35
|
+
DEFAULT_HELIOS_BASE_URL: () => DEFAULT_HELIOS_BASE_URL,
|
|
36
|
+
DEFAULT_HELIOS_SESSION_ISSUER: () => DEFAULT_HELIOS_SESSION_ISSUER,
|
|
35
37
|
DEFAULT_SNAPSHOT_FIELDS: () => DEFAULT_SNAPSHOT_FIELDS,
|
|
36
38
|
DEFAULT_SNAPSHOT_TOP: () => DEFAULT_SNAPSHOT_TOP,
|
|
37
39
|
FileAgentSessionStore: () => FileAgentSessionStore,
|
|
@@ -43,7 +45,14 @@ __export(index_exports, {
|
|
|
43
45
|
HeliosAgentMcpMarketService: () => HeliosAgentMcpMarketService,
|
|
44
46
|
HeliosAgentMcpProviderService: () => HeliosAgentMcpProviderService,
|
|
45
47
|
HeliosAgentMcpTradingService: () => HeliosAgentMcpTradingService,
|
|
48
|
+
MAX_TOOL_INPUT_ELEMENTS: () => MAX_TOOL_INPUT_ELEMENTS,
|
|
49
|
+
MCP_PROFILES: () => MCP_PROFILES,
|
|
46
50
|
MemoryAgentMcpSessionStore: () => MemoryAgentMcpSessionStore,
|
|
51
|
+
TRADE_KEY_WINDOW_SECONDS: () => TRADE_KEY_WINDOW_SECONDS,
|
|
52
|
+
accountInputSchema: () => accountInputSchema,
|
|
53
|
+
ago: () => ago,
|
|
54
|
+
assertHeliosBaseUrl: () => assertHeliosBaseUrl,
|
|
55
|
+
candlesInputSchema: () => candlesInputSchema,
|
|
47
56
|
compactSymbolDetail: () => compactSymbolDetail,
|
|
48
57
|
createHeliosAccountToolDefinitions: () => createHeliosAccountToolDefinitions,
|
|
49
58
|
createHeliosAgentMcpServer: () => createHeliosAgentMcpServer,
|
|
@@ -53,15 +62,41 @@ __export(index_exports, {
|
|
|
53
62
|
createHeliosMarketToolDefinitions: () => createHeliosMarketToolDefinitions,
|
|
54
63
|
createHeliosProviderToolDefinitions: () => createHeliosProviderToolDefinitions,
|
|
55
64
|
createHeliosTradingToolDefinitions: () => createHeliosTradingToolDefinitions,
|
|
65
|
+
createLeanToolDefinitions: () => createLeanToolDefinitions,
|
|
66
|
+
entryText: () => entryText,
|
|
56
67
|
formatHeliosLoginPrompt: () => formatHeliosLoginPrompt,
|
|
57
68
|
getDefaultHeliosAgentMcpDocsRoot: () => getDefaultHeliosAgentMcpDocsRoot,
|
|
58
69
|
getDefaultHeliosAgentMcpSessionPath: () => getDefaultHeliosAgentMcpSessionPath,
|
|
59
|
-
|
|
70
|
+
isSameHeliosIssuer: () => isSameHeliosIssuer,
|
|
71
|
+
leanToolsFor: () => leanToolsFor,
|
|
72
|
+
marketInputSchema: () => marketInputSchema,
|
|
73
|
+
moveText: () => moveText,
|
|
74
|
+
pct: () => pct,
|
|
75
|
+
pendingAgentLoginSchema: () => pendingAgentLoginSchema2,
|
|
76
|
+
positionLine: () => positionLine,
|
|
77
|
+
price: () => price,
|
|
78
|
+
protectInputSchema: () => protectInputSchema,
|
|
79
|
+
renderAccount: () => renderAccount,
|
|
80
|
+
renderBoard: () => renderBoard,
|
|
81
|
+
renderCandles: () => renderCandles,
|
|
82
|
+
renderCoin: () => renderCoin,
|
|
83
|
+
renderMarket: () => renderMarket,
|
|
84
|
+
renderMoves: () => renderMoves,
|
|
85
|
+
renderTrades: () => renderTrades,
|
|
60
86
|
retryAfterUnauthorizedRefresh: () => retryAfterUnauthorizedRefresh,
|
|
87
|
+
smBoardInputSchema: () => smBoardInputSchema,
|
|
88
|
+
smCoinInputSchema: () => smCoinInputSchema,
|
|
89
|
+
smMovesInputSchema: () => smMovesInputSchema,
|
|
61
90
|
toolAnnotations: () => toolAnnotations,
|
|
62
91
|
topOfSnapshot: () => topOfSnapshot,
|
|
92
|
+
tradeInputSchema: () => tradeInputSchema,
|
|
93
|
+
tradeKeyFor: () => tradeKeyFor,
|
|
94
|
+
trader: () => trader,
|
|
95
|
+
tradesInputSchema: () => tradesInputSchema,
|
|
96
|
+
usd: () => usd,
|
|
63
97
|
withDataText: () => withDataText,
|
|
64
|
-
withSnapshotDefaults: () => withSnapshotDefaults
|
|
98
|
+
withSnapshotDefaults: () => withSnapshotDefaults,
|
|
99
|
+
wrongSideTpSl: () => wrongSideTpSl
|
|
65
100
|
});
|
|
66
101
|
module.exports = __toCommonJS(index_exports);
|
|
67
102
|
|
|
@@ -69,9 +104,55 @@ module.exports = __toCommonJS(index_exports);
|
|
|
69
104
|
var import_zod = require("zod");
|
|
70
105
|
var import_zod2 = require("zod");
|
|
71
106
|
var import_zod3 = require("zod");
|
|
107
|
+
var import_zod4 = require("zod");
|
|
72
108
|
var __defProp2 = Object.defineProperty;
|
|
73
109
|
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
74
110
|
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
111
|
+
var VERIFIED_AGENT_APPS = [
|
|
112
|
+
{ name: "Claude", redirects: ["https://claude.ai/api/mcp/auth_callback", "https://claude.com/api/mcp/auth_callback"] },
|
|
113
|
+
{ name: "ChatGPT", redirects: ["https://chatgpt.com/connector_platform_oauth_redirect"] },
|
|
114
|
+
{ name: "Cursor", redirects: ["https://www.cursor.com/agents/mcp/oauth/callback"] }
|
|
115
|
+
];
|
|
116
|
+
var RESERVED_AGENT_APP_NAMES = [
|
|
117
|
+
...VERIFIED_AGENT_APPS.map((app) => app.name),
|
|
118
|
+
"VS Code",
|
|
119
|
+
"Helios",
|
|
120
|
+
"Wagmi",
|
|
121
|
+
"Ares",
|
|
122
|
+
"OpenAI",
|
|
123
|
+
"Anthropic",
|
|
124
|
+
"Grok"
|
|
125
|
+
];
|
|
126
|
+
var heliosAgentErrorResponseSchema = import_zod2.z.object({
|
|
127
|
+
error: import_zod2.z.string().optional(),
|
|
128
|
+
errorDescription: import_zod2.z.string().optional(),
|
|
129
|
+
message: import_zod2.z.string().optional()
|
|
130
|
+
});
|
|
131
|
+
var HeliosAgentApiError = class extends Error {
|
|
132
|
+
constructor(options) {
|
|
133
|
+
super(options.message);
|
|
134
|
+
__publicField2(this, "status");
|
|
135
|
+
__publicField2(this, "code");
|
|
136
|
+
__publicField2(this, "errorDescription");
|
|
137
|
+
__publicField2(this, "payload");
|
|
138
|
+
this.name = "HeliosAgentApiError";
|
|
139
|
+
this.status = options.status;
|
|
140
|
+
this.code = options.code;
|
|
141
|
+
this.errorDescription = options.errorDescription;
|
|
142
|
+
this.payload = options.payload;
|
|
143
|
+
if (options.cause !== void 0) {
|
|
144
|
+
Object.defineProperty(this, "cause", {
|
|
145
|
+
configurable: true,
|
|
146
|
+
enumerable: false,
|
|
147
|
+
value: options.cause,
|
|
148
|
+
writable: true
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
function isHeliosAgentApiError(error) {
|
|
154
|
+
return error instanceof HeliosAgentApiError;
|
|
155
|
+
}
|
|
75
156
|
var AGENT_SCOPES = {
|
|
76
157
|
ACCOUNT_READ: "account:read",
|
|
77
158
|
MARKET_READ: "market:read",
|
|
@@ -84,428 +165,970 @@ var AGENT_SCOPE_VALUES = [
|
|
|
84
165
|
AGENT_SCOPES.ORDERS_WRITE,
|
|
85
166
|
AGENT_SCOPES.ORDERS_CANCEL
|
|
86
167
|
];
|
|
87
|
-
var agentScopeSchema =
|
|
88
|
-
var
|
|
89
|
-
var
|
|
90
|
-
var
|
|
91
|
-
var
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
168
|
+
var agentScopeSchema = import_zod3.z.enum(AGENT_SCOPE_VALUES);
|
|
169
|
+
var AGENT_TRADING_SCOPES = [AGENT_SCOPES.ORDERS_WRITE, AGENT_SCOPES.ORDERS_CANCEL];
|
|
170
|
+
var agentGrantStatusSchema = import_zod3.z.enum(["active", "revoked"]);
|
|
171
|
+
var agentDeviceRequestStatusSchema = import_zod3.z.enum(["pending", "approved", "rejected", "exchanged", "expired"]);
|
|
172
|
+
var AGENT_CONSTRAINTS_MAX_KEYS = 16;
|
|
173
|
+
var agentConstraintsSchema = import_zod3.z.record(import_zod3.z.string().max(64), import_zod3.z.unknown()).refine((value) => Object.keys(value).length <= AGENT_CONSTRAINTS_MAX_KEYS, {
|
|
174
|
+
message: `At most ${AGENT_CONSTRAINTS_MAX_KEYS} constraints`
|
|
175
|
+
}).default({});
|
|
176
|
+
var agentGrantMetadataSchema = import_zod3.z.object({
|
|
177
|
+
id: import_zod3.z.string(),
|
|
178
|
+
userId: import_zod3.z.string(),
|
|
179
|
+
clientName: import_zod3.z.string(),
|
|
95
180
|
status: agentGrantStatusSchema,
|
|
96
|
-
scope:
|
|
181
|
+
scope: import_zod3.z.array(agentScopeSchema).min(1),
|
|
97
182
|
constraints: agentConstraintsSchema,
|
|
98
|
-
tokenVersion:
|
|
99
|
-
createdAt:
|
|
100
|
-
approvedAt:
|
|
101
|
-
lastUsedAt:
|
|
102
|
-
revokedAt:
|
|
103
|
-
});
|
|
104
|
-
var agentDeviceStartInputSchema = import_zod.z.object({
|
|
105
|
-
clientName: import_zod.z.string().min(1).max(64),
|
|
106
|
-
scope: import_zod.z.array(agentScopeSchema).min(1),
|
|
107
|
-
constraints: agentConstraintsSchema.optional()
|
|
183
|
+
tokenVersion: import_zod3.z.number().int().nonnegative(),
|
|
184
|
+
createdAt: import_zod3.z.string(),
|
|
185
|
+
approvedAt: import_zod3.z.string().optional(),
|
|
186
|
+
lastUsedAt: import_zod3.z.string().optional(),
|
|
187
|
+
revokedAt: import_zod3.z.string().optional()
|
|
108
188
|
});
|
|
109
|
-
var
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
189
|
+
var AGENT_START_SECRET_HEADER = "x-helios-agent-start-secret";
|
|
190
|
+
var AGENT_CLIENT_IP_HEADER = "x-helios-client-ip";
|
|
191
|
+
var agentDeviceStartInputSchema = import_zod3.z.object({
|
|
192
|
+
clientName: import_zod3.z.string().trim().min(1).max(64),
|
|
193
|
+
scope: import_zod3.z.array(agentScopeSchema).min(1),
|
|
194
|
+
constraints: agentConstraintsSchema.optional(),
|
|
195
|
+
/** The app was matched to a known client by the hosted MCP. Ignored unless the start carries the start secret. */
|
|
196
|
+
verified: import_zod3.z.boolean().optional(),
|
|
197
|
+
/** The approval answers a one-time proof the device must send with the token exchange (the hosted MCP's redirect). */
|
|
198
|
+
requireApprovalProof: import_zod3.z.boolean().optional()
|
|
116
199
|
});
|
|
117
|
-
var
|
|
118
|
-
deviceCode:
|
|
200
|
+
var agentDeviceStartOutputSchema = import_zod3.z.object({
|
|
201
|
+
deviceCode: import_zod3.z.string(),
|
|
202
|
+
userCode: import_zod3.z.string(),
|
|
203
|
+
verificationUri: import_zod3.z.string().url(),
|
|
204
|
+
verificationUriComplete: import_zod3.z.string().url(),
|
|
205
|
+
expiresInSeconds: import_zod3.z.number().int().positive(),
|
|
206
|
+
pollIntervalSeconds: import_zod3.z.number().int().positive()
|
|
119
207
|
});
|
|
120
|
-
var
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
208
|
+
var agentDeviceTokenInputSchema = import_zod3.z.object({
|
|
209
|
+
deviceCode: import_zod3.z.string().min(1),
|
|
210
|
+
/** Required when the login was started with requireApprovalProof: the proof the approval answered with. */
|
|
211
|
+
approvalProof: import_zod3.z.string().min(1).max(128).optional()
|
|
212
|
+
});
|
|
213
|
+
var agentDeviceRequestViewSchema = import_zod3.z.object({
|
|
214
|
+
userCode: import_zod3.z.string(),
|
|
215
|
+
clientName: import_zod3.z.string(),
|
|
216
|
+
scope: import_zod3.z.array(agentScopeSchema).min(1),
|
|
124
217
|
constraints: agentConstraintsSchema,
|
|
125
218
|
status: agentDeviceRequestStatusSchema,
|
|
126
|
-
expiresAt:
|
|
219
|
+
expiresAt: import_zod3.z.string(),
|
|
220
|
+
/** False when the app's name wasn't checked by Helios (anyone can start a login under any name). */
|
|
221
|
+
verified: import_zod3.z.boolean().optional(),
|
|
222
|
+
/** The signed-in viewer already has an active connection under this name, which approving would replace. */
|
|
223
|
+
replacesExisting: import_zod3.z.boolean().optional(),
|
|
224
|
+
/** Where the login was started, as identity saw the device/start call. Absent from older identity builds. */
|
|
225
|
+
startedFrom: import_zod3.z.object({
|
|
226
|
+
ip: import_zod3.z.string().nullable(),
|
|
227
|
+
userAgent: import_zod3.z.string().nullable(),
|
|
228
|
+
startedAt: import_zod3.z.string()
|
|
229
|
+
}).optional()
|
|
127
230
|
});
|
|
128
|
-
var
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
231
|
+
var agentDeviceApproveInputSchema = import_zod3.z.object({
|
|
232
|
+
/** The person confirmed replacing their existing connection of the same name. */
|
|
233
|
+
replaceExisting: import_zod3.z.boolean().optional(),
|
|
234
|
+
/**
|
|
235
|
+
* The scopes the person allowed: a non-empty subset of the request's scope (identity answers 400 invalid_scope
|
|
236
|
+
* otherwise). The grant and its tokens get exactly these. Absent: everything the request asked for.
|
|
237
|
+
*/
|
|
238
|
+
scope: import_zod3.z.array(agentScopeSchema).min(1).max(AGENT_SCOPE_VALUES.length).optional(),
|
|
239
|
+
/**
|
|
240
|
+
* The person confirmed they trust an unverified app to trade. Required (else 409 trading_acknowledgement_required)
|
|
241
|
+
* when the login is unverified and the approved scope includes a trading scope (AGENT_TRADING_SCOPES).
|
|
242
|
+
*/
|
|
243
|
+
acknowledgeTrading: import_zod3.z.boolean().optional()
|
|
244
|
+
});
|
|
245
|
+
var agentTokenResponseSchema = import_zod3.z.object({
|
|
246
|
+
accessToken: import_zod3.z.string(),
|
|
247
|
+
refreshToken: import_zod3.z.string(),
|
|
248
|
+
tokenType: import_zod3.z.literal("Bearer"),
|
|
249
|
+
expiresInSeconds: import_zod3.z.number().int().positive(),
|
|
250
|
+
scope: import_zod3.z.array(agentScopeSchema).min(1),
|
|
251
|
+
ownerUserId: import_zod3.z.string(),
|
|
252
|
+
agentGrantId: import_zod3.z.string()
|
|
136
253
|
});
|
|
137
|
-
var agentRefreshInputSchema =
|
|
138
|
-
refreshToken:
|
|
254
|
+
var agentRefreshInputSchema = import_zod3.z.object({
|
|
255
|
+
refreshToken: import_zod3.z.string().min(1)
|
|
139
256
|
});
|
|
140
|
-
var agentApprovalResponseSchema =
|
|
257
|
+
var agentApprovalResponseSchema = import_zod3.z.object({
|
|
141
258
|
request: agentDeviceRequestViewSchema,
|
|
142
|
-
grant: agentGrantMetadataSchema
|
|
259
|
+
grant: agentGrantMetadataSchema,
|
|
260
|
+
/** Present when the login asked for one: hand it back to the device, which sends it with the token exchange. */
|
|
261
|
+
approvalProof: import_zod3.z.string().optional()
|
|
143
262
|
});
|
|
144
|
-
var agentSessionStateSchema =
|
|
145
|
-
status:
|
|
263
|
+
var agentSessionStateSchema = import_zod3.z.object({
|
|
264
|
+
status: import_zod3.z.enum(["pending", "authorized", "rejected", "revoked"]),
|
|
146
265
|
request: agentDeviceRequestViewSchema.optional(),
|
|
147
266
|
grant: agentGrantMetadataSchema.optional()
|
|
148
267
|
});
|
|
149
|
-
var providerSchema =
|
|
150
|
-
var walletStateSchema =
|
|
151
|
-
provider:
|
|
152
|
-
accountValue:
|
|
153
|
-
availableBalance:
|
|
154
|
-
withdrawable:
|
|
155
|
-
totalMarginUsed:
|
|
156
|
-
totalNotionalPosition:
|
|
157
|
-
crossMarginAccountValue:
|
|
158
|
-
isActivated:
|
|
159
|
-
fetchedAt:
|
|
160
|
-
});
|
|
161
|
-
var openPositionSchema =
|
|
162
|
-
symbol:
|
|
163
|
-
side:
|
|
164
|
-
size:
|
|
165
|
-
entryPrice:
|
|
166
|
-
markPrice:
|
|
167
|
-
unrealizedPnl:
|
|
168
|
-
margin:
|
|
169
|
-
leverage:
|
|
170
|
-
liquidationPrice:
|
|
171
|
-
});
|
|
172
|
-
var openPositionsSnapshotSchema =
|
|
173
|
-
provider:
|
|
174
|
-
fetchedAt:
|
|
175
|
-
totalUnrealizedPnl:
|
|
176
|
-
positions:
|
|
177
|
-
});
|
|
178
|
-
var protectiveOrderKindSchema =
|
|
179
|
-
var protectiveOrderSideSchema =
|
|
180
|
-
var protectiveOrderPositionSideSchema =
|
|
181
|
-
var protectiveOrderSchema =
|
|
182
|
-
id:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
268
|
+
var providerSchema = import_zod3.z.enum(["hyperliquid"]);
|
|
269
|
+
var walletStateSchema = import_zod3.z.object({
|
|
270
|
+
provider: import_zod3.z.literal("hyperliquid"),
|
|
271
|
+
accountValue: import_zod3.z.number(),
|
|
272
|
+
availableBalance: import_zod3.z.number(),
|
|
273
|
+
withdrawable: import_zod3.z.number(),
|
|
274
|
+
totalMarginUsed: import_zod3.z.number(),
|
|
275
|
+
totalNotionalPosition: import_zod3.z.number(),
|
|
276
|
+
crossMarginAccountValue: import_zod3.z.number(),
|
|
277
|
+
isActivated: import_zod3.z.boolean(),
|
|
278
|
+
fetchedAt: import_zod3.z.string().datetime()
|
|
279
|
+
});
|
|
280
|
+
var openPositionSchema = import_zod3.z.object({
|
|
281
|
+
symbol: import_zod3.z.string(),
|
|
282
|
+
side: import_zod3.z.enum(["LONG", "SHORT"]),
|
|
283
|
+
size: import_zod3.z.number(),
|
|
284
|
+
entryPrice: import_zod3.z.number(),
|
|
285
|
+
markPrice: import_zod3.z.number(),
|
|
286
|
+
unrealizedPnl: import_zod3.z.number(),
|
|
287
|
+
margin: import_zod3.z.number(),
|
|
288
|
+
leverage: import_zod3.z.number(),
|
|
289
|
+
liquidationPrice: import_zod3.z.number().optional()
|
|
290
|
+
});
|
|
291
|
+
var openPositionsSnapshotSchema = import_zod3.z.object({
|
|
292
|
+
provider: import_zod3.z.literal("hyperliquid"),
|
|
293
|
+
fetchedAt: import_zod3.z.string().datetime(),
|
|
294
|
+
totalUnrealizedPnl: import_zod3.z.number(),
|
|
295
|
+
positions: import_zod3.z.array(openPositionSchema)
|
|
296
|
+
});
|
|
297
|
+
var protectiveOrderKindSchema = import_zod3.z.enum(["STOP_LOSS", "TAKE_PROFIT"]);
|
|
298
|
+
var protectiveOrderSideSchema = import_zod3.z.enum(["BUY", "SELL"]);
|
|
299
|
+
var protectiveOrderPositionSideSchema = import_zod3.z.enum(["BOTH", "LONG", "SHORT"]);
|
|
300
|
+
var protectiveOrderSchema = import_zod3.z.object({
|
|
301
|
+
/** Helios's record id: what helios_positions_delete_tp_sl takes (the Hyperliquid order id works there too). */
|
|
302
|
+
id: import_zod3.z.string(),
|
|
303
|
+
/** The Hyperliquid order id (named after the retired Binance field). */
|
|
304
|
+
binanceOrderId: import_zod3.z.number().int().nonnegative().nullish(),
|
|
305
|
+
clientOrderId: import_zod3.z.string().nullish(),
|
|
306
|
+
symbol: import_zod3.z.string(),
|
|
186
307
|
side: protectiveOrderSideSchema,
|
|
187
|
-
positionSide: protectiveOrderPositionSideSchema,
|
|
188
|
-
status:
|
|
308
|
+
positionSide: protectiveOrderPositionSideSchema.nullish().transform((value) => value ?? "BOTH"),
|
|
309
|
+
status: import_zod3.z.string(),
|
|
189
310
|
kind: protectiveOrderKindSchema,
|
|
190
|
-
groupId:
|
|
191
|
-
stopPrice:
|
|
192
|
-
parentClientOrderId:
|
|
193
|
-
assetId:
|
|
194
|
-
createdAt:
|
|
195
|
-
});
|
|
196
|
-
var
|
|
197
|
-
provider:
|
|
198
|
-
totalBalance:
|
|
199
|
-
availableBalance:
|
|
200
|
-
totalMargin:
|
|
201
|
-
unrealizedPnl:
|
|
202
|
-
positions:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
311
|
+
groupId: import_zod3.z.string().nullish(),
|
|
312
|
+
stopPrice: import_zod3.z.union([import_zod3.z.string(), import_zod3.z.number()]).nullish().transform((value) => value == null ? value : String(value)),
|
|
313
|
+
parentClientOrderId: import_zod3.z.string().nullish(),
|
|
314
|
+
assetId: import_zod3.z.number().int().nonnegative().nullish(),
|
|
315
|
+
createdAt: import_zod3.z.string().nullish()
|
|
316
|
+
});
|
|
317
|
+
var portfolioSnapshotSchema = import_zod3.z.object({
|
|
318
|
+
provider: import_zod3.z.literal("hyperliquid"),
|
|
319
|
+
totalBalance: import_zod3.z.number(),
|
|
320
|
+
availableBalance: import_zod3.z.number(),
|
|
321
|
+
totalMargin: import_zod3.z.number(),
|
|
322
|
+
unrealizedPnl: import_zod3.z.number(),
|
|
323
|
+
positions: import_zod3.z.array(openPositionSchema),
|
|
324
|
+
/** Unreadable entries are dropped (see readProtectiveOrders) rather than failing the read. */
|
|
325
|
+
protectiveOrders: import_zod3.z.array(protectiveOrderSchema)
|
|
326
|
+
});
|
|
327
|
+
var portfolioInputSchema = import_zod3.z.object({
|
|
206
328
|
provider: providerSchema
|
|
207
329
|
});
|
|
208
|
-
var
|
|
209
|
-
var
|
|
210
|
-
var ledgerFillsInputSchema = import_zod.z.object({
|
|
330
|
+
var ledgerWindowSchema = import_zod3.z.enum(["24h", "7d", "30d"]);
|
|
331
|
+
var ledgerFillsInputSchema = import_zod3.z.object({
|
|
211
332
|
window: ledgerWindowSchema.optional(),
|
|
212
|
-
limit:
|
|
213
|
-
before:
|
|
214
|
-
});
|
|
215
|
-
var ledgerFillSchema =
|
|
216
|
-
coin:
|
|
217
|
-
px:
|
|
218
|
-
sz:
|
|
219
|
-
side:
|
|
220
|
-
time:
|
|
221
|
-
dir:
|
|
222
|
-
closedPnl:
|
|
223
|
-
fee:
|
|
224
|
-
crossed:
|
|
225
|
-
oid:
|
|
226
|
-
tid:
|
|
227
|
-
});
|
|
228
|
-
var ledgerFillsPageSchema =
|
|
333
|
+
limit: import_zod3.z.number().int().positive().max(200).optional(),
|
|
334
|
+
before: import_zod3.z.number().int().nonnegative().optional()
|
|
335
|
+
});
|
|
336
|
+
var ledgerFillSchema = import_zod3.z.object({
|
|
337
|
+
coin: import_zod3.z.string(),
|
|
338
|
+
px: import_zod3.z.number(),
|
|
339
|
+
sz: import_zod3.z.number(),
|
|
340
|
+
side: import_zod3.z.enum(["B", "A"]),
|
|
341
|
+
time: import_zod3.z.number().int().nonnegative(),
|
|
342
|
+
dir: import_zod3.z.string(),
|
|
343
|
+
closedPnl: import_zod3.z.number(),
|
|
344
|
+
fee: import_zod3.z.number(),
|
|
345
|
+
crossed: import_zod3.z.boolean(),
|
|
346
|
+
oid: import_zod3.z.number().int().nonnegative(),
|
|
347
|
+
tid: import_zod3.z.number().int().nonnegative()
|
|
348
|
+
});
|
|
349
|
+
var ledgerFillsPageSchema = import_zod3.z.object({
|
|
229
350
|
window: ledgerWindowSchema,
|
|
230
|
-
limit:
|
|
231
|
-
before:
|
|
232
|
-
hasMore:
|
|
233
|
-
nextBefore:
|
|
234
|
-
fetchedAt:
|
|
235
|
-
fills:
|
|
236
|
-
});
|
|
237
|
-
var ledgerPeriodSummarySchema =
|
|
238
|
-
realizedPnl:
|
|
239
|
-
totalFees:
|
|
240
|
-
fillCount:
|
|
241
|
-
closeCount:
|
|
242
|
-
winCount:
|
|
243
|
-
lossCount:
|
|
244
|
-
averageWin:
|
|
245
|
-
averageLoss:
|
|
246
|
-
});
|
|
247
|
-
var ledgerSummaryInputSchema =
|
|
351
|
+
limit: import_zod3.z.number().int().positive(),
|
|
352
|
+
before: import_zod3.z.number().int().nonnegative().optional(),
|
|
353
|
+
hasMore: import_zod3.z.boolean(),
|
|
354
|
+
nextBefore: import_zod3.z.number().int().nonnegative().optional(),
|
|
355
|
+
fetchedAt: import_zod3.z.string().datetime(),
|
|
356
|
+
fills: import_zod3.z.array(ledgerFillSchema)
|
|
357
|
+
});
|
|
358
|
+
var ledgerPeriodSummarySchema = import_zod3.z.object({
|
|
359
|
+
realizedPnl: import_zod3.z.number(),
|
|
360
|
+
totalFees: import_zod3.z.number(),
|
|
361
|
+
fillCount: import_zod3.z.number().int().nonnegative(),
|
|
362
|
+
closeCount: import_zod3.z.number().int().nonnegative(),
|
|
363
|
+
winCount: import_zod3.z.number().int().nonnegative(),
|
|
364
|
+
lossCount: import_zod3.z.number().int().nonnegative(),
|
|
365
|
+
averageWin: import_zod3.z.number(),
|
|
366
|
+
averageLoss: import_zod3.z.number()
|
|
367
|
+
});
|
|
368
|
+
var ledgerSummaryInputSchema = import_zod3.z.object({
|
|
248
369
|
window: ledgerWindowSchema.optional()
|
|
249
370
|
});
|
|
250
371
|
var ledgerSummarySchema = ledgerPeriodSummarySchema.extend({
|
|
251
372
|
window: ledgerWindowSchema,
|
|
252
|
-
generatedAt:
|
|
253
|
-
periods:
|
|
373
|
+
generatedAt: import_zod3.z.string().datetime(),
|
|
374
|
+
periods: import_zod3.z.object({
|
|
254
375
|
"24h": ledgerPeriodSummarySchema,
|
|
255
376
|
"7d": ledgerPeriodSummarySchema
|
|
256
377
|
})
|
|
257
378
|
});
|
|
258
|
-
var marketSnapshotScopeSchema =
|
|
259
|
-
var marketSnapshotResolvedScopeSchema =
|
|
260
|
-
var marketSnapshotValueSchema =
|
|
261
|
-
var marketSnapshotInputSchema =
|
|
379
|
+
var marketSnapshotScopeSchema = import_zod3.z.enum(["selected", "all"]);
|
|
380
|
+
var marketSnapshotResolvedScopeSchema = import_zod3.z.enum(["selected", "all", "explicit"]);
|
|
381
|
+
var marketSnapshotValueSchema = import_zod3.z.union([import_zod3.z.string(), import_zod3.z.number(), import_zod3.z.boolean(), import_zod3.z.null()]);
|
|
382
|
+
var marketSnapshotInputSchema = import_zod3.z.object({
|
|
262
383
|
scope: marketSnapshotScopeSchema.optional(),
|
|
263
|
-
symbols:
|
|
264
|
-
fields:
|
|
265
|
-
sort:
|
|
384
|
+
symbols: import_zod3.z.array(import_zod3.z.string().min(1)).min(1).optional(),
|
|
385
|
+
fields: import_zod3.z.array(import_zod3.z.string().min(1)).min(1).optional().describe("Table columns to return; sym and base always come back. Ids: rank, lp, bv, qv, ts, r1m, r3m, r5m, r15m, r30m, r1h, r2h, r4h, r6h, r8h, r12h, r24h, r3d, r7d, hmi, hmiv, reg, ob, de, hfi, ar, ms, vcc, vcp, obosd, obosp, vwap, vwz, poc, pocd, vap, macdh, macds, macdd, srs, srr, fh24, fl24, fh7, fl7, sr, az, hfiw, trv, tri, trh, hs."),
|
|
386
|
+
sort: import_zod3.z.enum(["rank"]).optional().describe('"rank" orders rows by market cap rank, unranked last.')
|
|
266
387
|
});
|
|
267
388
|
var marketSnapshotChunkInputSchema = marketSnapshotInputSchema.extend({
|
|
268
|
-
offset:
|
|
269
|
-
limit:
|
|
270
|
-
});
|
|
271
|
-
var marketFieldBlockSchema =
|
|
272
|
-
fieldIds:
|
|
273
|
-
values:
|
|
274
|
-
symbolLists:
|
|
275
|
-
});
|
|
276
|
-
var marketMatrixSchema =
|
|
277
|
-
fieldIds:
|
|
278
|
-
rows:
|
|
279
|
-
});
|
|
280
|
-
var marketFieldSemanticsSchema =
|
|
281
|
-
nullMeans:
|
|
282
|
-
percentScale:
|
|
283
|
-
percentFields:
|
|
284
|
-
changeMapPercentKeys:
|
|
285
|
-
});
|
|
286
|
-
var marketSnapshotSchema =
|
|
287
|
-
schema:
|
|
288
|
-
view:
|
|
389
|
+
offset: import_zod3.z.number().int().nonnegative().optional(),
|
|
390
|
+
limit: import_zod3.z.number().int().positive().max(100).optional()
|
|
391
|
+
});
|
|
392
|
+
var marketFieldBlockSchema = import_zod3.z.object({
|
|
393
|
+
fieldIds: import_zod3.z.array(import_zod3.z.string()).min(1),
|
|
394
|
+
values: import_zod3.z.array(marketSnapshotValueSchema),
|
|
395
|
+
symbolLists: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.array(import_zod3.z.string())).optional()
|
|
396
|
+
});
|
|
397
|
+
var marketMatrixSchema = import_zod3.z.object({
|
|
398
|
+
fieldIds: import_zod3.z.array(import_zod3.z.string()).min(1),
|
|
399
|
+
rows: import_zod3.z.array(import_zod3.z.array(marketSnapshotValueSchema))
|
|
400
|
+
});
|
|
401
|
+
var marketFieldSemanticsSchema = import_zod3.z.object({
|
|
402
|
+
nullMeans: import_zod3.z.literal("unavailable"),
|
|
403
|
+
percentScale: import_zod3.z.literal("already_percent"),
|
|
404
|
+
percentFields: import_zod3.z.array(import_zod3.z.string()).min(1),
|
|
405
|
+
changeMapPercentKeys: import_zod3.z.array(import_zod3.z.string()).min(1)
|
|
406
|
+
});
|
|
407
|
+
var marketSnapshotSchema = import_zod3.z.object({
|
|
408
|
+
schema: import_zod3.z.literal("helios.market_snapshot.v1"),
|
|
409
|
+
view: import_zod3.z.literal("market_overview"),
|
|
289
410
|
scope: marketSnapshotResolvedScopeSchema,
|
|
290
|
-
symbolCount:
|
|
291
|
-
generatedAt:
|
|
292
|
-
returnedSymbols:
|
|
411
|
+
symbolCount: import_zod3.z.number().int().nonnegative(),
|
|
412
|
+
generatedAt: import_zod3.z.string().datetime(),
|
|
413
|
+
returnedSymbols: import_zod3.z.array(import_zod3.z.string()),
|
|
293
414
|
semantics: marketFieldSemanticsSchema,
|
|
294
415
|
market: marketFieldBlockSchema,
|
|
295
416
|
table: marketMatrixSchema,
|
|
296
|
-
legend:
|
|
417
|
+
legend: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.union([import_zod3.z.string(), import_zod3.z.array(import_zod3.z.string())]))
|
|
297
418
|
});
|
|
298
|
-
var marketSnapshotChunkSchema =
|
|
299
|
-
schema:
|
|
300
|
-
view:
|
|
419
|
+
var marketSnapshotChunkSchema = import_zod3.z.object({
|
|
420
|
+
schema: import_zod3.z.literal("helios.market_snapshot_chunk.v1"),
|
|
421
|
+
view: import_zod3.z.literal("market_overview_chunk"),
|
|
301
422
|
scope: marketSnapshotResolvedScopeSchema,
|
|
302
|
-
totalSymbolCount:
|
|
303
|
-
generatedAt:
|
|
304
|
-
offset:
|
|
305
|
-
limit:
|
|
306
|
-
chunkSymbolCount:
|
|
307
|
-
hasMore:
|
|
308
|
-
nextOffset:
|
|
309
|
-
returnedSymbols:
|
|
423
|
+
totalSymbolCount: import_zod3.z.number().int().nonnegative(),
|
|
424
|
+
generatedAt: import_zod3.z.string().datetime(),
|
|
425
|
+
offset: import_zod3.z.number().int().nonnegative(),
|
|
426
|
+
limit: import_zod3.z.number().int().positive(),
|
|
427
|
+
chunkSymbolCount: import_zod3.z.number().int().nonnegative(),
|
|
428
|
+
hasMore: import_zod3.z.boolean(),
|
|
429
|
+
nextOffset: import_zod3.z.number().int().nonnegative().optional(),
|
|
430
|
+
returnedSymbols: import_zod3.z.array(import_zod3.z.string()),
|
|
310
431
|
semantics: marketFieldSemanticsSchema,
|
|
311
432
|
market: marketFieldBlockSchema,
|
|
312
433
|
table: marketMatrixSchema,
|
|
313
|
-
legend:
|
|
434
|
+
legend: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.union([import_zod3.z.string(), import_zod3.z.array(import_zod3.z.string())]))
|
|
314
435
|
});
|
|
315
|
-
var marketSymbolDetailInputSchema =
|
|
316
|
-
symbol:
|
|
317
|
-
full:
|
|
436
|
+
var marketSymbolDetailInputSchema = import_zod3.z.object({
|
|
437
|
+
symbol: import_zod3.z.string().min(1),
|
|
438
|
+
full: import_zod3.z.boolean().optional().describe("true adds every raw indicator and the price history (large, ~25 KB). Omit for the compact row and market context.")
|
|
318
439
|
});
|
|
319
|
-
var marketSymbolDetailSchema =
|
|
320
|
-
schema:
|
|
321
|
-
generatedAt:
|
|
322
|
-
symbol:
|
|
440
|
+
var marketSymbolDetailSchema = import_zod3.z.object({
|
|
441
|
+
schema: import_zod3.z.literal("helios.market_symbol_detail.v1"),
|
|
442
|
+
generatedAt: import_zod3.z.string().datetime(),
|
|
443
|
+
symbol: import_zod3.z.string(),
|
|
323
444
|
semantics: marketFieldSemanticsSchema,
|
|
324
445
|
market: marketFieldBlockSchema,
|
|
325
|
-
row:
|
|
326
|
-
symbolCtx:
|
|
327
|
-
changeMap:
|
|
328
|
-
metrics:
|
|
329
|
-
legend:
|
|
330
|
-
});
|
|
331
|
-
var tradingDirectionSchema =
|
|
332
|
-
var tradingExecutionStyleSchema =
|
|
333
|
-
var tradingSizeSchema =
|
|
334
|
-
mode:
|
|
335
|
-
value:
|
|
336
|
-
});
|
|
337
|
-
var tradingProtectionLegSchema =
|
|
338
|
-
kind:
|
|
339
|
-
triggerPrice:
|
|
340
|
-
limitPrice:
|
|
341
|
-
});
|
|
342
|
-
var tradingProtectionSchema =
|
|
446
|
+
row: import_zod3.z.record(import_zod3.z.string(), marketSnapshotValueSchema),
|
|
447
|
+
symbolCtx: import_zod3.z.record(import_zod3.z.string(), marketSnapshotValueSchema),
|
|
448
|
+
changeMap: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.number().nullable()),
|
|
449
|
+
metrics: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.unknown()).nullable(),
|
|
450
|
+
legend: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.union([import_zod3.z.string(), import_zod3.z.array(import_zod3.z.string())]))
|
|
451
|
+
});
|
|
452
|
+
var tradingDirectionSchema = import_zod3.z.enum(["LONG", "SHORT"]);
|
|
453
|
+
var tradingExecutionStyleSchema = import_zod3.z.enum(["MARKET", "LIMIT_TAKER", "LIMIT_MAKER", "LIMIT_GTC"]);
|
|
454
|
+
var tradingSizeSchema = import_zod3.z.object({
|
|
455
|
+
mode: import_zod3.z.enum(["MARGIN_USD", "BASE_QUANTITY"]),
|
|
456
|
+
value: import_zod3.z.number().positive()
|
|
457
|
+
});
|
|
458
|
+
var tradingProtectionLegSchema = import_zod3.z.object({
|
|
459
|
+
kind: import_zod3.z.enum(["LIMIT", "MARKET"]),
|
|
460
|
+
triggerPrice: import_zod3.z.number().positive(),
|
|
461
|
+
limitPrice: import_zod3.z.number().positive().optional()
|
|
462
|
+
});
|
|
463
|
+
var tradingProtectionSchema = import_zod3.z.object({
|
|
343
464
|
takeProfit: tradingProtectionLegSchema.optional(),
|
|
344
465
|
stopLoss: tradingProtectionLegSchema.optional()
|
|
345
466
|
});
|
|
346
|
-
var
|
|
347
|
-
var
|
|
467
|
+
var tradingSymbolSchema = import_zod3.z.string().regex(/^(?:[a-zA-Z0-9]{1,10}:)?[a-zA-Z0-9]+(?:USDT)?$/);
|
|
468
|
+
var tradeIntentTypeSchema = import_zod3.z.enum(["OPEN", "DCA", "REVERSE", "CLOSE"]);
|
|
469
|
+
var submitTradeIntentInputSchema = import_zod3.z.object({
|
|
348
470
|
provider: providerSchema.optional(),
|
|
349
|
-
symbol:
|
|
471
|
+
symbol: tradingSymbolSchema,
|
|
350
472
|
intentType: tradeIntentTypeSchema,
|
|
351
473
|
direction: tradingDirectionSchema,
|
|
352
474
|
size: tradingSizeSchema,
|
|
353
|
-
execution:
|
|
475
|
+
execution: import_zod3.z.object({
|
|
354
476
|
style: tradingExecutionStyleSchema,
|
|
355
|
-
timeInForce:
|
|
356
|
-
limitPrice:
|
|
477
|
+
timeInForce: import_zod3.z.enum(["GTC", "IOC", "FOK", "GTX"]).optional(),
|
|
478
|
+
limitPrice: import_zod3.z.number().positive().optional()
|
|
357
479
|
}),
|
|
358
|
-
maxSlippageBps:
|
|
359
|
-
leverageOverride:
|
|
360
|
-
marginModeOverride:
|
|
361
|
-
reduceOnly:
|
|
480
|
+
maxSlippageBps: import_zod3.z.number().int().nonnegative().max(1e3).optional(),
|
|
481
|
+
leverageOverride: import_zod3.z.number().positive().optional(),
|
|
482
|
+
marginModeOverride: import_zod3.z.enum(["ISOLATED", "CROSS"]).optional(),
|
|
483
|
+
reduceOnly: import_zod3.z.boolean().optional(),
|
|
362
484
|
protection: tradingProtectionSchema.optional(),
|
|
363
485
|
/** One per user action, reused on retries: the data-bridge never places a second order for the same key. */
|
|
364
|
-
idempotencyKey:
|
|
486
|
+
idempotencyKey: import_zod3.z.string().regex(/^[A-Za-z0-9_-]{8,64}$/).optional(),
|
|
487
|
+
/** With it, idempotencyKey is the order's fingerprint: repeats within this many seconds of the first are one order. */
|
|
488
|
+
idempotencyWindowSeconds: import_zod3.z.number().int().min(10).max(600).optional()
|
|
365
489
|
});
|
|
366
|
-
var submitTradeIntentResultSchema =
|
|
367
|
-
status:
|
|
490
|
+
var submitTradeIntentResultSchema = import_zod3.z.object({
|
|
491
|
+
status: import_zod3.z.literal("submitted"),
|
|
368
492
|
/** `replayed`: an earlier attempt of this action already placed the order; nothing new was sent. */
|
|
369
|
-
outcome:
|
|
370
|
-
clientOrderId:
|
|
371
|
-
orderStatus:
|
|
493
|
+
outcome: import_zod3.z.enum(["placed", "replayed"]).optional(),
|
|
494
|
+
clientOrderId: import_zod3.z.string().optional(),
|
|
495
|
+
orderStatus: import_zod3.z.string().optional(),
|
|
372
496
|
/** TP/SL legs Hyperliquid refused while the entry went through: the position is open without them. */
|
|
373
|
-
rejectedProtection:
|
|
497
|
+
rejectedProtection: import_zod3.z.array(import_zod3.z.object({ kind: import_zod3.z.string(), reason: import_zod3.z.string() })).optional(),
|
|
374
498
|
provider: providerSchema.optional(),
|
|
375
|
-
symbol:
|
|
499
|
+
symbol: import_zod3.z.string(),
|
|
376
500
|
intentType: tradeIntentTypeSchema,
|
|
377
501
|
direction: tradingDirectionSchema,
|
|
378
502
|
size: tradingSizeSchema,
|
|
379
|
-
execution:
|
|
503
|
+
execution: import_zod3.z.object({
|
|
380
504
|
style: tradingExecutionStyleSchema,
|
|
381
|
-
timeInForce:
|
|
382
|
-
limitPrice:
|
|
505
|
+
timeInForce: import_zod3.z.enum(["GTC", "IOC", "FOK", "GTX"]).optional(),
|
|
506
|
+
limitPrice: import_zod3.z.number().positive().optional()
|
|
383
507
|
}),
|
|
384
|
-
maxSlippageBps:
|
|
385
|
-
leverageOverride:
|
|
386
|
-
marginModeOverride:
|
|
387
|
-
reduceOnly:
|
|
388
|
-
hasProtection:
|
|
508
|
+
maxSlippageBps: import_zod3.z.number().int().nonnegative().max(1e3).optional(),
|
|
509
|
+
leverageOverride: import_zod3.z.number().positive().optional(),
|
|
510
|
+
marginModeOverride: import_zod3.z.enum(["ISOLATED", "CROSS"]).optional(),
|
|
511
|
+
reduceOnly: import_zod3.z.boolean(),
|
|
512
|
+
hasProtection: import_zod3.z.boolean()
|
|
389
513
|
});
|
|
390
|
-
var closePositionInputSchema =
|
|
514
|
+
var closePositionInputSchema = import_zod3.z.object({
|
|
391
515
|
provider: providerSchema.optional(),
|
|
392
|
-
symbol:
|
|
393
|
-
maxSlippageBps:
|
|
516
|
+
symbol: tradingSymbolSchema,
|
|
517
|
+
maxSlippageBps: import_zod3.z.number().int().nonnegative().max(1e3).optional(),
|
|
518
|
+
/** The share of the position to close (0 < fraction < 1 closes part of it); omitted or 1 closes it all. */
|
|
519
|
+
fraction: import_zod3.z.number().positive().max(1).optional()
|
|
394
520
|
});
|
|
395
|
-
var closePositionResultSchema =
|
|
396
|
-
status:
|
|
521
|
+
var closePositionResultSchema = import_zod3.z.object({
|
|
522
|
+
status: import_zod3.z.literal("submitted"),
|
|
523
|
+
/** `no_position`: nothing was open on this coin, so no order was sent. Absent from older data-bridges. */
|
|
524
|
+
outcome: import_zod3.z.enum(["closed", "no_position"]).optional(),
|
|
397
525
|
provider: providerSchema.optional(),
|
|
398
|
-
symbol:
|
|
399
|
-
mode:
|
|
400
|
-
|
|
526
|
+
symbol: import_zod3.z.string(),
|
|
527
|
+
mode: import_zod3.z.enum(["FULL", "PARTIAL"]),
|
|
528
|
+
fraction: import_zod3.z.number().positive().max(1).optional(),
|
|
529
|
+
maxSlippageBps: import_zod3.z.number().int().nonnegative().max(1e3).optional()
|
|
401
530
|
});
|
|
402
|
-
var setTpSlInputSchema =
|
|
531
|
+
var setTpSlInputSchema = import_zod3.z.object({
|
|
403
532
|
provider: providerSchema.optional(),
|
|
404
|
-
symbol:
|
|
405
|
-
applyTo:
|
|
406
|
-
quantity:
|
|
407
|
-
parentClientOrderId:
|
|
533
|
+
symbol: tradingSymbolSchema,
|
|
534
|
+
applyTo: import_zod3.z.enum(["FULL_POSITION", "CUSTOM_QUANTITY"]),
|
|
535
|
+
quantity: import_zod3.z.number().positive().optional(),
|
|
536
|
+
parentClientOrderId: import_zod3.z.string(),
|
|
408
537
|
takeProfit: tradingProtectionLegSchema.optional(),
|
|
409
538
|
stopLoss: tradingProtectionLegSchema.optional()
|
|
410
539
|
});
|
|
411
|
-
var setTpSlResultSchema =
|
|
412
|
-
status:
|
|
540
|
+
var setTpSlResultSchema = import_zod3.z.object({
|
|
541
|
+
status: import_zod3.z.literal("submitted"),
|
|
413
542
|
provider: providerSchema.optional(),
|
|
414
|
-
symbol:
|
|
415
|
-
applyTo:
|
|
416
|
-
quantity:
|
|
417
|
-
parentClientOrderId:
|
|
418
|
-
hasTakeProfit:
|
|
419
|
-
hasStopLoss:
|
|
420
|
-
});
|
|
421
|
-
var deleteTpSlInputSchema =
|
|
543
|
+
symbol: import_zod3.z.string(),
|
|
544
|
+
applyTo: import_zod3.z.enum(["FULL_POSITION", "CUSTOM_QUANTITY"]),
|
|
545
|
+
quantity: import_zod3.z.number().positive().optional(),
|
|
546
|
+
parentClientOrderId: import_zod3.z.string(),
|
|
547
|
+
hasTakeProfit: import_zod3.z.boolean(),
|
|
548
|
+
hasStopLoss: import_zod3.z.boolean()
|
|
549
|
+
});
|
|
550
|
+
var deleteTpSlInputSchema = import_zod3.z.object({
|
|
422
551
|
provider: providerSchema.optional(),
|
|
423
|
-
symbol:
|
|
424
|
-
orderId:
|
|
552
|
+
symbol: tradingSymbolSchema,
|
|
553
|
+
orderId: import_zod3.z.string()
|
|
425
554
|
});
|
|
426
|
-
var deleteTpSlResultSchema =
|
|
427
|
-
status:
|
|
555
|
+
var deleteTpSlResultSchema = import_zod3.z.object({
|
|
556
|
+
status: import_zod3.z.literal("deleted"),
|
|
428
557
|
provider: providerSchema.optional(),
|
|
429
|
-
symbol:
|
|
430
|
-
orderId:
|
|
431
|
-
});
|
|
432
|
-
var openOrderSchema = import_zod.z.object({
|
|
433
|
-
coin: import_zod.z.string(),
|
|
434
|
-
side: import_zod.z.enum(["B", "A"]),
|
|
435
|
-
limitPx: import_zod.z.string(),
|
|
436
|
-
sz: import_zod.z.string(),
|
|
437
|
-
oid: import_zod.z.number().int().nonnegative(),
|
|
438
|
-
timestamp: import_zod.z.number().int().nonnegative(),
|
|
439
|
-
origSz: import_zod.z.string(),
|
|
440
|
-
orderType: import_zod.z.string(),
|
|
441
|
-
tif: import_zod.z.string().nullable(),
|
|
442
|
-
reduceOnly: import_zod.z.boolean(),
|
|
443
|
-
isTrigger: import_zod.z.boolean(),
|
|
444
|
-
triggerPx: import_zod.z.string(),
|
|
445
|
-
cloid: import_zod.z.string().nullable(),
|
|
446
|
-
assetId: import_zod.z.number().int().nonnegative().optional()
|
|
447
|
-
});
|
|
448
|
-
var cancelOrderInputSchema = import_zod.z.object({
|
|
449
|
-
symbol: import_zod.z.string().regex(/^[a-zA-Z0-9]+(?:USDT)?$/),
|
|
450
|
-
oid: import_zod.z.number().int().nonnegative()
|
|
451
|
-
});
|
|
452
|
-
var cancelOrderResultSchema = import_zod.z.object({
|
|
453
|
-
status: import_zod.z.literal("cancelled"),
|
|
454
|
-
symbol: import_zod.z.string(),
|
|
455
|
-
oid: import_zod.z.number().int().nonnegative()
|
|
558
|
+
symbol: import_zod3.z.string(),
|
|
559
|
+
orderId: import_zod3.z.string()
|
|
456
560
|
});
|
|
457
|
-
var
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
561
|
+
var openOrderSchema = import_zod3.z.object({
|
|
562
|
+
coin: import_zod3.z.string(),
|
|
563
|
+
side: import_zod3.z.enum(["B", "A"]),
|
|
564
|
+
limitPx: import_zod3.z.string(),
|
|
565
|
+
sz: import_zod3.z.string(),
|
|
566
|
+
oid: import_zod3.z.number().int().nonnegative(),
|
|
567
|
+
timestamp: import_zod3.z.number().int().nonnegative(),
|
|
568
|
+
origSz: import_zod3.z.string(),
|
|
569
|
+
orderType: import_zod3.z.string(),
|
|
570
|
+
tif: import_zod3.z.string().nullable(),
|
|
571
|
+
reduceOnly: import_zod3.z.boolean(),
|
|
572
|
+
isTrigger: import_zod3.z.boolean(),
|
|
573
|
+
triggerPx: import_zod3.z.string(),
|
|
574
|
+
cloid: import_zod3.z.string().nullable(),
|
|
575
|
+
assetId: import_zod3.z.number().int().nonnegative().optional()
|
|
461
576
|
});
|
|
462
|
-
var
|
|
577
|
+
var cancelOrderInputSchema = import_zod3.z.object({
|
|
578
|
+
symbol: tradingSymbolSchema,
|
|
579
|
+
oid: import_zod3.z.number().int().nonnegative()
|
|
580
|
+
});
|
|
581
|
+
var cancelOrderResultSchema = import_zod3.z.object({
|
|
582
|
+
status: import_zod3.z.literal("cancelled"),
|
|
583
|
+
symbol: import_zod3.z.string(),
|
|
584
|
+
oid: import_zod3.z.number().int().nonnegative()
|
|
585
|
+
});
|
|
586
|
+
var modifyOrderInputSchema = import_zod3.z.object({
|
|
587
|
+
symbol: tradingSymbolSchema,
|
|
588
|
+
oid: import_zod3.z.number().int().nonnegative(),
|
|
589
|
+
price: import_zod3.z.number().positive().finite(),
|
|
590
|
+
/** Size in the coin (base quantity). */
|
|
591
|
+
size: import_zod3.z.number().positive().finite()
|
|
592
|
+
});
|
|
593
|
+
var modifyOrderResultSchema = import_zod3.z.object({
|
|
594
|
+
status: import_zod3.z.literal("modified"),
|
|
595
|
+
symbol: import_zod3.z.string(),
|
|
596
|
+
oid: import_zod3.z.number().int().nonnegative(),
|
|
597
|
+
price: import_zod3.z.number(),
|
|
598
|
+
size: import_zod3.z.number()
|
|
599
|
+
});
|
|
600
|
+
var AGENT_PAPER_ACCOUNT = "agent";
|
|
601
|
+
var paperFillSchema = import_zod3.z.object({
|
|
602
|
+
ts: import_zod3.z.number(),
|
|
603
|
+
coin: import_zod3.z.string(),
|
|
604
|
+
kind: import_zod3.z.string(),
|
|
605
|
+
side: import_zod3.z.enum(["BUY", "SELL"]).nullable(),
|
|
606
|
+
size: import_zod3.z.number(),
|
|
607
|
+
price: import_zod3.z.number(),
|
|
608
|
+
fee: import_zod3.z.number(),
|
|
609
|
+
pnl: import_zod3.z.number()
|
|
610
|
+
});
|
|
611
|
+
var paperPortfolioSchema = portfolioSnapshotSchema.extend({
|
|
612
|
+
cash: import_zod3.z.number().optional(),
|
|
613
|
+
realizedPnl: import_zod3.z.number().optional(),
|
|
614
|
+
feesPaid: import_zod3.z.number().optional(),
|
|
615
|
+
fundingPaid: import_zod3.z.number().optional(),
|
|
616
|
+
liquidations: import_zod3.z.number().optional()
|
|
617
|
+
});
|
|
618
|
+
var agentSetupSchema = import_zod3.z.enum(["turning up from breakdown", "falling, no turn yet", "stretched up", "stretched down", "neutral"]);
|
|
619
|
+
var smSideNameSchema = import_zod3.z.enum(["long", "short"]);
|
|
620
|
+
var agentMarketInputSchema = import_zod3.z.object({
|
|
621
|
+
coins: import_zod3.z.array(import_zod3.z.string().min(1)).min(1).max(50).optional(),
|
|
622
|
+
limit: import_zod3.z.number().int().min(1).max(300).optional(),
|
|
623
|
+
offset: import_zod3.z.number().int().nonnegative().optional(),
|
|
624
|
+
sort: import_zod3.z.enum(["volume", "rank", "move1h", "move24h", "funding", "lean", "edge"]).optional(),
|
|
625
|
+
filter: import_zod3.z.enum(["turning", "falling", "stretched_up", "stretched_down", "sm_long", "sm_short", "discount"]).optional()
|
|
626
|
+
});
|
|
627
|
+
var agentMarketRowSchema = import_zod3.z.object({
|
|
628
|
+
coin: import_zod3.z.string(),
|
|
629
|
+
rank: import_zod3.z.number().nullable(),
|
|
630
|
+
px: import_zod3.z.number().nullable(),
|
|
631
|
+
r15m: import_zod3.z.number().nullable(),
|
|
632
|
+
r1h: import_zod3.z.number().nullable(),
|
|
633
|
+
r24h: import_zod3.z.number().nullable(),
|
|
634
|
+
volUsd: import_zod3.z.number().nullable(),
|
|
635
|
+
fundAprPct: import_zod3.z.number().nullable(),
|
|
636
|
+
oiUsd: import_zod3.z.number().nullable(),
|
|
637
|
+
setup: agentSetupSchema.nullable()
|
|
638
|
+
});
|
|
639
|
+
var agentMarketSchema = import_zod3.z.object({
|
|
640
|
+
asOf: import_zod3.z.string().nullable(),
|
|
641
|
+
smAsOf: import_zod3.z.number().nullable(),
|
|
642
|
+
market: import_zod3.z.object({ os: import_zod3.z.number().nullable(), mti: import_zod3.z.number().nullable(), Mti: import_zod3.z.number().nullable() }),
|
|
643
|
+
total: import_zod3.z.number().int().nonnegative(),
|
|
644
|
+
offset: import_zod3.z.number().int().nonnegative(),
|
|
645
|
+
rows: import_zod3.z.array(
|
|
646
|
+
agentMarketRowSchema.extend({
|
|
647
|
+
sm: import_zod3.z.object({
|
|
648
|
+
side: smSideNameSchema.nullable(),
|
|
649
|
+
share: import_zod3.z.number(),
|
|
650
|
+
edgePct: import_zod3.z.number().nullable(),
|
|
651
|
+
discount: import_zod3.z.enum(["strict", "fill"]).nullable()
|
|
652
|
+
}).nullable()
|
|
653
|
+
})
|
|
654
|
+
),
|
|
655
|
+
next: import_zod3.z.number().int().nonnegative().nullable(),
|
|
656
|
+
unknown: import_zod3.z.array(import_zod3.z.string())
|
|
657
|
+
});
|
|
658
|
+
var smPileInSchema = import_zod3.z.object({
|
|
659
|
+
coin: import_zod3.z.string(),
|
|
660
|
+
side: smSideNameSchema,
|
|
661
|
+
dir: import_zod3.z.enum(["in", "out"]),
|
|
662
|
+
traders: import_zod3.z.number(),
|
|
663
|
+
totalUsd: import_zod3.z.number(),
|
|
664
|
+
weight: import_zod3.z.number(),
|
|
665
|
+
names: import_zod3.z.array(import_zod3.z.string()),
|
|
666
|
+
first: import_zod3.z.number(),
|
|
667
|
+
last: import_zod3.z.number()
|
|
668
|
+
});
|
|
669
|
+
var smartMoneyBoardInputSchema = import_zod3.z.object({
|
|
670
|
+
view: import_zod3.z.enum(["conviction", "discount", "flows", "pileins"]).optional(),
|
|
671
|
+
side: import_zod3.z.enum(["long", "short", "any"]).optional(),
|
|
672
|
+
limit: import_zod3.z.number().int().min(1).max(25).optional()
|
|
673
|
+
});
|
|
674
|
+
var smartMoneyBoardSchema = import_zod3.z.object({
|
|
675
|
+
asOf: import_zod3.z.number().nullable(),
|
|
676
|
+
view: import_zod3.z.enum(["conviction", "discount", "flows", "pileins"]),
|
|
677
|
+
wallets: import_zod3.z.number(),
|
|
678
|
+
known: import_zod3.z.number(),
|
|
679
|
+
rows: import_zod3.z.array(
|
|
680
|
+
import_zod3.z.object({
|
|
681
|
+
coin: import_zod3.z.string(),
|
|
682
|
+
side: smSideNameSchema.nullable(),
|
|
683
|
+
share: import_zod3.z.number(),
|
|
684
|
+
lean: import_zod3.z.number(),
|
|
685
|
+
sideUsd: import_zod3.z.number(),
|
|
686
|
+
heads: import_zod3.z.number(),
|
|
687
|
+
traders: import_zod3.z.number(),
|
|
688
|
+
entry: import_zod3.z.number().nullable(),
|
|
689
|
+
mark: import_zod3.z.number().nullable(),
|
|
690
|
+
edgePct: import_zod3.z.number().nullable(),
|
|
691
|
+
flow1hUsd: import_zod3.z.number(),
|
|
692
|
+
discount: import_zod3.z.enum(["strict", "fill"]).nullable(),
|
|
693
|
+
setup: agentSetupSchema.nullable(),
|
|
694
|
+
r24h: import_zod3.z.number().nullable()
|
|
695
|
+
})
|
|
696
|
+
),
|
|
697
|
+
pileIns: import_zod3.z.array(smPileInSchema),
|
|
698
|
+
note: import_zod3.z.string().nullable()
|
|
699
|
+
});
|
|
700
|
+
var smartMoneyMovesInputSchema = import_zod3.z.object({
|
|
701
|
+
since: import_zod3.z.string().regex(/^\d{1,16}-\d{1,16}$/).optional(),
|
|
702
|
+
coin: import_zod3.z.string().min(1).optional(),
|
|
703
|
+
kind: import_zod3.z.enum(["entries", "exits", "all"]).optional(),
|
|
704
|
+
minUsd: import_zod3.z.number().nonnegative().optional(),
|
|
705
|
+
limit: import_zod3.z.number().int().min(1).max(50).optional()
|
|
706
|
+
});
|
|
707
|
+
var smMoveSchema = import_zod3.z.object({
|
|
708
|
+
id: import_zod3.z.string(),
|
|
709
|
+
t: import_zod3.z.number(),
|
|
710
|
+
coin: import_zod3.z.string(),
|
|
711
|
+
kind: import_zod3.z.enum(["open", "add", "reduce", "close", "flip"]),
|
|
712
|
+
side: smSideNameSchema,
|
|
713
|
+
usd: import_zod3.z.number(),
|
|
714
|
+
px: import_zod3.z.number(),
|
|
715
|
+
lev: import_zod3.z.number().nullable(),
|
|
716
|
+
pnl: import_zod3.z.number().nullable(),
|
|
717
|
+
pnlPct: import_zod3.z.number().nullable(),
|
|
718
|
+
nick: import_zod3.z.string(),
|
|
719
|
+
rank: import_zod3.z.number(),
|
|
720
|
+
style: import_zod3.z.string(),
|
|
721
|
+
win: import_zod3.z.number().nullable(),
|
|
722
|
+
w: import_zod3.z.number()
|
|
723
|
+
});
|
|
724
|
+
var smartMoneyMovesSchema = import_zod3.z.object({
|
|
725
|
+
order: import_zod3.z.enum(["newest", "oldest"]),
|
|
726
|
+
moves: import_zod3.z.array(smMoveSchema),
|
|
727
|
+
next: import_zod3.z.string().nullable(),
|
|
728
|
+
more: import_zod3.z.boolean(),
|
|
729
|
+
gap: import_zod3.z.boolean(),
|
|
730
|
+
note: import_zod3.z.string().nullable()
|
|
731
|
+
});
|
|
732
|
+
var smBookSideSchema = import_zod3.z.object({ usd: import_zod3.z.number(), heads: import_zod3.z.number(), traders: import_zod3.z.number(), entry: import_zod3.z.number().nullable() });
|
|
733
|
+
var smartMoneyCoinSchema = import_zod3.z.object({
|
|
734
|
+
coin: import_zod3.z.string(),
|
|
735
|
+
asOf: import_zod3.z.number().nullable(),
|
|
736
|
+
sm: import_zod3.z.object({
|
|
737
|
+
coin: import_zod3.z.string(),
|
|
738
|
+
mark: import_zod3.z.number().nullable(),
|
|
739
|
+
long: smBookSideSchema,
|
|
740
|
+
short: smBookSideSchema,
|
|
741
|
+
lean: import_zod3.z.number(),
|
|
742
|
+
side: smSideNameSchema.nullable(),
|
|
743
|
+
share: import_zod3.z.number(),
|
|
744
|
+
edgePct: import_zod3.z.number().nullable(),
|
|
745
|
+
flow1hUsd: import_zod3.z.number(),
|
|
746
|
+
conviction: import_zod3.z.number().nullable(),
|
|
747
|
+
discount: import_zod3.z.enum(["strict", "fill"]).nullable(),
|
|
748
|
+
pileIn: smPileInSchema.nullable()
|
|
749
|
+
}).nullable(),
|
|
750
|
+
market: agentMarketRowSchema.nullable(),
|
|
751
|
+
moves: import_zod3.z.array(smMoveSchema),
|
|
752
|
+
note: import_zod3.z.string().nullable()
|
|
753
|
+
});
|
|
754
|
+
var marketCandlesInputSchema = import_zod3.z.object({
|
|
755
|
+
symbol: import_zod3.z.string().regex(/^[A-Za-z0-9]{1,20}$/),
|
|
756
|
+
interval: import_zod3.z.enum(["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "1w", "1M"]),
|
|
757
|
+
endTime: import_zod3.z.number().int().positive().optional(),
|
|
758
|
+
limit: import_zod3.z.number().int().min(1).max(10080).optional()
|
|
759
|
+
});
|
|
760
|
+
var marketCandlesSchema = import_zod3.z.object({
|
|
761
|
+
candles: import_zod3.z.array(import_zod3.z.object({ t: import_zod3.z.number(), o: import_zod3.z.number(), h: import_zod3.z.number(), l: import_zod3.z.number(), c: import_zod3.z.number(), v: import_zod3.z.number() })),
|
|
762
|
+
complete: import_zod3.z.boolean()
|
|
763
|
+
});
|
|
764
|
+
var DEFAULT_AGENT_REFRESH_WINDOW_MS = 60 * 1e3;
|
|
765
|
+
var agentConnectionStatusSchema = import_zod4.z.enum([
|
|
766
|
+
"disconnected",
|
|
767
|
+
"pending",
|
|
768
|
+
"connected",
|
|
769
|
+
"rejected",
|
|
770
|
+
"expired",
|
|
771
|
+
"failed"
|
|
772
|
+
]);
|
|
773
|
+
var agentSessionIssuerSchema = import_zod4.z.object({
|
|
774
|
+
identityServiceUrl: import_zod4.z.string(),
|
|
775
|
+
dataBridgeUrl: import_zod4.z.string()
|
|
776
|
+
});
|
|
777
|
+
var storedAgentSessionSchema = import_zod4.z.object({
|
|
778
|
+
accessToken: import_zod4.z.string(),
|
|
779
|
+
refreshToken: import_zod4.z.string(),
|
|
780
|
+
tokenType: agentTokenResponseSchema.shape.tokenType,
|
|
781
|
+
ownerUserId: import_zod4.z.string(),
|
|
782
|
+
agentGrantId: import_zod4.z.string(),
|
|
783
|
+
scope: import_zod4.z.array(agentScopeSchema).min(1),
|
|
784
|
+
accessTokenExpiresAt: import_zod4.z.string().datetime(),
|
|
785
|
+
createdAt: import_zod4.z.string().datetime(),
|
|
786
|
+
lastRefreshedAt: import_zod4.z.string().datetime(),
|
|
787
|
+
issuer: agentSessionIssuerSchema.nullish()
|
|
788
|
+
});
|
|
789
|
+
var agentSessionViewSchema = import_zod4.z.object({
|
|
790
|
+
connected: import_zod4.z.boolean(),
|
|
791
|
+
status: agentConnectionStatusSchema,
|
|
792
|
+
ownerUserId: import_zod4.z.string().optional(),
|
|
793
|
+
agentGrantId: import_zod4.z.string().optional(),
|
|
794
|
+
scope: import_zod4.z.array(agentScopeSchema).optional(),
|
|
795
|
+
accessTokenExpiresAt: import_zod4.z.string().datetime().optional(),
|
|
796
|
+
lastRefreshedAt: import_zod4.z.string().datetime().optional()
|
|
797
|
+
});
|
|
798
|
+
function createStoredAgentSession(tokenResponse, now = /* @__PURE__ */ new Date()) {
|
|
799
|
+
const createdAt = now.toISOString();
|
|
800
|
+
return {
|
|
801
|
+
accessToken: tokenResponse.accessToken,
|
|
802
|
+
refreshToken: tokenResponse.refreshToken,
|
|
803
|
+
tokenType: tokenResponse.tokenType,
|
|
804
|
+
ownerUserId: tokenResponse.ownerUserId,
|
|
805
|
+
agentGrantId: tokenResponse.agentGrantId,
|
|
806
|
+
scope: tokenResponse.scope,
|
|
807
|
+
accessTokenExpiresAt: new Date(now.getTime() + tokenResponse.expiresInSeconds * 1e3).toISOString(),
|
|
808
|
+
createdAt,
|
|
809
|
+
lastRefreshedAt: createdAt
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
function sanitizeAgentSession(session, status = session ? "connected" : "disconnected") {
|
|
813
|
+
if (!session) {
|
|
814
|
+
return {
|
|
815
|
+
connected: false,
|
|
816
|
+
status: status === "connected" ? "disconnected" : status
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
return {
|
|
820
|
+
connected: true,
|
|
821
|
+
status,
|
|
822
|
+
ownerUserId: session.ownerUserId,
|
|
823
|
+
agentGrantId: session.agentGrantId,
|
|
824
|
+
scope: session.scope,
|
|
825
|
+
accessTokenExpiresAt: session.accessTokenExpiresAt,
|
|
826
|
+
lastRefreshedAt: session.lastRefreshedAt
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
function shouldRefreshAgentSession(session, now = /* @__PURE__ */ new Date(), refreshWindowMs = DEFAULT_AGENT_REFRESH_WINDOW_MS) {
|
|
830
|
+
return new Date(session.accessTokenExpiresAt).getTime() - now.getTime() <= refreshWindowMs;
|
|
831
|
+
}
|
|
832
|
+
var pendingAgentLoginSchema = import_zod.z.object({
|
|
833
|
+
status: import_zod.z.literal("pending"),
|
|
834
|
+
deviceCode: import_zod.z.string(),
|
|
835
|
+
userCode: import_zod.z.string(),
|
|
836
|
+
verificationUri: import_zod.z.string().url(),
|
|
837
|
+
verificationUriComplete: import_zod.z.string().url(),
|
|
838
|
+
expiresInSeconds: import_zod.z.number().int().positive(),
|
|
839
|
+
pollIntervalSeconds: import_zod.z.number().int().positive(),
|
|
840
|
+
requestedScope: import_zod.z.array(agentScopeSchema).min(1),
|
|
841
|
+
startedAt: import_zod.z.string().datetime()
|
|
842
|
+
});
|
|
843
|
+
var DEFAULT_SCOPE = [AGENT_SCOPES.ACCOUNT_READ, AGENT_SCOPES.MARKET_READ];
|
|
844
|
+
var AgentAuthService = class {
|
|
463
845
|
constructor(options) {
|
|
464
|
-
|
|
465
|
-
__publicField2(this, "
|
|
466
|
-
__publicField2(this, "
|
|
467
|
-
__publicField2(this, "
|
|
468
|
-
__publicField2(this, "
|
|
469
|
-
this
|
|
470
|
-
this
|
|
471
|
-
this
|
|
472
|
-
this
|
|
473
|
-
this
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
846
|
+
__publicField2(this, "client");
|
|
847
|
+
__publicField2(this, "sessionStore");
|
|
848
|
+
__publicField2(this, "messages");
|
|
849
|
+
__publicField2(this, "refreshWindowMs");
|
|
850
|
+
__publicField2(this, "now");
|
|
851
|
+
__publicField2(this, "defaultClientName");
|
|
852
|
+
__publicField2(this, "deviceLogin");
|
|
853
|
+
__publicField2(this, "reloginForMissingScope");
|
|
854
|
+
__publicField2(this, "refreshInFlight", null);
|
|
855
|
+
__publicField2(this, "exchangeInFlight", null);
|
|
856
|
+
this.client = options.client;
|
|
857
|
+
this.sessionStore = options.sessionStore;
|
|
858
|
+
this.messages = options.messages;
|
|
859
|
+
this.refreshWindowMs = options.refreshWindowMs ?? DEFAULT_AGENT_REFRESH_WINDOW_MS;
|
|
860
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
861
|
+
this.defaultClientName = options.defaultClientName;
|
|
862
|
+
this.deviceLogin = options.deviceLogin ?? true;
|
|
863
|
+
this.reloginForMissingScope = options.reloginForMissingScope ?? false;
|
|
864
|
+
}
|
|
865
|
+
async startLogin(input) {
|
|
866
|
+
if (!this.deviceLogin) {
|
|
867
|
+
throw new Error(this.messages.reconnect);
|
|
868
|
+
}
|
|
869
|
+
const response = await this.client.startLogin(input);
|
|
870
|
+
const result = {
|
|
871
|
+
status: "pending",
|
|
872
|
+
...response,
|
|
873
|
+
requestedScope: input.scope,
|
|
874
|
+
startedAt: this.now().toISOString()
|
|
875
|
+
};
|
|
876
|
+
await this.sessionStore.setPendingLogin(result);
|
|
877
|
+
return result;
|
|
878
|
+
}
|
|
879
|
+
async resetSessionAndStartLogin(input) {
|
|
880
|
+
await this.clearSessionState();
|
|
881
|
+
return this.startLogin(input);
|
|
882
|
+
}
|
|
883
|
+
async startRelogin(options = {}) {
|
|
884
|
+
const started = await this.resetSessionAndStartLogin(this.loginInput(options));
|
|
885
|
+
return this.pendingResult(started);
|
|
886
|
+
}
|
|
887
|
+
async clearSessionState() {
|
|
888
|
+
await this.sessionStore.clearSession();
|
|
889
|
+
await this.sessionStore.clearPendingLogin();
|
|
890
|
+
}
|
|
891
|
+
/** After a 401 from Helios: drops the session and starts a fresh login. Null for any other error. */
|
|
892
|
+
async invalidateIfUnauthorized(error, options = {}) {
|
|
893
|
+
if (!isHeliosAgentApiError(error) || error.status !== 401) {
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
return this.startRelogin(options);
|
|
897
|
+
}
|
|
898
|
+
/** After a 401 from Helios: refreshes the session even if it isn't near expiry. Null for any other error. */
|
|
899
|
+
async refreshSessionAfterUnauthorized(error) {
|
|
900
|
+
if (!isHeliosAgentApiError(error) || error.status !== 401) {
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
const session = await this.sessionStore.getSession();
|
|
904
|
+
return session ? this.refreshStoredSession(session, true) : null;
|
|
905
|
+
}
|
|
906
|
+
async ensureLogin(options = {}) {
|
|
907
|
+
const requestedScope = options.scope ?? DEFAULT_SCOPE;
|
|
908
|
+
const requestedConstraints = options.constraints ?? {};
|
|
909
|
+
const session = await this.getValidSession();
|
|
910
|
+
if (session) {
|
|
911
|
+
if (!this.reloginForMissingScope || hasScopes(session, requestedScope)) {
|
|
912
|
+
return connectedResult(session);
|
|
913
|
+
}
|
|
914
|
+
await this.sessionStore.clearSession();
|
|
915
|
+
}
|
|
916
|
+
const pendingLogin = await this.getPendingLogin(requestedScope, requestedConstraints);
|
|
917
|
+
if (pendingLogin) {
|
|
918
|
+
const polled = await this.exchangePendingLogin(pendingLogin.deviceCode).catch(() => null);
|
|
919
|
+
if (polled?.status === "connected") return polled;
|
|
920
|
+
const stillPending = await this.getPendingLogin(requestedScope, requestedConstraints);
|
|
921
|
+
if (stillPending) return this.pendingResult(stillPending);
|
|
922
|
+
}
|
|
923
|
+
return this.pendingResult(await this.startLogin(this.loginInput(options)));
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Polls the stored pending login. A code, when given, must be that login's device code or the user code shown to the
|
|
927
|
+
* person: a code from anywhere else (a prompt injection carrying a login someone else approved) is never exchanged, so
|
|
928
|
+
* it can't swap the stored session for another account's.
|
|
929
|
+
*/
|
|
930
|
+
async pollLogin(code) {
|
|
931
|
+
const pending = await this.sessionStore.getPendingLogin();
|
|
932
|
+
if (!pending) {
|
|
933
|
+
const session = await this.getValidSession();
|
|
934
|
+
if (session) return connectedResult(session);
|
|
935
|
+
return { status: "failed", connected: false, message: this.messages.noLoginInProgress };
|
|
936
|
+
}
|
|
937
|
+
const given = code?.trim();
|
|
938
|
+
if (given && given !== pending.deviceCode && given.toUpperCase() !== pending.userCode.toUpperCase()) {
|
|
939
|
+
return { status: "failed", connected: false, message: this.messages.wrongCode };
|
|
940
|
+
}
|
|
941
|
+
return this.exchangePendingLogin(pending.deviceCode);
|
|
942
|
+
}
|
|
943
|
+
async whoami() {
|
|
944
|
+
return sanitizeAgentSession(await this.getValidSession());
|
|
945
|
+
}
|
|
946
|
+
async getAccessToken() {
|
|
947
|
+
return (await this.getValidSession())?.accessToken ?? null;
|
|
948
|
+
}
|
|
949
|
+
async getPendingLoginState() {
|
|
950
|
+
return this.sessionStore.getPendingLogin();
|
|
951
|
+
}
|
|
952
|
+
async requireSession() {
|
|
953
|
+
const session = await this.getValidSession();
|
|
954
|
+
if (!session) {
|
|
955
|
+
throw new Error(this.deviceLogin ? "No Helios delegated agent session is connected" : this.messages.reconnect);
|
|
956
|
+
}
|
|
957
|
+
return session;
|
|
958
|
+
}
|
|
959
|
+
loginInput(options) {
|
|
960
|
+
return {
|
|
961
|
+
clientName: options.clientName ?? this.defaultClientName,
|
|
962
|
+
scope: options.scope ?? DEFAULT_SCOPE,
|
|
963
|
+
constraints: options.constraints
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
pendingResult(started) {
|
|
967
|
+
return { ...started, connected: false, message: this.messages.loginPrompt(started) };
|
|
968
|
+
}
|
|
969
|
+
async getValidSession() {
|
|
970
|
+
const session = await this.sessionStore.getSession();
|
|
971
|
+
if (!session) {
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
if (!shouldRefreshAgentSession(session, this.now(), this.refreshWindowMs)) {
|
|
975
|
+
return session;
|
|
976
|
+
}
|
|
977
|
+
return this.refreshStoredSession(session, false);
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Refreshes `seen` (the session the caller read), joining a refresh already in progress. `force` refreshes even a
|
|
981
|
+
* session that isn't near expiry (Helios just answered 401 to it).
|
|
982
|
+
*/
|
|
983
|
+
refreshStoredSession(seen, force) {
|
|
984
|
+
this.refreshInFlight ?? (this.refreshInFlight = this.refreshOnce(seen, force).finally(() => {
|
|
985
|
+
this.refreshInFlight = null;
|
|
986
|
+
}));
|
|
987
|
+
return this.refreshInFlight;
|
|
988
|
+
}
|
|
989
|
+
async refreshOnce(seen, force) {
|
|
990
|
+
const latest = await this.sessionStore.getSession();
|
|
991
|
+
if (!latest) {
|
|
992
|
+
return null;
|
|
993
|
+
}
|
|
994
|
+
const rotatedElsewhere = latest.refreshToken !== seen.refreshToken;
|
|
995
|
+
if ((rotatedElsewhere || !force) && !shouldRefreshAgentSession(latest, this.now(), this.refreshWindowMs)) {
|
|
996
|
+
return latest;
|
|
997
|
+
}
|
|
998
|
+
if (!latest.refreshToken) {
|
|
999
|
+
return null;
|
|
1000
|
+
}
|
|
1001
|
+
try {
|
|
1002
|
+
const refreshed = await this.client.refreshSession(latest.refreshToken);
|
|
1003
|
+
const nextSession = createStoredAgentSession(refreshed, this.now());
|
|
1004
|
+
await this.sessionStore.setSession(nextSession);
|
|
1005
|
+
return nextSession;
|
|
1006
|
+
} catch (error) {
|
|
1007
|
+
const stored = await this.sessionStore.getSession().catch(() => null);
|
|
1008
|
+
if (stored && stored.refreshToken !== latest.refreshToken) {
|
|
1009
|
+
return stored;
|
|
1010
|
+
}
|
|
1011
|
+
if (isHeliosAgentApiError(error) && error.code === "invalid_grant") {
|
|
1012
|
+
await this.clearSessionState();
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
throw error;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
exchangePendingLogin(deviceCode) {
|
|
1019
|
+
if (this.exchangeInFlight?.deviceCode !== deviceCode) {
|
|
1020
|
+
const flight = {
|
|
1021
|
+
deviceCode,
|
|
1022
|
+
promise: this.exchangeOnce(deviceCode).finally(() => {
|
|
1023
|
+
if (this.exchangeInFlight === flight) this.exchangeInFlight = null;
|
|
1024
|
+
})
|
|
1025
|
+
};
|
|
1026
|
+
this.exchangeInFlight = flight;
|
|
1027
|
+
}
|
|
1028
|
+
return this.exchangeInFlight.promise;
|
|
1029
|
+
}
|
|
1030
|
+
async exchangeOnce(deviceCode) {
|
|
1031
|
+
try {
|
|
1032
|
+
const response = await this.client.exchangeDeviceCode(deviceCode);
|
|
1033
|
+
const session = createStoredAgentSession(response, this.now());
|
|
1034
|
+
await this.sessionStore.setSession(session);
|
|
1035
|
+
await this.sessionStore.clearPendingLogin();
|
|
1036
|
+
return connectedResult(session);
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
if (!isHeliosAgentApiError(error)) {
|
|
1039
|
+
return {
|
|
1040
|
+
status: "failed",
|
|
1041
|
+
connected: false,
|
|
1042
|
+
message: error instanceof Error ? error.message : "Unexpected Helios login error"
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
switch (error.code) {
|
|
1046
|
+
case "authorization_pending":
|
|
1047
|
+
return { status: "pending", connected: false, message: error.message };
|
|
1048
|
+
case "access_denied":
|
|
1049
|
+
await this.clearPendingLoginIfMatches(deviceCode);
|
|
1050
|
+
return { status: "rejected", connected: false, message: error.message };
|
|
1051
|
+
case "expired_token":
|
|
1052
|
+
await this.clearPendingLoginIfMatches(deviceCode);
|
|
1053
|
+
return { status: "expired", connected: false, message: error.message };
|
|
1054
|
+
case "invalid_grant":
|
|
1055
|
+
await this.clearPendingLoginIfMatches(deviceCode);
|
|
1056
|
+
return { status: "failed", connected: false, message: this.messages.staleLogin };
|
|
1057
|
+
default:
|
|
1058
|
+
return { status: "failed", connected: false, message: error.message };
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
async getPendingLogin(requestedScope, requestedConstraints) {
|
|
1063
|
+
const pendingLogin = await this.sessionStore.getPendingLogin();
|
|
1064
|
+
if (!pendingLogin) {
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
const expiresAt = new Date(pendingLogin.startedAt).getTime() + pendingLogin.expiresInSeconds * 1e3;
|
|
1068
|
+
if (this.now().getTime() >= expiresAt) {
|
|
1069
|
+
await this.sessionStore.clearPendingLogin();
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
const pendingScope = new Set(pendingLogin.requestedScope);
|
|
1073
|
+
if (!requestedScope.every((scope) => pendingScope.has(scope))) {
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
return Object.keys(requestedConstraints ?? {}).length === 0 ? pendingLogin : null;
|
|
1077
|
+
}
|
|
1078
|
+
async clearPendingLoginIfMatches(deviceCode) {
|
|
1079
|
+
const pendingLogin = await this.sessionStore.getPendingLogin();
|
|
1080
|
+
if (pendingLogin?.deviceCode === deviceCode) {
|
|
1081
|
+
await this.sessionStore.clearPendingLogin();
|
|
481
1082
|
}
|
|
482
1083
|
}
|
|
483
1084
|
};
|
|
484
|
-
function
|
|
485
|
-
|
|
1085
|
+
async function retryAfterUnauthorizedRefresh(options) {
|
|
1086
|
+
const refreshedSession = await options.authService.refreshSessionAfterUnauthorized(options.error);
|
|
1087
|
+
if (!refreshedSession) {
|
|
1088
|
+
return options.authService.invalidateIfUnauthorized(options.error, options.loginOptions);
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
return await options.retry(refreshedSession.accessToken);
|
|
1092
|
+
} catch (retryError) {
|
|
1093
|
+
const relogin = await options.authService.invalidateIfUnauthorized(retryError, options.loginOptions);
|
|
1094
|
+
if (relogin) {
|
|
1095
|
+
return relogin;
|
|
1096
|
+
}
|
|
1097
|
+
throw retryError;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
function connectedResult(session) {
|
|
1101
|
+
return { ...sanitizeAgentSession(session), connected: true, status: "connected" };
|
|
1102
|
+
}
|
|
1103
|
+
function hasScopes(session, requestedScope) {
|
|
1104
|
+
const granted = new Set(session.scope);
|
|
1105
|
+
return requestedScope.every((scope) => granted.has(scope));
|
|
486
1106
|
}
|
|
487
1107
|
var HeliosAgentAuthClient = class {
|
|
488
1108
|
constructor(options) {
|
|
489
1109
|
__publicField2(this, "baseUrl");
|
|
490
1110
|
__publicField2(this, "fetchImpl");
|
|
491
1111
|
__publicField2(this, "headers");
|
|
1112
|
+
__publicField2(this, "startSecret");
|
|
492
1113
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
493
1114
|
this.fetchImpl = options.fetch ?? fetch;
|
|
494
1115
|
this.headers = options.headers ?? {};
|
|
1116
|
+
this.startSecret = options.startSecret || void 0;
|
|
495
1117
|
}
|
|
496
|
-
async startLogin(input) {
|
|
1118
|
+
async startLogin(input, options = {}) {
|
|
497
1119
|
const parsed = agentDeviceStartInputSchema.parse(input);
|
|
498
|
-
|
|
1120
|
+
const headers = this.startSecret ? { [AGENT_START_SECRET_HEADER]: this.startSecret, ...options.clientIp ? { [AGENT_CLIENT_IP_HEADER]: options.clientIp } : {} } : void 0;
|
|
1121
|
+
return this.post("/api/agents/device/start", parsed, agentDeviceStartOutputSchema, headers);
|
|
499
1122
|
}
|
|
500
|
-
async exchangeDeviceCode(deviceCode) {
|
|
501
|
-
const parsed =
|
|
502
|
-
return this.post("/api/agents/device/token",
|
|
1123
|
+
async exchangeDeviceCode(deviceCode, options = {}) {
|
|
1124
|
+
const parsed = agentDeviceTokenInputSchema.parse({ deviceCode, approvalProof: options.approvalProof });
|
|
1125
|
+
return this.post("/api/agents/device/token", parsed, agentTokenResponseSchema);
|
|
503
1126
|
}
|
|
504
1127
|
async refreshSession(refreshToken) {
|
|
505
1128
|
const parsed = agentRefreshInputSchema.parse({ refreshToken });
|
|
506
1129
|
return this.post("/api/agents/auth/refresh", parsed, agentTokenResponseSchema);
|
|
507
1130
|
}
|
|
508
|
-
async post(path3, body, schema) {
|
|
1131
|
+
async post(path3, body, schema, extraHeaders) {
|
|
509
1132
|
let response;
|
|
510
1133
|
try {
|
|
511
1134
|
response = await this.fetchImpl(`${this.baseUrl}${path3}`, {
|
|
@@ -513,7 +1136,8 @@ var HeliosAgentAuthClient = class {
|
|
|
513
1136
|
headers: {
|
|
514
1137
|
Accept: "application/json",
|
|
515
1138
|
"Content-Type": "application/json",
|
|
516
|
-
...this.headers
|
|
1139
|
+
...this.headers,
|
|
1140
|
+
...extraHeaders
|
|
517
1141
|
},
|
|
518
1142
|
body: JSON.stringify(body)
|
|
519
1143
|
});
|
|
@@ -539,12 +1163,12 @@ var HeliosAgentAuthClient = class {
|
|
|
539
1163
|
return schema.parse(payload);
|
|
540
1164
|
}
|
|
541
1165
|
async parseJson(response) {
|
|
542
|
-
const
|
|
543
|
-
if (!
|
|
1166
|
+
const text2 = await response.text();
|
|
1167
|
+
if (!text2) {
|
|
544
1168
|
return null;
|
|
545
1169
|
}
|
|
546
1170
|
try {
|
|
547
|
-
return JSON.parse(
|
|
1171
|
+
return JSON.parse(text2);
|
|
548
1172
|
} catch (error) {
|
|
549
1173
|
throw new HeliosAgentApiError({
|
|
550
1174
|
status: response.status,
|
|
@@ -584,7 +1208,8 @@ var HeliosAgentDataBridgeClient = class {
|
|
|
584
1208
|
}
|
|
585
1209
|
return portfolioSnapshotSchema.parse({
|
|
586
1210
|
...result,
|
|
587
|
-
provider: parsedInput.provider
|
|
1211
|
+
provider: parsedInput.provider,
|
|
1212
|
+
protectiveOrders: readProtectiveOrders(result.protectiveOrders)
|
|
588
1213
|
});
|
|
589
1214
|
}
|
|
590
1215
|
async getLedgerFills(accessToken, input = {}) {
|
|
@@ -612,6 +1237,30 @@ var HeliosAgentDataBridgeClient = class {
|
|
|
612
1237
|
const payload = await this.request(accessToken, "market.getSymbolDetail", parsedInput);
|
|
613
1238
|
return marketSymbolDetailSchema.parse(this.unwrapResult(payload));
|
|
614
1239
|
}
|
|
1240
|
+
/** The lean market table: price, two returns, volume, funding, open interest, setup label and the smart money lean. */
|
|
1241
|
+
async getAgentMarket(accessToken, input = {}) {
|
|
1242
|
+
const payload = await this.request(accessToken, "market.getAgentTable", agentMarketInputSchema.parse(input));
|
|
1243
|
+
return agentMarketSchema.parse(this.unwrapResult(payload));
|
|
1244
|
+
}
|
|
1245
|
+
async getMarketCandles(accessToken, input) {
|
|
1246
|
+
const payload = await this.request(accessToken, "market.getCandles", marketCandlesInputSchema.parse(input));
|
|
1247
|
+
return marketCandlesSchema.parse(this.unwrapResult(payload));
|
|
1248
|
+
}
|
|
1249
|
+
/** The research wallets' smart money board (conviction, discount, flows or pile-ins). */
|
|
1250
|
+
async getSmartMoneyBoard(accessToken, input = {}) {
|
|
1251
|
+
const payload = await this.request(accessToken, "smartMoney.board", smartMoneyBoardInputSchema.parse(input));
|
|
1252
|
+
return smartMoneyBoardSchema.parse(this.unwrapResult(payload));
|
|
1253
|
+
}
|
|
1254
|
+
/** Their trades by nickname: the latest, or only what came after an earlier call's `next`. */
|
|
1255
|
+
async getSmartMoneyMoves(accessToken, input = {}) {
|
|
1256
|
+
const payload = await this.request(accessToken, "smartMoney.moves", smartMoneyMovesInputSchema.parse(input));
|
|
1257
|
+
return smartMoneyMovesSchema.parse(this.unwrapResult(payload));
|
|
1258
|
+
}
|
|
1259
|
+
/** One coin: their book, its market row and their latest trades on it. */
|
|
1260
|
+
async getSmartMoneyCoin(accessToken, coin2) {
|
|
1261
|
+
const payload = await this.request(accessToken, "smartMoney.coin", { coin: coin2 });
|
|
1262
|
+
return smartMoneyCoinSchema.parse(this.unwrapResult(payload));
|
|
1263
|
+
}
|
|
615
1264
|
async submitTradeIntent(accessToken, input) {
|
|
616
1265
|
const parsedInput = submitTradeIntentInputSchema.parse(input);
|
|
617
1266
|
const payload = await this.request(accessToken, "trading.submitIntent", parsedInput, "POST");
|
|
@@ -637,22 +1286,27 @@ var HeliosAgentDataBridgeClient = class {
|
|
|
637
1286
|
}
|
|
638
1287
|
async closePosition(accessToken, input) {
|
|
639
1288
|
const parsedInput = closePositionInputSchema.parse(input);
|
|
640
|
-
|
|
1289
|
+
const partial = parsedInput.fraction !== void 0 && parsedInput.fraction < 1;
|
|
1290
|
+
const payload = await this.request(
|
|
641
1291
|
accessToken,
|
|
642
1292
|
"positions.close",
|
|
643
1293
|
{
|
|
644
1294
|
...parsedInput.provider !== void 0 ? { provider: parsedInput.provider } : {},
|
|
645
1295
|
symbol: parsedInput.symbol,
|
|
646
|
-
mode: "FULL",
|
|
1296
|
+
mode: partial ? "PARTIAL" : "FULL",
|
|
1297
|
+
...partial ? { fraction: parsedInput.fraction } : {},
|
|
647
1298
|
...parsedInput.maxSlippageBps !== void 0 ? { maxSlippageBps: parsedInput.maxSlippageBps } : {}
|
|
648
1299
|
},
|
|
649
1300
|
"POST"
|
|
650
1301
|
);
|
|
1302
|
+
const outcome = this.tryUnwrapResult(payload)?.outcome;
|
|
651
1303
|
return closePositionResultSchema.parse({
|
|
652
1304
|
status: "submitted",
|
|
1305
|
+
...outcome === "closed" || outcome === "no_position" ? { outcome } : {},
|
|
653
1306
|
provider: parsedInput.provider,
|
|
654
1307
|
symbol: parsedInput.symbol,
|
|
655
|
-
mode: "FULL",
|
|
1308
|
+
mode: partial ? "PARTIAL" : "FULL",
|
|
1309
|
+
...partial ? { fraction: parsedInput.fraction } : {},
|
|
656
1310
|
maxSlippageBps: parsedInput.maxSlippageBps
|
|
657
1311
|
});
|
|
658
1312
|
}
|
|
@@ -693,6 +1347,62 @@ var HeliosAgentDataBridgeClient = class {
|
|
|
693
1347
|
oid: parsedInput.oid
|
|
694
1348
|
});
|
|
695
1349
|
}
|
|
1350
|
+
async modifyOrder(accessToken, input) {
|
|
1351
|
+
const parsedInput = modifyOrderInputSchema.parse(input);
|
|
1352
|
+
await this.request(accessToken, "positions.modifyOrder", parsedInput, "POST");
|
|
1353
|
+
return modifyOrderResultSchema.parse({ status: "modified", ...parsedInput });
|
|
1354
|
+
}
|
|
1355
|
+
// ---------------------------------------------------------------- paper (AGENT_PAPER_ACCOUNT unless told otherwise)
|
|
1356
|
+
async getPaperPortfolio(accessToken, accountId = AGENT_PAPER_ACCOUNT) {
|
|
1357
|
+
const payload = await this.request(accessToken, "paper.getPortfolio", { accountId, fills: 0 });
|
|
1358
|
+
return paperPortfolioSchema.parse(this.unwrapResult(payload));
|
|
1359
|
+
}
|
|
1360
|
+
/** A paper market order; answers with its fills (none when nothing filled). */
|
|
1361
|
+
async submitPaperIntent(accessToken, input, accountId = AGENT_PAPER_ACCOUNT) {
|
|
1362
|
+
const parsedInput = submitTradeIntentInputSchema.parse(input);
|
|
1363
|
+
const payload = await this.request(accessToken, "paper.submitIntent", { ...parsedInput, accountId }, "POST");
|
|
1364
|
+
return paperFillSchema.array().parse(this.tryUnwrapResult(payload) ?? []);
|
|
1365
|
+
}
|
|
1366
|
+
/** Closes all or part of a paper position; no fills when there was none. */
|
|
1367
|
+
async closePaperPosition(accessToken, input, accountId = AGENT_PAPER_ACCOUNT) {
|
|
1368
|
+
const parsedInput = closePositionInputSchema.parse(input);
|
|
1369
|
+
const partial = parsedInput.fraction !== void 0 && parsedInput.fraction < 1;
|
|
1370
|
+
const payload = await this.request(
|
|
1371
|
+
accessToken,
|
|
1372
|
+
"paper.close",
|
|
1373
|
+
{ symbol: parsedInput.symbol, mode: partial ? "PARTIAL" : "FULL", ...partial ? { fraction: parsedInput.fraction } : {}, accountId },
|
|
1374
|
+
"POST"
|
|
1375
|
+
);
|
|
1376
|
+
return paperFillSchema.array().parse(this.tryUnwrapResult(payload) ?? []);
|
|
1377
|
+
}
|
|
1378
|
+
async setPaperTpSl(accessToken, input, accountId = AGENT_PAPER_ACCOUNT) {
|
|
1379
|
+
const parsedInput = setTpSlInputSchema.parse(input);
|
|
1380
|
+
await this.request(accessToken, "paper.setTpSl", { ...parsedInput, accountId }, "POST");
|
|
1381
|
+
return setTpSlResultSchema.parse({
|
|
1382
|
+
status: "submitted",
|
|
1383
|
+
provider: parsedInput.provider,
|
|
1384
|
+
symbol: parsedInput.symbol,
|
|
1385
|
+
applyTo: parsedInput.applyTo,
|
|
1386
|
+
quantity: parsedInput.quantity,
|
|
1387
|
+
parentClientOrderId: parsedInput.parentClientOrderId,
|
|
1388
|
+
hasTakeProfit: Boolean(parsedInput.takeProfit),
|
|
1389
|
+
hasStopLoss: Boolean(parsedInput.stopLoss)
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
async deletePaperTpSl(accessToken, input, accountId = AGENT_PAPER_ACCOUNT) {
|
|
1393
|
+
const parsedInput = deleteTpSlInputSchema.parse(input);
|
|
1394
|
+
await this.request(accessToken, "paper.deleteTpSl", { ...parsedInput, accountId }, "POST");
|
|
1395
|
+
return deleteTpSlResultSchema.parse({ status: "deleted", provider: parsedInput.provider, symbol: parsedInput.symbol, orderId: parsedInput.orderId });
|
|
1396
|
+
}
|
|
1397
|
+
async getPaperLedgerFills(accessToken, input = {}, accountId = AGENT_PAPER_ACCOUNT) {
|
|
1398
|
+
const parsedInput = ledgerFillsInputSchema.parse(input);
|
|
1399
|
+
const payload = await this.request(accessToken, "paper.getLedgerFills", { ...parsedInput, accountId });
|
|
1400
|
+
return ledgerFillsPageSchema.parse(this.unwrapResult(payload));
|
|
1401
|
+
}
|
|
1402
|
+
/** Starts the paper account over with `startingBalance` USD (data-bridge's default when omitted). */
|
|
1403
|
+
async resetPaperAccount(accessToken, startingBalance, accountId = AGENT_PAPER_ACCOUNT) {
|
|
1404
|
+
await this.request(accessToken, "paper.reset", { accountId, ...startingBalance ? { startingBalance } : {} }, "POST");
|
|
1405
|
+
}
|
|
696
1406
|
async request(accessToken, procedure, input, method = "GET") {
|
|
697
1407
|
let response;
|
|
698
1408
|
try {
|
|
@@ -732,12 +1442,12 @@ var HeliosAgentDataBridgeClient = class {
|
|
|
732
1442
|
return url.toString();
|
|
733
1443
|
}
|
|
734
1444
|
async parseJson(response) {
|
|
735
|
-
const
|
|
736
|
-
if (!
|
|
1445
|
+
const text2 = await response.text();
|
|
1446
|
+
if (!text2) {
|
|
737
1447
|
return null;
|
|
738
1448
|
}
|
|
739
1449
|
try {
|
|
740
|
-
return JSON.parse(
|
|
1450
|
+
return JSON.parse(text2);
|
|
741
1451
|
} catch (error) {
|
|
742
1452
|
throw new HeliosAgentApiError({
|
|
743
1453
|
status: response.status,
|
|
@@ -747,6 +1457,14 @@ var HeliosAgentDataBridgeClient = class {
|
|
|
747
1457
|
});
|
|
748
1458
|
}
|
|
749
1459
|
}
|
|
1460
|
+
/** The result of a mutation older data-bridges answer with nothing; null when there is none. */
|
|
1461
|
+
tryUnwrapResult(payload) {
|
|
1462
|
+
try {
|
|
1463
|
+
return this.unwrapResult(payload) ?? null;
|
|
1464
|
+
} catch {
|
|
1465
|
+
return null;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
750
1468
|
unwrapResult(payload) {
|
|
751
1469
|
if (!payload || typeof payload !== "object") {
|
|
752
1470
|
throw new HeliosAgentApiError({
|
|
@@ -760,373 +1478,78 @@ var HeliosAgentDataBridgeClient = class {
|
|
|
760
1478
|
if (!result) {
|
|
761
1479
|
throw new HeliosAgentApiError({
|
|
762
1480
|
status: 200,
|
|
763
|
-
code: "invalid_response",
|
|
764
|
-
message: "Helios data bridge response is missing a result payload",
|
|
765
|
-
payload
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
const data = result.data;
|
|
769
|
-
if (data && typeof data === "object" && "json" in data) {
|
|
770
|
-
return data.json;
|
|
771
|
-
}
|
|
772
|
-
return data;
|
|
773
|
-
}
|
|
774
|
-
extractErrorCode(payload) {
|
|
775
|
-
if (payload && typeof payload === "object") {
|
|
776
|
-
const errorShape = this.unwrapTrpcError(payload);
|
|
777
|
-
if (errorShape?.data?.code) {
|
|
778
|
-
return errorShape.data.code;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
return "request_failed";
|
|
782
|
-
}
|
|
783
|
-
extractErrorMessage(payload, status) {
|
|
784
|
-
if (payload && typeof payload === "object") {
|
|
785
|
-
const errorShape = this.unwrapTrpcError(payload);
|
|
786
|
-
if (errorShape?.message) {
|
|
787
|
-
return errorShape.message;
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
return `Request failed with status ${status}`;
|
|
791
|
-
}
|
|
792
|
-
/**
|
|
793
|
-
* Unwrap a tRPC error envelope, handling the superjson `{ json: ... }` wrapper
|
|
794
|
-
* that the data-bridge applies via its transformer configuration.
|
|
795
|
-
*/
|
|
796
|
-
unwrapTrpcError(payload) {
|
|
797
|
-
const envelope = payload.error;
|
|
798
|
-
if (!envelope || typeof envelope !== "object") {
|
|
799
|
-
return void 0;
|
|
800
|
-
}
|
|
801
|
-
if ("json" in envelope) {
|
|
802
|
-
const inner = envelope.json;
|
|
803
|
-
if (inner && typeof inner === "object") {
|
|
804
|
-
return inner;
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
return envelope;
|
|
808
|
-
}
|
|
809
|
-
};
|
|
810
|
-
var DEFAULT_AGENT_REFRESH_WINDOW_MS = 60 * 1e3;
|
|
811
|
-
var agentConnectionStatusSchema = import_zod3.z.enum([
|
|
812
|
-
"disconnected",
|
|
813
|
-
"pending",
|
|
814
|
-
"connected",
|
|
815
|
-
"rejected",
|
|
816
|
-
"expired",
|
|
817
|
-
"failed"
|
|
818
|
-
]);
|
|
819
|
-
var storedAgentSessionSchema = import_zod3.z.object({
|
|
820
|
-
accessToken: import_zod3.z.string(),
|
|
821
|
-
refreshToken: import_zod3.z.string(),
|
|
822
|
-
tokenType: agentTokenResponseSchema.shape.tokenType,
|
|
823
|
-
ownerUserId: import_zod3.z.string(),
|
|
824
|
-
agentGrantId: import_zod3.z.string(),
|
|
825
|
-
scope: import_zod3.z.array(agentScopeSchema).min(1),
|
|
826
|
-
accessTokenExpiresAt: import_zod3.z.string().datetime(),
|
|
827
|
-
createdAt: import_zod3.z.string().datetime(),
|
|
828
|
-
lastRefreshedAt: import_zod3.z.string().datetime()
|
|
829
|
-
});
|
|
830
|
-
var agentSessionViewSchema = import_zod3.z.object({
|
|
831
|
-
connected: import_zod3.z.boolean(),
|
|
832
|
-
status: agentConnectionStatusSchema,
|
|
833
|
-
ownerUserId: import_zod3.z.string().optional(),
|
|
834
|
-
agentGrantId: import_zod3.z.string().optional(),
|
|
835
|
-
scope: import_zod3.z.array(agentScopeSchema).optional(),
|
|
836
|
-
accessTokenExpiresAt: import_zod3.z.string().datetime().optional(),
|
|
837
|
-
lastRefreshedAt: import_zod3.z.string().datetime().optional()
|
|
838
|
-
});
|
|
839
|
-
function createStoredAgentSession(tokenResponse, now = /* @__PURE__ */ new Date()) {
|
|
840
|
-
const createdAt = now.toISOString();
|
|
841
|
-
return {
|
|
842
|
-
accessToken: tokenResponse.accessToken,
|
|
843
|
-
refreshToken: tokenResponse.refreshToken,
|
|
844
|
-
tokenType: tokenResponse.tokenType,
|
|
845
|
-
ownerUserId: tokenResponse.ownerUserId,
|
|
846
|
-
agentGrantId: tokenResponse.agentGrantId,
|
|
847
|
-
scope: tokenResponse.scope,
|
|
848
|
-
accessTokenExpiresAt: new Date(now.getTime() + tokenResponse.expiresInSeconds * 1e3).toISOString(),
|
|
849
|
-
createdAt,
|
|
850
|
-
lastRefreshedAt: createdAt
|
|
851
|
-
};
|
|
852
|
-
}
|
|
853
|
-
function sanitizeAgentSession(session, status = session ? "connected" : "disconnected") {
|
|
854
|
-
if (!session) {
|
|
855
|
-
return {
|
|
856
|
-
connected: false,
|
|
857
|
-
status: status === "connected" ? "disconnected" : status
|
|
858
|
-
};
|
|
859
|
-
}
|
|
860
|
-
return {
|
|
861
|
-
connected: true,
|
|
862
|
-
status,
|
|
863
|
-
ownerUserId: session.ownerUserId,
|
|
864
|
-
agentGrantId: session.agentGrantId,
|
|
865
|
-
scope: session.scope,
|
|
866
|
-
accessTokenExpiresAt: session.accessTokenExpiresAt,
|
|
867
|
-
lastRefreshedAt: session.lastRefreshedAt
|
|
868
|
-
};
|
|
869
|
-
}
|
|
870
|
-
function shouldRefreshAgentSession(session, now = /* @__PURE__ */ new Date(), refreshWindowMs = DEFAULT_AGENT_REFRESH_WINDOW_MS) {
|
|
871
|
-
return new Date(session.accessTokenExpiresAt).getTime() - now.getTime() <= refreshWindowMs;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
// src/auth-service.ts
|
|
875
|
-
var HOSTED_RECONNECT_MESSAGE = "Helios no longer accepts this connection (it expired or was revoked). Reconnect Helios in your AI app to continue.";
|
|
876
|
-
var HeliosAgentMcpAuthService = class {
|
|
877
|
-
constructor(options) {
|
|
878
|
-
__publicField(this, "client");
|
|
879
|
-
__publicField(this, "sessionStore");
|
|
880
|
-
__publicField(this, "refreshWindowMs");
|
|
881
|
-
__publicField(this, "now");
|
|
882
|
-
__publicField(this, "deviceLogin");
|
|
883
|
-
this.client = options.client;
|
|
884
|
-
this.sessionStore = options.sessionStore;
|
|
885
|
-
this.refreshWindowMs = options.refreshWindowMs ?? 60 * 1e3;
|
|
886
|
-
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
887
|
-
this.deviceLogin = options.deviceLogin ?? true;
|
|
888
|
-
}
|
|
889
|
-
async startLogin(input) {
|
|
890
|
-
const response = await this.startDeviceLogin(input);
|
|
891
|
-
const result = this.buildStartLoginResult(response, input.scope);
|
|
892
|
-
await this.sessionStore.setPendingLogin(result);
|
|
893
|
-
return result;
|
|
894
|
-
}
|
|
895
|
-
async resetSessionAndStartLogin(input) {
|
|
896
|
-
await this.sessionStore.clearSession();
|
|
897
|
-
await this.sessionStore.clearPendingLogin();
|
|
898
|
-
const response = await this.startDeviceLogin(input);
|
|
899
|
-
const result = this.buildStartLoginResult(response, input.scope);
|
|
900
|
-
await this.sessionStore.setPendingLogin(result);
|
|
901
|
-
return result;
|
|
902
|
-
}
|
|
903
|
-
async startDeviceLogin(input) {
|
|
904
|
-
if (!this.deviceLogin) {
|
|
905
|
-
throw new Error(HOSTED_RECONNECT_MESSAGE);
|
|
906
|
-
}
|
|
907
|
-
return this.client.startLogin(input);
|
|
908
|
-
}
|
|
909
|
-
buildStartLoginResult(response, requestedScope) {
|
|
910
|
-
return {
|
|
911
|
-
status: "pending",
|
|
912
|
-
...response,
|
|
913
|
-
requestedScope,
|
|
914
|
-
startedAt: this.now().toISOString()
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
async startRelogin(options = {}) {
|
|
918
|
-
const requestedScope = options.scope ?? [AGENT_SCOPES.ACCOUNT_READ, AGENT_SCOPES.MARKET_READ];
|
|
919
|
-
const started = await this.resetSessionAndStartLogin({
|
|
920
|
-
clientName: options.clientName ?? "Helios MCP",
|
|
921
|
-
scope: requestedScope,
|
|
922
|
-
constraints: options.constraints
|
|
923
|
-
});
|
|
924
|
-
return {
|
|
925
|
-
...started,
|
|
926
|
-
connected: false,
|
|
927
|
-
message: formatHeliosLoginPrompt(started)
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
async clearSessionState() {
|
|
931
|
-
await this.sessionStore.clearSession();
|
|
932
|
-
await this.sessionStore.clearPendingLogin();
|
|
933
|
-
}
|
|
934
|
-
async invalidateIfUnauthorized(error, options = {}) {
|
|
935
|
-
if (!isHeliosAgentApiError(error) || error.status !== 401) {
|
|
936
|
-
return null;
|
|
937
|
-
}
|
|
938
|
-
return this.startRelogin(options);
|
|
939
|
-
}
|
|
940
|
-
async refreshSessionAfterUnauthorized(error) {
|
|
941
|
-
if (!isHeliosAgentApiError(error) || error.status !== 401) {
|
|
942
|
-
return null;
|
|
943
|
-
}
|
|
944
|
-
const session = await this.sessionStore.getSession();
|
|
945
|
-
if (!session) {
|
|
946
|
-
return null;
|
|
947
|
-
}
|
|
948
|
-
return this.refreshStoredSession(session);
|
|
949
|
-
}
|
|
950
|
-
async ensureLogin(options = {}) {
|
|
951
|
-
const requestedScope = options.scope ?? [AGENT_SCOPES.ACCOUNT_READ, AGENT_SCOPES.MARKET_READ];
|
|
952
|
-
const requestedConstraints = options.constraints ?? {};
|
|
953
|
-
const session = await this.getValidSession();
|
|
954
|
-
if (session) {
|
|
955
|
-
return {
|
|
956
|
-
...sanitizeAgentSession(session),
|
|
957
|
-
connected: true,
|
|
958
|
-
status: "connected"
|
|
959
|
-
};
|
|
960
|
-
}
|
|
961
|
-
const pendingLogin = await this.getPendingLogin({
|
|
962
|
-
requestedScope,
|
|
963
|
-
requestedConstraints
|
|
964
|
-
});
|
|
965
|
-
if (pendingLogin) {
|
|
966
|
-
const polled = await this.pollLogin(pendingLogin.deviceCode).catch(() => null);
|
|
967
|
-
if (polled?.status === "connected") return polled;
|
|
968
|
-
if (polled?.status !== "rejected" && polled?.status !== "expired") {
|
|
969
|
-
return {
|
|
970
|
-
...pendingLogin,
|
|
971
|
-
connected: false,
|
|
972
|
-
message: formatHeliosLoginPrompt(pendingLogin)
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
const started = await this.startLogin({
|
|
977
|
-
clientName: options.clientName ?? "Helios MCP",
|
|
978
|
-
scope: requestedScope,
|
|
979
|
-
constraints: options.constraints
|
|
980
|
-
});
|
|
981
|
-
return {
|
|
982
|
-
...started,
|
|
983
|
-
connected: false,
|
|
984
|
-
message: formatHeliosLoginPrompt(started)
|
|
985
|
-
};
|
|
986
|
-
}
|
|
987
|
-
/** Polls the given login, or the stored pending one when no device code is given. */
|
|
988
|
-
async pollLogin(deviceCode) {
|
|
989
|
-
if (!deviceCode) {
|
|
990
|
-
const pending = await this.sessionStore.getPendingLogin();
|
|
991
|
-
if (!pending) {
|
|
992
|
-
const session = await this.getValidSession();
|
|
993
|
-
if (session) return { ...sanitizeAgentSession(session), connected: true, status: "connected" };
|
|
994
|
-
return {
|
|
995
|
-
status: "failed",
|
|
996
|
-
connected: false,
|
|
997
|
-
message: "No Helios login is in progress. Call helios_auth_ensure_login to start one."
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
deviceCode = pending.deviceCode;
|
|
1001
|
-
} else {
|
|
1002
|
-
const pending = await this.sessionStore.getPendingLogin();
|
|
1003
|
-
if (pending && pending.userCode.toUpperCase() === deviceCode.trim().toUpperCase()) deviceCode = pending.deviceCode;
|
|
1004
|
-
}
|
|
1005
|
-
try {
|
|
1006
|
-
const response = await this.client.exchangeDeviceCode(deviceCode);
|
|
1007
|
-
const session = createStoredAgentSession(response, this.now());
|
|
1008
|
-
await this.sessionStore.setSession(session);
|
|
1009
|
-
await this.sessionStore.clearPendingLogin();
|
|
1010
|
-
return {
|
|
1011
|
-
...sanitizeAgentSession(session),
|
|
1012
|
-
connected: true,
|
|
1013
|
-
status: "connected"
|
|
1014
|
-
};
|
|
1015
|
-
} catch (error) {
|
|
1016
|
-
if (isHeliosAgentApiError(error)) {
|
|
1017
|
-
if (error.code === "authorization_pending") {
|
|
1018
|
-
return {
|
|
1019
|
-
status: "pending",
|
|
1020
|
-
connected: false,
|
|
1021
|
-
message: error.message
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
1024
|
-
if (error.code === "access_denied") {
|
|
1025
|
-
await this.clearPendingLoginIfMatches(deviceCode);
|
|
1026
|
-
return {
|
|
1027
|
-
status: "rejected",
|
|
1028
|
-
connected: false,
|
|
1029
|
-
message: error.message
|
|
1030
|
-
};
|
|
1031
|
-
}
|
|
1032
|
-
if (error.code === "expired_token") {
|
|
1033
|
-
await this.clearPendingLoginIfMatches(deviceCode);
|
|
1034
|
-
return {
|
|
1035
|
-
status: "expired",
|
|
1036
|
-
connected: false,
|
|
1037
|
-
message: error.message
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
return {
|
|
1041
|
-
status: "failed",
|
|
1042
|
-
connected: false,
|
|
1043
|
-
message: error.message
|
|
1044
|
-
};
|
|
1045
|
-
}
|
|
1046
|
-
return {
|
|
1047
|
-
status: "failed",
|
|
1048
|
-
connected: false,
|
|
1049
|
-
message: error instanceof Error ? error.message : "Unexpected Helios login error"
|
|
1050
|
-
};
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
async whoami() {
|
|
1054
|
-
const session = await this.getValidSession();
|
|
1055
|
-
return sanitizeAgentSession(session);
|
|
1056
|
-
}
|
|
1057
|
-
async getAccessToken() {
|
|
1058
|
-
const session = await this.getValidSession();
|
|
1059
|
-
return session?.accessToken ?? null;
|
|
1060
|
-
}
|
|
1061
|
-
async requireSession() {
|
|
1062
|
-
const session = await this.getValidSession();
|
|
1063
|
-
if (!session) {
|
|
1064
|
-
throw new Error("No Helios delegated agent session is connected");
|
|
1065
|
-
}
|
|
1066
|
-
return session;
|
|
1067
|
-
}
|
|
1068
|
-
async getValidSession() {
|
|
1069
|
-
const session = await this.sessionStore.getSession();
|
|
1070
|
-
if (!session) {
|
|
1071
|
-
return null;
|
|
1481
|
+
code: "invalid_response",
|
|
1482
|
+
message: "Helios data bridge response is missing a result payload",
|
|
1483
|
+
payload
|
|
1484
|
+
});
|
|
1072
1485
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1486
|
+
const data = result.data;
|
|
1487
|
+
if (data && typeof data === "object" && "json" in data) {
|
|
1488
|
+
return data.json;
|
|
1075
1489
|
}
|
|
1076
|
-
return
|
|
1490
|
+
return data;
|
|
1077
1491
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
const
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
return nextSession;
|
|
1084
|
-
} catch (error) {
|
|
1085
|
-
if (isHeliosAgentApiError(error) && error.code === "invalid_grant") {
|
|
1086
|
-
await this.sessionStore.clearSession();
|
|
1087
|
-
await this.sessionStore.clearPendingLogin();
|
|
1088
|
-
return null;
|
|
1492
|
+
extractErrorCode(payload) {
|
|
1493
|
+
if (payload && typeof payload === "object") {
|
|
1494
|
+
const errorShape = this.unwrapTrpcError(payload);
|
|
1495
|
+
if (errorShape?.data?.code) {
|
|
1496
|
+
return errorShape.data.code;
|
|
1089
1497
|
}
|
|
1090
|
-
throw error;
|
|
1091
1498
|
}
|
|
1499
|
+
return "request_failed";
|
|
1092
1500
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
if (this.now().getTime() >= expiresAt) {
|
|
1100
|
-
await this.sessionStore.clearPendingLogin();
|
|
1101
|
-
return null;
|
|
1102
|
-
}
|
|
1103
|
-
if (!isPendingLoginCompatible(pendingLogin, options.requestedScope, options.requestedConstraints)) {
|
|
1104
|
-
return null;
|
|
1501
|
+
extractErrorMessage(payload, status) {
|
|
1502
|
+
if (payload && typeof payload === "object") {
|
|
1503
|
+
const errorShape = this.unwrapTrpcError(payload);
|
|
1504
|
+
if (errorShape?.message) {
|
|
1505
|
+
return errorShape.message;
|
|
1506
|
+
}
|
|
1105
1507
|
}
|
|
1106
|
-
return
|
|
1508
|
+
return `Request failed with status ${status}`;
|
|
1107
1509
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1510
|
+
/**
|
|
1511
|
+
* Unwrap a tRPC error envelope, handling the superjson `{ json: ... }` wrapper
|
|
1512
|
+
* that the data-bridge applies via its transformer configuration.
|
|
1513
|
+
*/
|
|
1514
|
+
unwrapTrpcError(payload) {
|
|
1515
|
+
const envelope = payload.error;
|
|
1516
|
+
if (!envelope || typeof envelope !== "object") {
|
|
1517
|
+
return void 0;
|
|
1112
1518
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
return options.authService.invalidateIfUnauthorized(options.error, options.loginOptions);
|
|
1119
|
-
}
|
|
1120
|
-
try {
|
|
1121
|
-
return await options.retry(refreshedSession.accessToken);
|
|
1122
|
-
} catch (retryError) {
|
|
1123
|
-
const relogin = await options.authService.invalidateIfUnauthorized(retryError, options.loginOptions);
|
|
1124
|
-
if (relogin) {
|
|
1125
|
-
return relogin;
|
|
1519
|
+
if ("json" in envelope) {
|
|
1520
|
+
const inner = envelope.json;
|
|
1521
|
+
if (inner && typeof inner === "object") {
|
|
1522
|
+
return inner;
|
|
1523
|
+
}
|
|
1126
1524
|
}
|
|
1127
|
-
|
|
1525
|
+
return envelope;
|
|
1128
1526
|
}
|
|
1527
|
+
};
|
|
1528
|
+
function readProtectiveOrders(value) {
|
|
1529
|
+
if (!Array.isArray(value)) return [];
|
|
1530
|
+
return value.flatMap((entry) => {
|
|
1531
|
+
const parsed = protectiveOrderSchema.safeParse(entry);
|
|
1532
|
+
return parsed.success ? [parsed.data] : [];
|
|
1533
|
+
});
|
|
1129
1534
|
}
|
|
1535
|
+
|
|
1536
|
+
// src/auth-service.ts
|
|
1537
|
+
var HOSTED_RECONNECT_MESSAGE = "Helios no longer accepts this connection (it expired or was revoked). Reconnect Helios in your AI app to continue.";
|
|
1538
|
+
var HeliosAgentMcpAuthService = class extends AgentAuthService {
|
|
1539
|
+
constructor(options) {
|
|
1540
|
+
super({
|
|
1541
|
+
...options,
|
|
1542
|
+
defaultClientName: "Helios MCP",
|
|
1543
|
+
messages: {
|
|
1544
|
+
loginPrompt: formatHeliosLoginPrompt,
|
|
1545
|
+
noLoginInProgress: "No Helios login is in progress. Call helios_auth_ensure_login to start one.",
|
|
1546
|
+
wrongCode: "That code is not the Helios login in progress. Call helios_auth_poll_login without a code to check it.",
|
|
1547
|
+
staleLogin: "Helios no longer knows this login request. Call helios_auth_ensure_login to start a fresh one.",
|
|
1548
|
+
reconnect: HOSTED_RECONNECT_MESSAGE
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1130
1553
|
function formatHeliosLoginPrompt(result) {
|
|
1131
1554
|
return `Log in to Helios by opening this URL in your browser:
|
|
1132
1555
|
|
|
@@ -1138,14 +1561,6 @@ Requested scopes: ${result.requestedScope.join(", ")}
|
|
|
1138
1561
|
|
|
1139
1562
|
After approving access, call helios_auth_ensure_login (or helios_auth_poll_login) again to finish connecting.`;
|
|
1140
1563
|
}
|
|
1141
|
-
function isPendingLoginCompatible(pendingLogin, requestedScope, requestedConstraints) {
|
|
1142
|
-
const pendingScope = new Set(pendingLogin.requestedScope);
|
|
1143
|
-
const scopeCompatible = requestedScope.every((scope) => pendingScope.has(scope));
|
|
1144
|
-
if (!scopeCompatible) {
|
|
1145
|
-
return false;
|
|
1146
|
-
}
|
|
1147
|
-
return Object.keys(requestedConstraints ?? {}).length === 0;
|
|
1148
|
-
}
|
|
1149
1564
|
|
|
1150
1565
|
// src/account-service.ts
|
|
1151
1566
|
var HeliosAgentMcpAccountService = class {
|
|
@@ -1260,6 +1675,9 @@ var HeliosAgentMcpDocsService = class {
|
|
|
1260
1675
|
}
|
|
1261
1676
|
async getDocument(input) {
|
|
1262
1677
|
const documentPath = resolveDocumentPath(this.docsRoot, input.path);
|
|
1678
|
+
if (!(0, import_node_fs.existsSync)(documentPath) || !(0, import_node_fs.statSync)(documentPath).isFile()) {
|
|
1679
|
+
throw new Error(`No document "${input.path}". helios_docs_get_index lists the documents.`);
|
|
1680
|
+
}
|
|
1263
1681
|
const body = (0, import_node_fs.readFileSync)(documentPath, "utf8");
|
|
1264
1682
|
const stats = (0, import_node_fs.statSync)(documentPath);
|
|
1265
1683
|
const relativePath = toPosixPath(import_node_path.default.relative(this.docsRoot, documentPath));
|
|
@@ -1340,6 +1758,41 @@ function toPosixPath(value) {
|
|
|
1340
1758
|
return value.split(import_node_path.default.sep).join("/");
|
|
1341
1759
|
}
|
|
1342
1760
|
|
|
1761
|
+
// src/endpoints.ts
|
|
1762
|
+
var DEFAULT_HELIOS_BASE_URL = "https://helios.pgn.io";
|
|
1763
|
+
var DEFAULT_HELIOS_SESSION_ISSUER = {
|
|
1764
|
+
identityServiceUrl: DEFAULT_HELIOS_BASE_URL,
|
|
1765
|
+
dataBridgeUrl: DEFAULT_HELIOS_BASE_URL
|
|
1766
|
+
};
|
|
1767
|
+
var LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
1768
|
+
function assertHeliosBaseUrl(name, value) {
|
|
1769
|
+
let url;
|
|
1770
|
+
try {
|
|
1771
|
+
url = new URL(value);
|
|
1772
|
+
} catch {
|
|
1773
|
+
throw new Error(`${name} must be a URL, got ${JSON.stringify(value)}`);
|
|
1774
|
+
}
|
|
1775
|
+
if (url.protocol === "https:") {
|
|
1776
|
+
return value;
|
|
1777
|
+
}
|
|
1778
|
+
if (url.protocol === "http:" && LOOPBACK_HOSTNAMES.has(url.hostname)) {
|
|
1779
|
+
return value;
|
|
1780
|
+
}
|
|
1781
|
+
throw new Error(`${name} must use https (plain http only for localhost, 127.0.0.1 or [::1]), got ${url.origin}`);
|
|
1782
|
+
}
|
|
1783
|
+
function isSameHeliosIssuer(stored, configured) {
|
|
1784
|
+
const issuer = stored ?? DEFAULT_HELIOS_SESSION_ISSUER;
|
|
1785
|
+
return normalizeBaseUrl(issuer.identityServiceUrl) === normalizeBaseUrl(configured.identityServiceUrl) && normalizeBaseUrl(issuer.dataBridgeUrl) === normalizeBaseUrl(configured.dataBridgeUrl);
|
|
1786
|
+
}
|
|
1787
|
+
function normalizeBaseUrl(value) {
|
|
1788
|
+
try {
|
|
1789
|
+
const url = new URL(value);
|
|
1790
|
+
return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
|
|
1791
|
+
} catch {
|
|
1792
|
+
return value.replace(/\/+$/, "");
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1343
1796
|
// src/ledger-service.ts
|
|
1344
1797
|
var HeliosAgentMcpLedgerService = class {
|
|
1345
1798
|
constructor(options) {
|
|
@@ -1685,149 +2138,150 @@ var HeliosAgentMcpProviderService = class {
|
|
|
1685
2138
|
};
|
|
1686
2139
|
|
|
1687
2140
|
// src/session-store.ts
|
|
2141
|
+
var import_node_crypto = require("crypto");
|
|
2142
|
+
var import_node_fs2 = require("fs");
|
|
1688
2143
|
var import_promises = require("fs/promises");
|
|
1689
2144
|
var import_node_os = require("os");
|
|
1690
2145
|
var import_node_path2 = __toESM(require("path"));
|
|
1691
|
-
var
|
|
2146
|
+
var import_zod5 = require("zod");
|
|
1692
2147
|
function getDefaultHeliosAgentMcpSessionPath() {
|
|
1693
2148
|
return import_node_path2.default.join((0, import_node_os.homedir)(), ".helios-agent-mcp", "session.json");
|
|
1694
2149
|
}
|
|
1695
|
-
var
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
verificationUriComplete: import_zod4.z.string().url(),
|
|
1701
|
-
expiresInSeconds: import_zod4.z.number().int().positive(),
|
|
1702
|
-
pollIntervalSeconds: import_zod4.z.number().int().positive(),
|
|
1703
|
-
requestedScope: import_zod4.z.array(agentScopeSchema).min(1),
|
|
1704
|
-
startedAt: import_zod4.z.string().datetime()
|
|
1705
|
-
});
|
|
1706
|
-
var mcpSessionStateSchema = import_zod4.z.object({
|
|
2150
|
+
var pendingAgentLoginSchema2 = pendingAgentLoginSchema.extend({
|
|
2151
|
+
/** The hosts the login was started with (FileAgentSessionStoreOptions.issuer); its device code is only polled there. */
|
|
2152
|
+
issuer: agentSessionIssuerSchema.nullish()
|
|
2153
|
+
});
|
|
2154
|
+
var mcpSessionStateSchema = import_zod5.z.object({
|
|
1707
2155
|
session: storedAgentSessionSchema.nullish(),
|
|
1708
|
-
pendingLogin:
|
|
2156
|
+
pendingLogin: pendingAgentLoginSchema2.nullish(),
|
|
1709
2157
|
// A provider pinned before Binance was retired ('binance') reads as unpinned, so the agent must pick again, rather than
|
|
1710
2158
|
// failing the whole file (which would drop the session) or silently switching it to Hyperliquid.
|
|
1711
2159
|
activeProvider: providerSchema.nullish().catch(null)
|
|
1712
2160
|
});
|
|
2161
|
+
var EMPTY_STATE = { session: null, pendingLogin: null, activeProvider: null };
|
|
1713
2162
|
var FileAgentSessionStore = class {
|
|
1714
|
-
constructor(filePath = getDefaultHeliosAgentMcpSessionPath()) {
|
|
1715
|
-
this
|
|
2163
|
+
constructor(filePath = getDefaultHeliosAgentMcpSessionPath(), options = {}) {
|
|
2164
|
+
__publicField(this, "filePath", filePath);
|
|
2165
|
+
__publicField(this, "issuer");
|
|
2166
|
+
this.issuer = options.issuer ?? null;
|
|
1716
2167
|
}
|
|
1717
2168
|
async getSession() {
|
|
1718
|
-
|
|
2169
|
+
const { session } = await this.readState();
|
|
2170
|
+
if (!session || !this.isFromIssuer(session.issuer)) return null;
|
|
2171
|
+
const { issuer: _issuer, ...rest } = session;
|
|
2172
|
+
return rest;
|
|
1719
2173
|
}
|
|
1720
2174
|
async getPendingLogin() {
|
|
1721
|
-
|
|
2175
|
+
const { pendingLogin } = await this.readState();
|
|
2176
|
+
if (!pendingLogin || !this.isFromIssuer(pendingLogin.issuer)) return null;
|
|
2177
|
+
const { issuer: _issuer, ...rest } = pendingLogin;
|
|
2178
|
+
return rest;
|
|
1722
2179
|
}
|
|
1723
2180
|
async getActiveProvider() {
|
|
1724
2181
|
return (await this.readState()).activeProvider;
|
|
1725
2182
|
}
|
|
1726
2183
|
async setSession(session) {
|
|
1727
|
-
|
|
1728
|
-
await this.writeState({
|
|
2184
|
+
await this.update((state) => ({
|
|
1729
2185
|
...state,
|
|
1730
|
-
session
|
|
1731
|
-
});
|
|
2186
|
+
session: this.issuer ? { ...session, issuer: this.issuer } : session
|
|
2187
|
+
}));
|
|
1732
2188
|
}
|
|
1733
2189
|
async setPendingLogin(pendingLogin) {
|
|
1734
|
-
|
|
1735
|
-
await this.writeState({
|
|
2190
|
+
await this.update((state) => ({
|
|
1736
2191
|
...state,
|
|
1737
|
-
pendingLogin
|
|
1738
|
-
});
|
|
2192
|
+
pendingLogin: this.issuer ? { ...pendingLogin, issuer: this.issuer } : pendingLogin
|
|
2193
|
+
}));
|
|
1739
2194
|
}
|
|
1740
2195
|
async setActiveProvider(activeProvider) {
|
|
1741
|
-
|
|
1742
|
-
await this.writeState({
|
|
2196
|
+
await this.update((state) => ({
|
|
1743
2197
|
...state,
|
|
1744
2198
|
activeProvider
|
|
1745
|
-
});
|
|
2199
|
+
}));
|
|
1746
2200
|
}
|
|
1747
2201
|
async clearSession() {
|
|
1748
|
-
|
|
1749
|
-
await this.writeStateOrDelete({
|
|
1750
|
-
...state,
|
|
1751
|
-
session: null
|
|
1752
|
-
});
|
|
2202
|
+
await this.update((state) => ({ ...state, session: null }), { deleteWhenEmpty: true });
|
|
1753
2203
|
}
|
|
1754
2204
|
async clearPendingLogin() {
|
|
1755
|
-
|
|
1756
|
-
await this.writeStateOrDelete({
|
|
1757
|
-
...state,
|
|
1758
|
-
pendingLogin: null
|
|
1759
|
-
});
|
|
2205
|
+
await this.update((state) => ({ ...state, pendingLogin: null }), { deleteWhenEmpty: true });
|
|
1760
2206
|
}
|
|
1761
2207
|
async clearActiveProvider() {
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
2208
|
+
await this.update((state) => ({ ...state, activeProvider: null }), { deleteWhenEmpty: true });
|
|
2209
|
+
}
|
|
2210
|
+
isFromIssuer(issuer) {
|
|
2211
|
+
return !this.issuer || isSameHeliosIssuer(issuer, this.issuer);
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Read-modify-write under the file's lock: tool calls run concurrently and several MCP processes can share the file,
|
|
2215
|
+
* so without it one write could drop another's (a refreshed session, a pending login).
|
|
2216
|
+
*/
|
|
2217
|
+
async update(change, options = {}) {
|
|
2218
|
+
await withSessionFileLock(this.filePath, async () => {
|
|
2219
|
+
const state = change(await this.readState());
|
|
2220
|
+
if (options.deleteWhenEmpty && !state.session && !state.pendingLogin && !state.activeProvider) {
|
|
2221
|
+
await this.deleteFile();
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
await this.writeState(state);
|
|
1766
2225
|
});
|
|
1767
2226
|
}
|
|
1768
2227
|
async readState() {
|
|
2228
|
+
let parsed;
|
|
1769
2229
|
try {
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
if (
|
|
1773
|
-
|
|
1774
|
-
if (envelope.success) {
|
|
1775
|
-
return {
|
|
1776
|
-
session: envelope.data.session ?? null,
|
|
1777
|
-
pendingLogin: envelope.data.pendingLogin ?? null,
|
|
1778
|
-
activeProvider: envelope.data.activeProvider ?? null
|
|
1779
|
-
};
|
|
1780
|
-
}
|
|
2230
|
+
parsed = JSON.parse(await readFileNoFollow(this.filePath));
|
|
2231
|
+
} catch (error) {
|
|
2232
|
+
if (isErrnoException(error, "ENOENT") || isErrnoException(error, "ELOOP") || error instanceof SyntaxError) {
|
|
2233
|
+
return { ...EMPTY_STATE };
|
|
1781
2234
|
}
|
|
1782
|
-
|
|
1783
|
-
|
|
2235
|
+
throw error;
|
|
2236
|
+
}
|
|
2237
|
+
if (parsed && typeof parsed === "object" && ("session" in parsed || "pendingLogin" in parsed || "activeProvider" in parsed)) {
|
|
2238
|
+
const envelope = mcpSessionStateSchema.safeParse(parsed);
|
|
2239
|
+
if (envelope.success) {
|
|
1784
2240
|
return {
|
|
1785
|
-
session:
|
|
1786
|
-
pendingLogin: null,
|
|
1787
|
-
activeProvider: null
|
|
2241
|
+
session: envelope.data.session ?? null,
|
|
2242
|
+
pendingLogin: envelope.data.pendingLogin ?? null,
|
|
2243
|
+
activeProvider: envelope.data.activeProvider ?? null
|
|
1788
2244
|
};
|
|
1789
2245
|
}
|
|
2246
|
+
}
|
|
2247
|
+
const legacySession = storedAgentSessionSchema.safeParse(parsed);
|
|
2248
|
+
if (legacySession.success) {
|
|
1790
2249
|
return {
|
|
1791
|
-
session:
|
|
2250
|
+
session: legacySession.data,
|
|
1792
2251
|
pendingLogin: null,
|
|
1793
2252
|
activeProvider: null
|
|
1794
2253
|
};
|
|
1795
|
-
} catch (error) {
|
|
1796
|
-
if (isMissingFileError(error)) {
|
|
1797
|
-
return {
|
|
1798
|
-
session: null,
|
|
1799
|
-
pendingLogin: null,
|
|
1800
|
-
activeProvider: null
|
|
1801
|
-
};
|
|
1802
|
-
}
|
|
1803
|
-
if (error instanceof Error && error.name === "ZodError") {
|
|
1804
|
-
return {
|
|
1805
|
-
session: null,
|
|
1806
|
-
pendingLogin: null,
|
|
1807
|
-
activeProvider: null
|
|
1808
|
-
};
|
|
1809
|
-
}
|
|
1810
|
-
throw error;
|
|
1811
2254
|
}
|
|
2255
|
+
return { ...EMPTY_STATE };
|
|
1812
2256
|
}
|
|
1813
2257
|
async writeState(state) {
|
|
1814
|
-
|
|
1815
|
-
await (0, import_promises.
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
2258
|
+
const directory = import_node_path2.default.dirname(this.filePath);
|
|
2259
|
+
await (0, import_promises.mkdir)(directory, { recursive: true, mode: 448 });
|
|
2260
|
+
if (import_node_path2.default.resolve(directory) === import_node_path2.default.dirname(getDefaultHeliosAgentMcpSessionPath())) {
|
|
2261
|
+
await (0, import_promises.chmod)(directory, 448).catch(() => void 0);
|
|
2262
|
+
}
|
|
2263
|
+
const tempPath = `${this.filePath}.${process.pid}.${(0, import_node_crypto.randomBytes)(6).toString("hex")}.tmp`;
|
|
2264
|
+
const handle = await (0, import_promises.open)(tempPath, "wx", 384);
|
|
2265
|
+
try {
|
|
2266
|
+
try {
|
|
2267
|
+
await handle.writeFile(`${JSON.stringify(state, null, 2)}
|
|
2268
|
+
`, "utf8");
|
|
2269
|
+
await handle.chmod(384);
|
|
2270
|
+
await handle.sync();
|
|
2271
|
+
} finally {
|
|
2272
|
+
await handle.close();
|
|
2273
|
+
}
|
|
2274
|
+
await (0, import_promises.rename)(tempPath, this.filePath);
|
|
2275
|
+
} catch (error) {
|
|
2276
|
+
await (0, import_promises.rm)(tempPath, { force: true }).catch(() => void 0);
|
|
2277
|
+
throw error;
|
|
1823
2278
|
}
|
|
1824
|
-
await this.writeState(state);
|
|
1825
2279
|
}
|
|
1826
2280
|
async deleteFile() {
|
|
1827
2281
|
try {
|
|
1828
2282
|
await (0, import_promises.rm)(this.filePath, { force: true });
|
|
1829
2283
|
} catch (error) {
|
|
1830
|
-
if (!
|
|
2284
|
+
if (!isErrnoException(error, "ENOENT")) {
|
|
1831
2285
|
throw error;
|
|
1832
2286
|
}
|
|
1833
2287
|
}
|
|
@@ -1835,8 +2289,8 @@ var FileAgentSessionStore = class {
|
|
|
1835
2289
|
};
|
|
1836
2290
|
var MemoryAgentMcpSessionStore = class {
|
|
1837
2291
|
constructor(session = null, activeProvider = "hyperliquid") {
|
|
1838
|
-
this
|
|
1839
|
-
this
|
|
2292
|
+
__publicField(this, "session", session);
|
|
2293
|
+
__publicField(this, "activeProvider", activeProvider);
|
|
1840
2294
|
__publicField(this, "pendingLogin", null);
|
|
1841
2295
|
}
|
|
1842
2296
|
async getSession() {
|
|
@@ -1867,12 +2321,62 @@ var MemoryAgentMcpSessionStore = class {
|
|
|
1867
2321
|
this.pendingLogin = null;
|
|
1868
2322
|
}
|
|
1869
2323
|
};
|
|
1870
|
-
function
|
|
1871
|
-
return error instanceof Error && "code" in error && error.code ===
|
|
2324
|
+
function isErrnoException(error, code) {
|
|
2325
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
2326
|
+
}
|
|
2327
|
+
async function readFileNoFollow(filePath) {
|
|
2328
|
+
const flags = process.platform === "win32" || import_node_fs2.constants.O_NOFOLLOW === void 0 ? "r" : import_node_fs2.constants.O_RDONLY | import_node_fs2.constants.O_NOFOLLOW;
|
|
2329
|
+
const handle = await (0, import_promises.open)(filePath, flags);
|
|
2330
|
+
try {
|
|
2331
|
+
return await handle.readFile("utf8");
|
|
2332
|
+
} finally {
|
|
2333
|
+
await handle.close();
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
var sessionFileQueues = /* @__PURE__ */ new Map();
|
|
2337
|
+
var LOCK_FILE_STALE_MS = 1e4;
|
|
2338
|
+
var LOCK_FILE_WAIT_MS = 2e3;
|
|
2339
|
+
async function withSessionFileLock(filePath, task) {
|
|
2340
|
+
const key = import_node_path2.default.resolve(filePath);
|
|
2341
|
+
const previous = sessionFileQueues.get(key) ?? Promise.resolve();
|
|
2342
|
+
let release;
|
|
2343
|
+
const done = new Promise((resolve) => release = resolve);
|
|
2344
|
+
const queued = previous.then(() => done);
|
|
2345
|
+
sessionFileQueues.set(key, queued);
|
|
2346
|
+
await previous;
|
|
2347
|
+
try {
|
|
2348
|
+
const locked = await acquireLockFile(`${key}.lock`);
|
|
2349
|
+
try {
|
|
2350
|
+
return await task();
|
|
2351
|
+
} finally {
|
|
2352
|
+
if (locked) await (0, import_promises.rm)(`${key}.lock`, { force: true }).catch(() => void 0);
|
|
2353
|
+
}
|
|
2354
|
+
} finally {
|
|
2355
|
+
release();
|
|
2356
|
+
if (sessionFileQueues.get(key) === queued) sessionFileQueues.delete(key);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
async function acquireLockFile(lockPath) {
|
|
2360
|
+
const deadline = Date.now() + LOCK_FILE_WAIT_MS;
|
|
2361
|
+
for (; ; ) {
|
|
2362
|
+
try {
|
|
2363
|
+
await (await (0, import_promises.open)(lockPath, "wx", 384)).close();
|
|
2364
|
+
return true;
|
|
2365
|
+
} catch (error) {
|
|
2366
|
+
if (!isErrnoException(error, "EEXIST")) return false;
|
|
2367
|
+
}
|
|
2368
|
+
const stats = await (0, import_promises.lstat)(lockPath).catch(() => null);
|
|
2369
|
+
if (stats && Date.now() - stats.mtimeMs > LOCK_FILE_STALE_MS) {
|
|
2370
|
+
await (0, import_promises.rm)(lockPath, { force: true }).catch(() => void 0);
|
|
2371
|
+
continue;
|
|
2372
|
+
}
|
|
2373
|
+
if (Date.now() >= deadline) return false;
|
|
2374
|
+
await new Promise((resolve) => setTimeout(resolve, 20 + Math.floor(Math.random() * 30)));
|
|
2375
|
+
}
|
|
1872
2376
|
}
|
|
1873
2377
|
|
|
1874
2378
|
// src/trading-service.ts
|
|
1875
|
-
var
|
|
2379
|
+
var import_node_crypto2 = require("crypto");
|
|
1876
2380
|
var HeliosAgentMcpTradingService = class {
|
|
1877
2381
|
constructor(options) {
|
|
1878
2382
|
__publicField(this, "authService");
|
|
@@ -2110,7 +2614,7 @@ var HeliosAgentMcpTradingService = class {
|
|
|
2110
2614
|
return asPending2(login);
|
|
2111
2615
|
}
|
|
2112
2616
|
const session = await this.authService.requireSession();
|
|
2113
|
-
const idempotencyKey = input.idempotencyKey ?? (0,
|
|
2617
|
+
const idempotencyKey = input.idempotencyKey ?? (0, import_node_crypto2.randomUUID)().replace(/-/g, "");
|
|
2114
2618
|
try {
|
|
2115
2619
|
const provider = await this.providerService.resolveProvider(input.provider);
|
|
2116
2620
|
const submission = await this.dataBridgeClient.submitTradeIntent(session.accessToken, {
|
|
@@ -2159,251 +2663,960 @@ function asPending2(login) {
|
|
|
2159
2663
|
};
|
|
2160
2664
|
}
|
|
2161
2665
|
|
|
2666
|
+
// src/lean/tools.ts
|
|
2667
|
+
var import_node_crypto3 = require("crypto");
|
|
2668
|
+
var import_zod6 = require("zod");
|
|
2669
|
+
|
|
2670
|
+
// src/lean/format.ts
|
|
2671
|
+
var usd = (v, signed = false) => {
|
|
2672
|
+
if (v === null || v === void 0 || !Number.isFinite(v)) return "n/a";
|
|
2673
|
+
const sign = v < 0 ? "-" : signed && v > 0 ? "+" : "";
|
|
2674
|
+
const a = Math.abs(v);
|
|
2675
|
+
const body = a >= 1e9 ? `${trim(a / 1e9, 2)}B` : a >= 1e6 ? `${trim(a / 1e6, 2)}M` : a >= 1e4 ? `${Math.round(a / 1e3)}k` : a >= 1e3 ? `${trim(a / 1e3, 1)}k` : `${Math.round(a)}`;
|
|
2676
|
+
return `${sign}$${body}`;
|
|
2677
|
+
};
|
|
2678
|
+
var trim = (v, places) => String(Number(v.toFixed(places)));
|
|
2679
|
+
var pct = (v, places = 1) => v === null || v === void 0 || !Number.isFinite(v) ? "n/a" : `${v > 0 ? "+" : ""}${v.toFixed(places)}%`;
|
|
2680
|
+
var price = (v) => {
|
|
2681
|
+
if (v === null || v === void 0 || !Number.isFinite(v)) return "n/a";
|
|
2682
|
+
const a = Math.abs(v);
|
|
2683
|
+
if (a >= 1e3) return v.toLocaleString("en-US", { maximumFractionDigits: a >= 1e4 ? 0 : 2 });
|
|
2684
|
+
if (a >= 1) return v.toFixed(a >= 100 ? 2 : a >= 10 ? 2 : 3);
|
|
2685
|
+
return v.toPrecision(4);
|
|
2686
|
+
};
|
|
2687
|
+
var ago = (t, now) => {
|
|
2688
|
+
if (t === null || t === void 0) return "n/a";
|
|
2689
|
+
const s = Math.max(0, Math.round((now - t) / 1e3));
|
|
2690
|
+
if (s < 60) return `${s}s`;
|
|
2691
|
+
if (s < 3600) return `${Math.round(s / 60)}m`;
|
|
2692
|
+
if (s < 172800) return `${Math.round(s / 3600)}h`;
|
|
2693
|
+
return `${Math.round(s / 86400)}d`;
|
|
2694
|
+
};
|
|
2695
|
+
var STYLE = {
|
|
2696
|
+
scalper: "scalper",
|
|
2697
|
+
"intraday trader": "intraday",
|
|
2698
|
+
"swing trader": "swing",
|
|
2699
|
+
"position trader": "position"
|
|
2700
|
+
};
|
|
2701
|
+
var trader = (m) => {
|
|
2702
|
+
const tags = [STYLE[m.style], m.win === null ? null : `${Math.round(m.win)}% win`].filter(Boolean);
|
|
2703
|
+
return `${m.nick} #${m.rank}${tags.length ? ` (${tags.join(", ")})` : ""}`;
|
|
2704
|
+
};
|
|
2705
|
+
var VERB = { open: "opened", add: "added", reduce: "trimmed", close: "closed", flip: "flipped to" };
|
|
2706
|
+
var moveText = (m) => {
|
|
2707
|
+
const pnl = m.pnl === null ? "" : ` ${usd(m.pnl, true)}${m.pnlPct === null ? "" : ` (${pct(m.pnlPct)})`}`;
|
|
2708
|
+
const lev = m.lev && (m.kind === "open" || m.kind === "add" || m.kind === "flip") ? ` ${trim(m.lev, 1)}x` : "";
|
|
2709
|
+
return `${VERB[m.kind]} ${m.side} ${m.coin} ${usd(m.usd)} @ ${price(m.px)}${lev}${pnl}`;
|
|
2710
|
+
};
|
|
2711
|
+
var entryText = (side, edgePct) => {
|
|
2712
|
+
if (!side || edgePct === null) return null;
|
|
2713
|
+
const below = side === "long" ? edgePct >= 0 : edgePct < 0;
|
|
2714
|
+
return `${Math.abs(edgePct).toFixed(1)}% ${below ? "under" : "over"} their entry`;
|
|
2715
|
+
};
|
|
2716
|
+
function renderMarket(m, now = Date.now()) {
|
|
2717
|
+
const head = [`market \xB7 ${m.rows.length} of ${m.total}${m.offset ? ` from #${m.offset + 1}` : ""}`];
|
|
2718
|
+
if (m.market.os !== null) head.push(`${m.market.os} of 23 large coins down >7% in 24h`);
|
|
2719
|
+
if (m.market.Mti !== null) head.push(`majors trend ${pct(m.market.Mti, 2)}`);
|
|
2720
|
+
if (m.asOf) head.push(`as of ${ago(Date.parse(m.asOf), now)} ago`);
|
|
2721
|
+
const lines = [head.join(" \xB7 ")];
|
|
2722
|
+
if (m.rows.length) {
|
|
2723
|
+
lines.push("coin | price | 1h | 24h | vol 24h | funding APR | open interest | setup | top traders");
|
|
2724
|
+
for (const r of m.rows) {
|
|
2725
|
+
const sm = r.sm?.side ? [`${r.sm.side} ${Math.round(r.sm.share * 100)}%`, entryText(r.sm.side, r.sm.edgePct), r.sm.discount === "strict" ? "discount" : null].filter(Boolean).join(", ") : "none";
|
|
2726
|
+
lines.push(
|
|
2727
|
+
[r.coin, price(r.px), pct(r.r1h), pct(r.r24h), usd(r.volUsd), pct(r.fundAprPct, 0), usd(r.oiUsd), r.setup ?? "n/a", sm].join(
|
|
2728
|
+
" | "
|
|
2729
|
+
)
|
|
2730
|
+
);
|
|
2731
|
+
}
|
|
2732
|
+
} else lines.push("No coins match.");
|
|
2733
|
+
if (m.unknown.length) lines.push(`Not listed on Hyperliquid: ${m.unknown.join(", ")}`);
|
|
2734
|
+
if (m.next !== null) lines.push(`More: call again with offset=${m.next}.`);
|
|
2735
|
+
return lines.join("\n");
|
|
2736
|
+
}
|
|
2737
|
+
var pileText = (p, now) => `${p.coin} | ${p.traders} traders ${p.dir === "in" ? "into" : "out of"} ${p.side} | ${usd(p.totalUsd)} | ${ago(p.first, now)} to ${ago(p.last, now)} ago | ${p.names.join(", ")}`;
|
|
2738
|
+
function renderBoard(b, now = Date.now()) {
|
|
2739
|
+
const title = {
|
|
2740
|
+
conviction: "where the top traders agree (weighted by track record)",
|
|
2741
|
+
discount: "coins below the top traders\u2019 entry (longs) or above it (shorts)",
|
|
2742
|
+
flows: "biggest net flows of the last hour",
|
|
2743
|
+
pileins: "pile-ins: 3+ top traders moving the same way within 45 min, last 2h"
|
|
2744
|
+
};
|
|
2745
|
+
const lines = [`smart money \xB7 ${title[b.view]}${b.asOf ? ` \xB7 as of ${ago(b.asOf, now)} ago` : ""}`];
|
|
2746
|
+
if (b.view === "pileins") {
|
|
2747
|
+
if (b.pileIns.length) lines.push("coin | who | size | when | biggest movers", ...b.pileIns.map((p) => pileText(p, now)));
|
|
2748
|
+
else lines.push("No pile-ins in the last 2 hours.");
|
|
2749
|
+
} else if (b.rows.length) {
|
|
2750
|
+
lines.push("coin | side | share | size | traders | their entry | price | vs entry | 1h flow | setup | 24h");
|
|
2751
|
+
for (const r of b.rows)
|
|
2752
|
+
lines.push(
|
|
2753
|
+
[
|
|
2754
|
+
r.coin,
|
|
2755
|
+
r.side ?? "n/a",
|
|
2756
|
+
`${Math.round(r.share * 100)}%`,
|
|
2757
|
+
usd(r.sideUsd),
|
|
2758
|
+
`${r.traders} (${trim(r.heads, 1)} weighted)`,
|
|
2759
|
+
price(r.entry),
|
|
2760
|
+
price(r.mark),
|
|
2761
|
+
entryText(r.side, r.edgePct) ?? "n/a",
|
|
2762
|
+
usd(r.flow1hUsd, true),
|
|
2763
|
+
r.setup ?? "n/a",
|
|
2764
|
+
pct(r.r24h)
|
|
2765
|
+
].join(" | ")
|
|
2766
|
+
);
|
|
2767
|
+
} else lines.push("No coins on this board right now.");
|
|
2768
|
+
if (b.note) lines.push(b.note);
|
|
2769
|
+
return lines.join("\n");
|
|
2770
|
+
}
|
|
2771
|
+
function renderMoves(m, now = Date.now()) {
|
|
2772
|
+
const lines = [];
|
|
2773
|
+
if (m.order === "oldest") lines.push(m.moves.length ? `${m.moves.length} new move${m.moves.length === 1 ? "" : "s"}, oldest first` : "No new moves.");
|
|
2774
|
+
else lines.push(m.moves.length ? `latest ${m.moves.length} moves, newest first` : "No moves match.");
|
|
2775
|
+
for (const mv of m.moves) lines.push(`${ago(mv.t, now)} ago | ${trader(mv)} | ${moveText(mv)}`);
|
|
2776
|
+
if (m.gap) lines.push("Some moves before these were missed: the cursor was older than the moves kept.");
|
|
2777
|
+
if (m.more) lines.push(`More waiting: call again with since=${m.next}.`);
|
|
2778
|
+
else if (m.next) lines.push(`next=${m.next} (pass as since to get only newer moves)`);
|
|
2779
|
+
if (m.note) lines.push(m.note);
|
|
2780
|
+
return lines.join("\n");
|
|
2781
|
+
}
|
|
2782
|
+
function renderCoin(c, you, now = Date.now()) {
|
|
2783
|
+
const lines = [];
|
|
2784
|
+
const sm = c.sm;
|
|
2785
|
+
const mk = c.market;
|
|
2786
|
+
if (sm && sm.side) {
|
|
2787
|
+
const ours = sm.side === "long" ? sm.long : sm.short;
|
|
2788
|
+
const theirs = sm.side === "long" ? sm.short : sm.long;
|
|
2789
|
+
const tags = [sm.discount === "strict" ? "on the discount board" : null, sm.conviction !== null ? "on the conviction board" : null].filter(Boolean);
|
|
2790
|
+
lines.push(
|
|
2791
|
+
`${c.coin}: top traders lean ${sm.side.toUpperCase()} ${Math.round(sm.share * 100)}% (${usd(ours.usd)} vs ${usd(theirs.usd)})${tags.length ? ` \xB7 ${tags.join(", ")}` : ""}`
|
|
2792
|
+
);
|
|
2793
|
+
lines.push(
|
|
2794
|
+
`book | long ${sm.long.traders} traders (${trim(sm.long.heads, 1)} weighted), avg entry ${price(sm.long.entry)} | short ${sm.short.traders} traders (${trim(sm.short.heads, 1)} weighted), avg entry ${price(sm.short.entry)}`
|
|
2795
|
+
);
|
|
2796
|
+
} else lines.push(`${c.coin}: no top-trader positions right now`);
|
|
2797
|
+
if (mk) {
|
|
2798
|
+
const vs = sm?.side ? entryText(sm.side, sm.edgePct) : null;
|
|
2799
|
+
lines.push(`price | ${price(mk.px)}${vs ? ` (${vs})` : ""} \xB7 15m ${pct(mk.r15m)} \xB7 1h ${pct(mk.r1h)} \xB7 24h ${pct(mk.r24h)}`);
|
|
2800
|
+
lines.push(`setup | ${mk.setup ?? "n/a"} \xB7 funding ${pct(mk.fundAprPct, 0)} APR \xB7 open interest ${usd(mk.oiUsd)} \xB7 vol ${usd(mk.volUsd)}`);
|
|
2801
|
+
} else lines.push("price | no market data for this coin");
|
|
2802
|
+
if (sm && sm.flow1hUsd !== 0) lines.push(`flow | ${usd(Math.abs(sm.flow1hUsd))} net toward ${sm.flow1hUsd > 0 ? "long" : "short"} in the last hour`);
|
|
2803
|
+
if (sm?.pileIn) lines.push(`pile-in | ${pileText(sm.pileIn, now)}`);
|
|
2804
|
+
if (c.moves.length) lines.push(...c.moves.map((m) => `move | ${ago(m.t, now)} ago | ${trader(m)} | ${moveText(m)}`));
|
|
2805
|
+
else lines.push("move | none of $10k+ recently");
|
|
2806
|
+
if (you) {
|
|
2807
|
+
const p = you.position;
|
|
2808
|
+
lines.push(
|
|
2809
|
+
p ? `you | ${p.side.toLowerCase()} ${trim(Math.abs(p.size), 6)} @ ${price(p.entryPrice)}, open PnL ${usd(p.unrealizedPnl, true)}, ${trim(p.leverage, 1)}x \xB7 ${usd(you.availableBalance)} free` : `you | no ${c.coin} position \xB7 ${usd(you.availableBalance)} free`
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
if (c.note) lines.push(c.note);
|
|
2813
|
+
return lines.join("\n");
|
|
2814
|
+
}
|
|
2815
|
+
function renderAccount(p, orders, summary) {
|
|
2816
|
+
const lines = [
|
|
2817
|
+
`account \xB7 value ${usd(p.totalBalance)} \xB7 free ${usd(p.availableBalance)} \xB7 margin used ${usd(p.totalMargin)} \xB7 open PnL ${usd(p.unrealizedPnl, true)}`
|
|
2818
|
+
];
|
|
2819
|
+
if (p.positions.length) {
|
|
2820
|
+
lines.push("positions: coin | side | size @ entry | now | open PnL | leverage | liquidation | TP/SL (id)");
|
|
2821
|
+
for (const pos of p.positions) {
|
|
2822
|
+
const prot = p.protectiveOrders.filter((o) => o.symbol.replace(/USDT$/, "").toUpperCase() === pos.symbol.replace(/USDT$/, "").toUpperCase()).map((o) => `${o.kind === "TAKE_PROFIT" ? "TP" : "SL"} ${o.stopPrice ?? "n/a"} (${o.id})`);
|
|
2823
|
+
lines.push(
|
|
2824
|
+
[
|
|
2825
|
+
pos.symbol,
|
|
2826
|
+
pos.side.toLowerCase(),
|
|
2827
|
+
`${trim(Math.abs(pos.size), 6)} @ ${price(pos.entryPrice)}`,
|
|
2828
|
+
price(pos.markPrice),
|
|
2829
|
+
usd(pos.unrealizedPnl, true),
|
|
2830
|
+
`${trim(pos.leverage, 1)}x`,
|
|
2831
|
+
price(pos.liquidationPrice ?? null),
|
|
2832
|
+
prot.length ? prot.join(", ") : "none"
|
|
2833
|
+
].join(" | ")
|
|
2834
|
+
);
|
|
2835
|
+
}
|
|
2836
|
+
} else lines.push("positions: none");
|
|
2837
|
+
if (orders) {
|
|
2838
|
+
const working = orders.filter((o) => !o.isTrigger);
|
|
2839
|
+
if (working.length) {
|
|
2840
|
+
lines.push("open orders: coin | side | size @ price | type | oid");
|
|
2841
|
+
for (const o of working) lines.push([o.coin, o.side === "B" ? "buy" : "sell", `${o.sz} @ ${o.limitPx}`, `${o.orderType}${o.tif ? ` ${o.tif}` : ""}${o.reduceOnly ? " reduce-only" : ""}`, o.oid].join(" | "));
|
|
2842
|
+
} else lines.push("open orders: none");
|
|
2843
|
+
}
|
|
2844
|
+
if (summary)
|
|
2845
|
+
lines.push(
|
|
2846
|
+
`${summary.window}: realized ${usd(summary.realizedPnl, true)} \xB7 fees ${usd(summary.totalFees)} \xB7 ${summary.winCount} wins / ${summary.lossCount} losses`
|
|
2847
|
+
);
|
|
2848
|
+
return lines.join("\n");
|
|
2849
|
+
}
|
|
2850
|
+
function positionLine(pos) {
|
|
2851
|
+
return [
|
|
2852
|
+
`${pos.side.toLowerCase()} ${trim(Math.abs(pos.size), 6)} ${pos.symbol.replace(/USDT$/, "")} @ ${price(pos.entryPrice)}`,
|
|
2853
|
+
`mark ${price(pos.markPrice)}`,
|
|
2854
|
+
`open PnL ${usd(pos.unrealizedPnl, true)}`,
|
|
2855
|
+
`${trim(pos.leverage, 1)}x`,
|
|
2856
|
+
`liquidation ${price(pos.liquidationPrice ?? null)}`
|
|
2857
|
+
].join(" \xB7 ");
|
|
2858
|
+
}
|
|
2859
|
+
var cents = (v, signed = false) => Math.abs(v) >= 1e3 ? usd(v, signed) : `${v < 0 ? "-" : signed && v > 0 ? "+" : ""}$${Math.abs(v).toFixed(2)}`;
|
|
2860
|
+
function renderTrades(page, paper2, coin2, now = Date.now()) {
|
|
2861
|
+
const f = page.fills;
|
|
2862
|
+
const closes = f.filter((x) => x.closedPnl !== 0);
|
|
2863
|
+
const pnl = f.reduce((sum, x) => sum + x.closedPnl, 0);
|
|
2864
|
+
const fees = f.reduce((sum, x) => sum + x.fee, 0);
|
|
2865
|
+
const wins = closes.filter((x) => x.closedPnl > 0).length;
|
|
2866
|
+
const head = `${paper2 ? "PRACTICE " : ""}trades ${page.window}${coin2 ? ` ${coin2}` : ""} \xB7 ${f.length} fills \xB7 realized ${cents(pnl, true)} \xB7 fees ${cents(fees)} \xB7 net ${cents(pnl - fees, true)} \xB7 ${wins} wins / ${closes.length - wins} losses`;
|
|
2867
|
+
if (!f.length) return `${head}
|
|
2868
|
+
no fills in this window`;
|
|
2869
|
+
const lines = [head, "ago | coin | what | size @ price | realized PnL | fee"];
|
|
2870
|
+
for (const x of f)
|
|
2871
|
+
lines.push([ago(x.time, now), x.coin, x.dir, `${x.sz} @ ${price(x.px)}`, x.closedPnl === 0 ? "-" : cents(x.closedPnl, true), cents(x.fee)].join(" | "));
|
|
2872
|
+
if (page.hasMore) lines.push("more fills are older: widen window or raise limit");
|
|
2873
|
+
return lines.join("\n");
|
|
2874
|
+
}
|
|
2875
|
+
function renderCandles(coin2, interval, c) {
|
|
2876
|
+
const iso = (t) => new Date(t).toISOString().slice(0, 16).replace("T", " ");
|
|
2877
|
+
const lines = [`${coin2} ${interval} candles \xB7 ${c.candles.length}, oldest first${c.complete ? "" : " \xB7 some missing minutes left gaps"}`, "time UTC,open,high,low,close,volume"];
|
|
2878
|
+
for (const k of c.candles) lines.push([iso(k.t), k.o, k.h, k.l, k.c, k.v].join(","));
|
|
2879
|
+
return lines.join("\n");
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
// src/lean/tools.ts
|
|
2883
|
+
var text = (body, data) => ({
|
|
2884
|
+
content: [{ type: "text", text: body }],
|
|
2885
|
+
structuredContent: data
|
|
2886
|
+
});
|
|
2887
|
+
var loginText = (login) => `Helios is not connected yet. Open this link to approve access, then call this tool again:
|
|
2888
|
+
${login.verificationUriComplete}
|
|
2889
|
+
User code: ${login.userCode}`;
|
|
2890
|
+
var pendingResult = (login) => text(loginText(login), {
|
|
2891
|
+
status: "pending",
|
|
2892
|
+
connected: false,
|
|
2893
|
+
actionRequired: true,
|
|
2894
|
+
loginUrl: login.verificationUriComplete,
|
|
2895
|
+
userCode: login.userCode
|
|
2896
|
+
});
|
|
2897
|
+
async function withSession(auth, scope, call) {
|
|
2898
|
+
const loginOptions = { clientName: "Helios MCP", scope };
|
|
2899
|
+
const login = await auth.ensureLogin(loginOptions);
|
|
2900
|
+
if (login.status !== "connected") return { ok: false, pending: login };
|
|
2901
|
+
const session = await auth.requireSession();
|
|
2902
|
+
try {
|
|
2903
|
+
return { ok: true, value: await call(session.accessToken) };
|
|
2904
|
+
} catch (error) {
|
|
2905
|
+
const recovery = await retryAfterUnauthorizedRefresh({ authService: auth, error, loginOptions, retry: call });
|
|
2906
|
+
if (recovery === null) throw error;
|
|
2907
|
+
if (isPending(recovery)) return { ok: false, pending: recovery };
|
|
2908
|
+
return { ok: true, value: recovery };
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
function isPending(value) {
|
|
2912
|
+
return Boolean(value && typeof value === "object" && value.connected === false && "verificationUriComplete" in value);
|
|
2913
|
+
}
|
|
2914
|
+
async function sessionScopes(auth) {
|
|
2915
|
+
const session = await auth.requireSession().catch(() => null);
|
|
2916
|
+
return new Set(session?.scope ?? []);
|
|
2917
|
+
}
|
|
2918
|
+
var coin = import_zod6.z.string().trim().min(1).max(36).describe("e.g. BTC, kPEPE, xyz:TSLA");
|
|
2919
|
+
var smBoardInputSchema = import_zod6.z.object({
|
|
2920
|
+
view: import_zod6.z.enum(["conviction", "discount", "flows", "pileins"]).optional().describe("conviction (default): where they agree; discount: price better than their entry; flows: last hour; pileins: 3+ moving together"),
|
|
2921
|
+
side: import_zod6.z.enum(["long", "short", "any"]).optional(),
|
|
2922
|
+
limit: import_zod6.z.number().int().min(1).max(25).optional().describe("Default 8")
|
|
2923
|
+
});
|
|
2924
|
+
var smMovesInputSchema = import_zod6.z.object({
|
|
2925
|
+
since: import_zod6.z.string().regex(/^\d{1,16}-\d{1,16}$/).optional().describe("next from your last call: only newer moves"),
|
|
2926
|
+
coin: coin.optional(),
|
|
2927
|
+
kind: import_zod6.z.enum(["entries", "exits", "all"]).optional().describe("entries = opens, adds; exits = trims, closes"),
|
|
2928
|
+
min_usd: import_zod6.z.number().nonnegative().optional().describe("Default 25000"),
|
|
2929
|
+
limit: import_zod6.z.number().int().min(1).max(50).optional().describe("Default 15")
|
|
2930
|
+
});
|
|
2931
|
+
var smCoinInputSchema = import_zod6.z.object({ coin });
|
|
2932
|
+
var marketInputSchema = import_zod6.z.object({
|
|
2933
|
+
coins: import_zod6.z.string().max(400).optional().describe('Comma list, e.g. "BTC,HYPE"; omit for all'),
|
|
2934
|
+
limit: import_zod6.z.number().int().min(1).max(300).optional().describe("Default 20"),
|
|
2935
|
+
offset: import_zod6.z.number().int().nonnegative().optional(),
|
|
2936
|
+
sort: import_zod6.z.enum(["volume", "rank", "move1h", "move24h", "funding", "lean", "edge"]).optional(),
|
|
2937
|
+
filter: import_zod6.z.enum(["turning", "falling", "stretched_up", "stretched_down", "sm_long", "sm_short", "discount"]).optional().describe("turning = turning up from breakdown (held up in backtests); discount = better than their entry")
|
|
2938
|
+
});
|
|
2939
|
+
var paper = import_zod6.z.boolean().optional().describe("true = your practice account: live prices, simulated fills, no real money (market orders only)");
|
|
2940
|
+
var accountInputSchema = import_zod6.z.object({ paper });
|
|
2941
|
+
var tradeInputSchema = import_zod6.z.object({
|
|
2942
|
+
coin,
|
|
2943
|
+
action: import_zod6.z.enum(["open", "add", "close", "reverse", "reset"]).describe("reset = start the practice account over (paper only; margin_usd = starting balance, default $10k)"),
|
|
2944
|
+
side: import_zod6.z.enum(["long", "short"]).optional().describe("For open, add, reverse"),
|
|
2945
|
+
// Only obviously absurd values are cut here; Hyperliquid's margin check and the person's approval do the rest.
|
|
2946
|
+
margin_usd: import_zod6.z.number().positive().max(1e6).optional().describe("Size = margin x leverage"),
|
|
2947
|
+
leverage: import_zod6.z.number().positive().max(100).optional(),
|
|
2948
|
+
sl_pct: import_zod6.z.number().positive().max(95).optional().describe("Stop, % from entry"),
|
|
2949
|
+
tp_pct: import_zod6.z.number().positive().max(1e3).optional().describe("Take profit, % from entry"),
|
|
2950
|
+
limit_px: import_zod6.z.number().positive().optional().describe("Omit for market"),
|
|
2951
|
+
slippage_bps: import_zod6.z.number().int().min(1).max(1e3).optional().describe("Market only: worst fill vs price, bps. Default 300 (3%)"),
|
|
2952
|
+
fraction: import_zod6.z.number().positive().max(1).optional().describe("close only: share to close, e.g. 0.5 = half. Default 1 = all"),
|
|
2953
|
+
paper,
|
|
2954
|
+
key: import_zod6.z.string().regex(/^[A-Za-z0-9_-]{8,64}$/).optional().describe("Same key on retry = never placed twice. Omitted: repeats of the same call within 60s of the first are placed once")
|
|
2955
|
+
});
|
|
2956
|
+
var TRADE_KEY_WINDOW_SECONDS = 60;
|
|
2957
|
+
function tradeKeyFor(input, account) {
|
|
2958
|
+
const order = [
|
|
2959
|
+
account,
|
|
2960
|
+
input.coin.toUpperCase(),
|
|
2961
|
+
input.action,
|
|
2962
|
+
input.side ?? "",
|
|
2963
|
+
input.margin_usd ?? "",
|
|
2964
|
+
input.leverage ?? "",
|
|
2965
|
+
input.sl_pct ?? "",
|
|
2966
|
+
input.tp_pct ?? "",
|
|
2967
|
+
input.limit_px ?? "",
|
|
2968
|
+
input.slippage_bps ?? "",
|
|
2969
|
+
input.fraction ?? ""
|
|
2970
|
+
];
|
|
2971
|
+
return `mcpw-${(0, import_node_crypto3.createHash)("sha256").update(`trade:${order.join("|")}`).digest("base64url")}`;
|
|
2972
|
+
}
|
|
2973
|
+
var protectInputSchema = import_zod6.z.object({
|
|
2974
|
+
coin,
|
|
2975
|
+
op: import_zod6.z.enum(["set", "remove", "cancel", "move"]).describe("set TP/SL; remove a TP/SL; cancel an order; move an open order to px"),
|
|
2976
|
+
tp_px: import_zod6.z.number().positive().optional(),
|
|
2977
|
+
sl_px: import_zod6.z.number().positive().optional(),
|
|
2978
|
+
order_id: import_zod6.z.string().max(64).optional().describe("From account: TP/SL id or order oid"),
|
|
2979
|
+
px: import_zod6.z.number().positive().optional().describe("move only: the new limit (or trigger) price"),
|
|
2980
|
+
paper
|
|
2981
|
+
});
|
|
2982
|
+
var tradesInputSchema = import_zod6.z.object({
|
|
2983
|
+
window: import_zod6.z.enum(["24h", "7d", "30d"]).optional().describe("Default 7d"),
|
|
2984
|
+
coin: coin.optional(),
|
|
2985
|
+
limit: import_zod6.z.number().int().min(1).max(100).optional().describe("Default 20"),
|
|
2986
|
+
paper
|
|
2987
|
+
});
|
|
2988
|
+
var candlesInputSchema = import_zod6.z.object({
|
|
2989
|
+
coin: import_zod6.z.string().regex(/^[A-Za-z0-9]{1,20}$/).describe("e.g. BTC, kPEPE"),
|
|
2990
|
+
interval: import_zod6.z.enum(["1m", "5m", "15m", "1h", "4h", "1d"]),
|
|
2991
|
+
limit: import_zod6.z.number().int().min(1).max(1e3).optional().describe("Default 100")
|
|
2992
|
+
});
|
|
2993
|
+
var READ = [AGENT_SCOPES.MARKET_READ];
|
|
2994
|
+
var sameCoinName = (a, b) => a.replace(/USDT$/, "").toUpperCase() === b.replace(/USDT$/, "").toUpperCase();
|
|
2995
|
+
var sameCoin = (pos, coin2) => sameCoinName(pos.symbol, coin2);
|
|
2996
|
+
var paperTotals = (p) => p.realizedPnl === void 0 ? "" : `
|
|
2997
|
+
practice totals: realized ${p.realizedPnl >= 0 ? "+" : "-"}$${Math.abs(p.realizedPnl).toFixed(2)} \xB7 fees $${(p.feesPaid ?? 0).toFixed(2)} \xB7 funding $${(p.fundingPaid ?? 0).toFixed(2)} \xB7 liquidations ${p.liquidations ?? 0}`;
|
|
2998
|
+
function wrongSideTpSl(side, mark, tp, sl) {
|
|
2999
|
+
const long = side === "LONG";
|
|
3000
|
+
const at = `${side.toLowerCase()} (price ${price(mark)})`;
|
|
3001
|
+
if (sl !== void 0 && (long ? sl >= mark : sl <= mark)) return `Stop ${price(sl)} must be ${long ? "under" : "over"} the price for a ${at}`;
|
|
3002
|
+
if (tp !== void 0 && (long ? tp <= mark : tp >= mark)) return `Take profit ${price(tp)} must be ${long ? "over" : "under"} the price for a ${at}`;
|
|
3003
|
+
return null;
|
|
3004
|
+
}
|
|
3005
|
+
function createLeanToolDefinitions(services) {
|
|
3006
|
+
const { auth, dataBridge } = services;
|
|
3007
|
+
const smBoard = {
|
|
3008
|
+
name: "sm_board",
|
|
3009
|
+
title: "Smart Money Board",
|
|
3010
|
+
description: "Where the top Hyperliquid traders (ranked on a year of results) are positioned: side, size, entry vs price.",
|
|
3011
|
+
inputSchema: smBoardInputSchema,
|
|
3012
|
+
async execute(input) {
|
|
3013
|
+
const r = await withSession(auth, READ, (token) => dataBridge.getSmartMoneyBoard(token, input));
|
|
3014
|
+
return r.ok ? text(renderBoard(r.value), r.value) : pendingResult(r.pending);
|
|
3015
|
+
}
|
|
3016
|
+
};
|
|
3017
|
+
const smMoves = {
|
|
3018
|
+
name: "sm_moves",
|
|
3019
|
+
title: "Smart Money Moves",
|
|
3020
|
+
description: "Top traders\u2019 latest trades. Poll with since=next for new ones only.",
|
|
3021
|
+
inputSchema: smMovesInputSchema,
|
|
3022
|
+
async execute(input) {
|
|
3023
|
+
const r = await withSession(
|
|
3024
|
+
auth,
|
|
3025
|
+
READ,
|
|
3026
|
+
(token) => dataBridge.getSmartMoneyMoves(token, { since: input.since, coin: input.coin, kind: input.kind, minUsd: input.min_usd, limit: input.limit })
|
|
3027
|
+
);
|
|
3028
|
+
return r.ok ? text(renderMoves(r.value), r.value) : pendingResult(r.pending);
|
|
3029
|
+
}
|
|
3030
|
+
};
|
|
3031
|
+
const smCoin = {
|
|
3032
|
+
name: "sm_coin",
|
|
3033
|
+
title: "Smart Money Coin Brief",
|
|
3034
|
+
description: "One coin in one call: top traders\u2019 book, entries and trades, price, setup, funding, OI, your position.",
|
|
3035
|
+
inputSchema: smCoinInputSchema,
|
|
3036
|
+
async execute(input) {
|
|
3037
|
+
const r = await withSession(auth, READ, (token) => dataBridge.getSmartMoneyCoin(token, input.coin));
|
|
3038
|
+
if (!r.ok) return pendingResult(r.pending);
|
|
3039
|
+
let you = null;
|
|
3040
|
+
if ((await sessionScopes(auth)).has(AGENT_SCOPES.ACCOUNT_READ)) {
|
|
3041
|
+
const p = await withSession(auth, [AGENT_SCOPES.ACCOUNT_READ], (token) => dataBridge.getPortfolio(token, { provider: "hyperliquid" })).catch(
|
|
3042
|
+
() => null
|
|
3043
|
+
);
|
|
3044
|
+
if (p?.ok) {
|
|
3045
|
+
you = {
|
|
3046
|
+
position: p.value.positions.find((pos) => sameCoin(pos, r.value.coin)) ?? null,
|
|
3047
|
+
availableBalance: p.value.availableBalance
|
|
3048
|
+
};
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
return text(renderCoin(r.value, you), { ...r.value, you });
|
|
3052
|
+
}
|
|
3053
|
+
};
|
|
3054
|
+
const market = {
|
|
3055
|
+
name: "market",
|
|
3056
|
+
title: "Market Scan",
|
|
3057
|
+
description: "All coins: price, moves, volume, funding, OI, setup label, top traders\u2019 lean. Filter, sort, page.",
|
|
3058
|
+
inputSchema: marketInputSchema,
|
|
3059
|
+
async execute(input) {
|
|
3060
|
+
const coins = input.coins?.split(",").map((c) => c.trim()).filter(Boolean).slice(0, 50);
|
|
3061
|
+
const r = await withSession(
|
|
3062
|
+
auth,
|
|
3063
|
+
READ,
|
|
3064
|
+
(token) => dataBridge.getAgentMarket(token, {
|
|
3065
|
+
...coins?.length ? { coins } : {},
|
|
3066
|
+
limit: input.limit,
|
|
3067
|
+
offset: input.offset,
|
|
3068
|
+
sort: input.sort,
|
|
3069
|
+
filter: input.filter
|
|
3070
|
+
})
|
|
3071
|
+
);
|
|
3072
|
+
return r.ok ? text(renderMarket(r.value), r.value) : pendingResult(r.pending);
|
|
3073
|
+
}
|
|
3074
|
+
};
|
|
3075
|
+
const account = {
|
|
3076
|
+
name: "account",
|
|
3077
|
+
title: "Account",
|
|
3078
|
+
description: "Balance, positions with TP/SL ids, open orders, 30-day result. paper=true for the practice account.",
|
|
3079
|
+
inputSchema: accountInputSchema,
|
|
3080
|
+
async execute(input) {
|
|
3081
|
+
if (input.paper) {
|
|
3082
|
+
const p = await withSession(auth, [AGENT_SCOPES.ACCOUNT_READ], (token) => dataBridge.getPaperPortfolio(token));
|
|
3083
|
+
if (!p.ok) return pendingResult(p.pending);
|
|
3084
|
+
return text(`PRACTICE ${renderAccount(p.value, null, null)}${paperTotals(p.value)}`, { paper: true, portfolio: p.value });
|
|
3085
|
+
}
|
|
3086
|
+
const r = await withSession(
|
|
3087
|
+
auth,
|
|
3088
|
+
[AGENT_SCOPES.ACCOUNT_READ],
|
|
3089
|
+
(token) => Promise.all([
|
|
3090
|
+
dataBridge.getPortfolio(token, { provider: "hyperliquid" }),
|
|
3091
|
+
dataBridge.getOpenOrders(token).catch(() => null),
|
|
3092
|
+
dataBridge.getLedgerSummary(token, { window: "30d" }).catch(() => null)
|
|
3093
|
+
])
|
|
3094
|
+
);
|
|
3095
|
+
if (!r.ok) return pendingResult(r.pending);
|
|
3096
|
+
const [portfolio, orders, summary] = r.value;
|
|
3097
|
+
return text(renderAccount(portfolio, orders, summary), { portfolio, orders, summary });
|
|
3098
|
+
}
|
|
3099
|
+
};
|
|
3100
|
+
const trade = {
|
|
3101
|
+
name: "trade",
|
|
3102
|
+
title: "Trade",
|
|
3103
|
+
description: "Open, add to, close (all or part) or reverse a position, sized by margin in USD. Answers with the position after the order. paper=true to practice.",
|
|
3104
|
+
inputSchema: tradeInputSchema,
|
|
3105
|
+
acts: true,
|
|
3106
|
+
async execute(input) {
|
|
3107
|
+
if (input.fraction !== void 0 && input.action !== "close") throw new Error("fraction is for close only");
|
|
3108
|
+
if (input.action === "reset") {
|
|
3109
|
+
if (!input.paper) throw new Error("reset is for the practice account only: pass paper=true");
|
|
3110
|
+
const r2 = await withSession(auth, [AGENT_SCOPES.ORDERS_WRITE], (token) => dataBridge.resetPaperAccount(token, input.margin_usd));
|
|
3111
|
+
if (!r2.ok) return pendingResult(r2.pending);
|
|
3112
|
+
return text(`Practice account started over with ${input.margin_usd ? `$${input.margin_usd}` : "the default balance"}.`, { paper: true, reset: true });
|
|
3113
|
+
}
|
|
3114
|
+
if (input.paper) return paperTrade(input);
|
|
3115
|
+
if (input.action === "close") {
|
|
3116
|
+
const r2 = await withSession(
|
|
3117
|
+
auth,
|
|
3118
|
+
[AGENT_SCOPES.ORDERS_WRITE],
|
|
3119
|
+
(token) => dataBridge.closePosition(token, {
|
|
3120
|
+
provider: "hyperliquid",
|
|
3121
|
+
symbol: input.coin,
|
|
3122
|
+
...input.fraction !== void 0 && input.fraction < 1 ? { fraction: input.fraction } : {},
|
|
3123
|
+
// A close is a market order: the same cap applies (unset, data-bridge uses its default).
|
|
3124
|
+
...input.slippage_bps ? { maxSlippageBps: input.slippage_bps } : {}
|
|
3125
|
+
})
|
|
3126
|
+
);
|
|
3127
|
+
if (!r2.ok) return pendingResult(r2.pending);
|
|
3128
|
+
if (r2.value.outcome === "no_position") return text(`No open ${input.coin} position: nothing was sent.`, r2.value);
|
|
3129
|
+
const after2 = await positionAfter(input.coin, false);
|
|
3130
|
+
const what = r2.value.mode === "PARTIAL" ? `${Math.round((input.fraction ?? 1) * 100)}% of the ${input.coin} position` : `the whole ${input.coin} position`;
|
|
3131
|
+
return text(`Sent a market order closing ${what}. ${after2.text}`, { ...r2.value, after: after2.position });
|
|
3132
|
+
}
|
|
3133
|
+
if (!input.side || !input.margin_usd) throw new Error(`${input.action} needs side and margin_usd`);
|
|
3134
|
+
if (input.limit_px && input.slippage_bps) throw new Error("slippage_bps is for market orders: a limit order fills at limit_px or better");
|
|
3135
|
+
const prot = await protectionFor(input);
|
|
3136
|
+
if ("pending" in prot) return pendingResult(prot.pending);
|
|
3137
|
+
const protection = prot.value;
|
|
3138
|
+
const intentType = input.action === "open" ? "OPEN" : input.action === "add" ? "DCA" : "REVERSE";
|
|
3139
|
+
let idempotencyKey = input.key;
|
|
3140
|
+
const r = await withSession(auth, [AGENT_SCOPES.ORDERS_WRITE], async (token) => {
|
|
3141
|
+
if (!idempotencyKey) {
|
|
3142
|
+
const session = await auth.requireSession();
|
|
3143
|
+
idempotencyKey = tradeKeyFor(input, `${session.ownerUserId}|${session.agentGrantId}`);
|
|
3144
|
+
}
|
|
3145
|
+
return dataBridge.submitTradeIntent(token, {
|
|
3146
|
+
provider: "hyperliquid",
|
|
3147
|
+
symbol: input.coin,
|
|
3148
|
+
intentType,
|
|
3149
|
+
direction: input.side === "long" ? "LONG" : "SHORT",
|
|
3150
|
+
size: { mode: "MARGIN_USD", value: input.margin_usd },
|
|
3151
|
+
execution: input.limit_px ? { style: "LIMIT_GTC", limitPrice: input.limit_px } : { style: "MARKET" },
|
|
3152
|
+
// Unset, data-bridge caps a market order at its own default (300 bps).
|
|
3153
|
+
...input.slippage_bps ? { maxSlippageBps: input.slippage_bps } : {},
|
|
3154
|
+
...input.leverage ? { leverageOverride: input.leverage } : {},
|
|
3155
|
+
...protection ? { protection } : {},
|
|
3156
|
+
idempotencyKey,
|
|
3157
|
+
...input.key ? {} : { idempotencyWindowSeconds: TRADE_KEY_WINDOW_SECONDS }
|
|
3158
|
+
});
|
|
3159
|
+
}).catch((error) => {
|
|
3160
|
+
if (!isHeliosAgentApiError(error) || error.code !== "CONFLICT" || !/being placed/.test(error.message)) throw error;
|
|
3161
|
+
const why = input.key ? `key ${idempotencyKey} was already used` : `an identical trade call within ${TRADE_KEY_WINDOW_SECONDS}s of the first is the same order`;
|
|
3162
|
+
throw new Error(
|
|
3163
|
+
`${error.message} (${why}, so this may be that earlier order.) Call account to see whether it went through. To place another one on purpose, call trade again with a new key.`
|
|
3164
|
+
);
|
|
3165
|
+
});
|
|
3166
|
+
if (!r.ok) return pendingResult(r.pending);
|
|
3167
|
+
const s = r.value;
|
|
3168
|
+
const parts = [
|
|
3169
|
+
s.outcome === "replayed" ? `Already sent earlier with key ${idempotencyKey}${input.key ? "" : ` (an identical call within ${TRADE_KEY_WINDOW_SECONDS}s of the first is the same order)`}: nothing new was placed. To place another on purpose, pass a new key.` : `Sent: ${input.action} ${input.side} ${input.coin}, $${input.margin_usd} margin${input.leverage ? ` at ${input.leverage}x` : ""}, ${input.limit_px ? `limit ${price(input.limit_px)}` : "market"}.`
|
|
3170
|
+
];
|
|
3171
|
+
if (protection?.stopLoss) parts.push(`Stop ${price(protection.stopLoss.triggerPrice)}.`);
|
|
3172
|
+
if (protection?.takeProfit) parts.push(`Take profit ${price(protection.takeProfit.triggerPrice)}.`);
|
|
3173
|
+
if (s.orderStatus) parts.push(`Order status: ${s.orderStatus}.`);
|
|
3174
|
+
if (s.rejectedProtection?.length) parts.push(`Hyperliquid refused part of the TP/SL; check account and set it with protect.`);
|
|
3175
|
+
parts.push(input.key ? `key=${idempotencyKey}` : `key=${idempotencyKey} (an identical call within ${TRADE_KEY_WINDOW_SECONDS}s of the first is the same order)`);
|
|
3176
|
+
let after;
|
|
3177
|
+
if (!input.limit_px && s.outcome !== "replayed") {
|
|
3178
|
+
const a = await positionAfter(input.coin, false);
|
|
3179
|
+
parts.push(a.text);
|
|
3180
|
+
after = a.position;
|
|
3181
|
+
} else if (input.limit_px) parts.push("A limit order rests until filled: see it in account; move it with protect op=move.");
|
|
3182
|
+
return text(parts.join(" "), { ...s, idempotencyKey, ...after !== void 0 ? { after } : {} });
|
|
3183
|
+
}
|
|
3184
|
+
};
|
|
3185
|
+
const protect = {
|
|
3186
|
+
name: "protect",
|
|
3187
|
+
title: "Protect",
|
|
3188
|
+
description: "Set or remove TP/SL on a position, or cancel an order.",
|
|
3189
|
+
inputSchema: protectInputSchema,
|
|
3190
|
+
acts: true,
|
|
3191
|
+
async execute(input) {
|
|
3192
|
+
if (input.op === "set") {
|
|
3193
|
+
if (!input.tp_px && !input.sl_px) throw new Error("set needs tp_px, sl_px or both");
|
|
3194
|
+
const p = await withSession(
|
|
3195
|
+
auth,
|
|
3196
|
+
[AGENT_SCOPES.ACCOUNT_READ],
|
|
3197
|
+
(token) => input.paper ? dataBridge.getPaperPortfolio(token) : dataBridge.getPortfolio(token, { provider: "hyperliquid" })
|
|
3198
|
+
).catch(
|
|
3199
|
+
(error) => {
|
|
3200
|
+
throw new Error(
|
|
3201
|
+
`Could not read your ${input.coin} position to check the TP/SL side (${error instanceof Error ? error.message : String(error)}); nothing was set.`
|
|
3202
|
+
);
|
|
3203
|
+
}
|
|
3204
|
+
);
|
|
3205
|
+
if (!p.ok) return pendingResult(p.pending);
|
|
3206
|
+
const position = p.value.positions.find((pos) => sameCoin(pos, input.coin));
|
|
3207
|
+
if (!position) throw new Error(`No open ${input.coin} position: nothing was set.`);
|
|
3208
|
+
if (!(position.markPrice > 0)) throw new Error(`No price for ${input.coin} to check the TP/SL against: nothing was set. Try again shortly.`);
|
|
3209
|
+
const wrong = wrongSideTpSl(position.side, position.markPrice, input.tp_px, input.sl_px);
|
|
3210
|
+
if (wrong) throw new Error(`${wrong}: it would trigger at once and close the position. Nothing was set.`);
|
|
3211
|
+
const tpSl = {
|
|
3212
|
+
provider: "hyperliquid",
|
|
3213
|
+
symbol: input.coin,
|
|
3214
|
+
applyTo: "FULL_POSITION",
|
|
3215
|
+
// TP/SL attach to the position on Hyperliquid; data-bridge links them to the order that opened it.
|
|
3216
|
+
parentClientOrderId: `hl-position:${input.coin}`,
|
|
3217
|
+
...input.tp_px ? { takeProfit: { kind: "MARKET", triggerPrice: input.tp_px } } : {},
|
|
3218
|
+
...input.sl_px ? { stopLoss: { kind: "MARKET", triggerPrice: input.sl_px } } : {}
|
|
3219
|
+
};
|
|
3220
|
+
const r2 = await withSession(
|
|
3221
|
+
auth,
|
|
3222
|
+
[AGENT_SCOPES.ORDERS_WRITE],
|
|
3223
|
+
(token) => input.paper ? dataBridge.setPaperTpSl(token, tpSl) : dataBridge.setTpSl(token, tpSl)
|
|
3224
|
+
);
|
|
3225
|
+
if (!r2.ok) return pendingResult(r2.pending);
|
|
3226
|
+
const legs = [input.tp_px ? `take profit ${price(input.tp_px)}` : null, input.sl_px ? `stop ${price(input.sl_px)}` : null].filter(Boolean);
|
|
3227
|
+
return text(`${input.paper ? "Practice: s" : "S"}et on the whole ${input.coin} position: ${legs.join(", ")}.`, r2.value);
|
|
3228
|
+
}
|
|
3229
|
+
if (!input.order_id) throw new Error(`${input.op} needs order_id (from account)`);
|
|
3230
|
+
if (input.op === "remove") {
|
|
3231
|
+
const del = { provider: "hyperliquid", symbol: input.coin, orderId: input.order_id };
|
|
3232
|
+
const r2 = await withSession(
|
|
3233
|
+
auth,
|
|
3234
|
+
[AGENT_SCOPES.ORDERS_CANCEL],
|
|
3235
|
+
(token) => input.paper ? dataBridge.deletePaperTpSl(token, del) : dataBridge.deleteTpSl(token, del)
|
|
3236
|
+
);
|
|
3237
|
+
return r2.ok ? text(`Removed TP/SL ${input.order_id} on ${input.coin}.`, r2.value) : pendingResult(r2.pending);
|
|
3238
|
+
}
|
|
3239
|
+
if (input.paper) throw new Error("The practice account has no resting orders (every order fills at market): nothing to cancel or move");
|
|
3240
|
+
const oid = Number(input.order_id);
|
|
3241
|
+
if (!Number.isInteger(oid) || oid < 0) throw new Error(`${input.op} needs the numeric order oid from account`);
|
|
3242
|
+
if (input.op === "move") {
|
|
3243
|
+
if (!input.px) throw new Error("move needs px, the new price");
|
|
3244
|
+
const o = await withSession(auth, [AGENT_SCOPES.ACCOUNT_READ], (token) => dataBridge.getOpenOrders(token));
|
|
3245
|
+
if (!o.ok) return pendingResult(o.pending);
|
|
3246
|
+
const order = o.value.find((x) => x.oid === oid);
|
|
3247
|
+
if (!order) throw new Error(`No open order ${oid}: it may have filled or been cancelled. Call account.`);
|
|
3248
|
+
const size = Number(order.sz);
|
|
3249
|
+
const r2 = await withSession(auth, [AGENT_SCOPES.ORDERS_WRITE], (token) => dataBridge.modifyOrder(token, { symbol: order.coin, oid, price: input.px, size }));
|
|
3250
|
+
return r2.ok ? text(`Moved order ${oid} on ${order.coin} to ${price(input.px)} (size ${order.sz}).`, r2.value) : pendingResult(r2.pending);
|
|
3251
|
+
}
|
|
3252
|
+
const r = await withSession(auth, [AGENT_SCOPES.ORDERS_CANCEL], (token) => dataBridge.cancelOrder(token, { symbol: input.coin, oid }));
|
|
3253
|
+
return r.ok ? text(`Cancelled order ${oid} on ${input.coin}.`, r.value) : pendingResult(r.pending);
|
|
3254
|
+
}
|
|
3255
|
+
};
|
|
3256
|
+
const trades = {
|
|
3257
|
+
name: "trades",
|
|
3258
|
+
title: "Your Trades",
|
|
3259
|
+
description: "Your recent fills, newest first: price, size, realized PnL and fees, with totals. Learn from results. paper=true for practice.",
|
|
3260
|
+
inputSchema: tradesInputSchema,
|
|
3261
|
+
async execute(input) {
|
|
3262
|
+
const window = input.window ?? "7d";
|
|
3263
|
+
const want = input.limit ?? 20;
|
|
3264
|
+
const ask = { window, limit: input.coin ? 200 : want };
|
|
3265
|
+
const r = await withSession(
|
|
3266
|
+
auth,
|
|
3267
|
+
[AGENT_SCOPES.ACCOUNT_READ],
|
|
3268
|
+
(token) => input.paper ? dataBridge.getPaperLedgerFills(token, ask) : dataBridge.getLedgerFills(token, ask)
|
|
3269
|
+
);
|
|
3270
|
+
if (!r.ok) return pendingResult(r.pending);
|
|
3271
|
+
const fills = r.value.fills.filter((f) => !input.coin || sameCoinName(f.coin, input.coin)).slice(0, want);
|
|
3272
|
+
const page = { ...r.value, fills };
|
|
3273
|
+
return text(renderTrades(page, Boolean(input.paper), input.coin ?? null), { paper: Boolean(input.paper), ...page });
|
|
3274
|
+
}
|
|
3275
|
+
};
|
|
3276
|
+
const candles = {
|
|
3277
|
+
name: "candles",
|
|
3278
|
+
title: "Candles",
|
|
3279
|
+
description: "OHLCV candles for one coin (up to 7 days), oldest first, as CSV.",
|
|
3280
|
+
inputSchema: candlesInputSchema,
|
|
3281
|
+
async execute(input) {
|
|
3282
|
+
const r = await withSession(
|
|
3283
|
+
auth,
|
|
3284
|
+
READ,
|
|
3285
|
+
(token) => dataBridge.getMarketCandles(token, { symbol: input.coin, interval: input.interval, limit: input.limit ?? 100 })
|
|
3286
|
+
);
|
|
3287
|
+
return r.ok ? text(renderCandles(input.coin, input.interval, r.value), r.value) : pendingResult(r.pending);
|
|
3288
|
+
}
|
|
3289
|
+
};
|
|
3290
|
+
async function positionAfter(coinName, isPaper) {
|
|
3291
|
+
const p = await withSession(
|
|
3292
|
+
auth,
|
|
3293
|
+
[AGENT_SCOPES.ACCOUNT_READ],
|
|
3294
|
+
(token) => isPaper ? dataBridge.getPaperPortfolio(token) : dataBridge.getPortfolio(token, { provider: "hyperliquid" })
|
|
3295
|
+
).catch(() => null);
|
|
3296
|
+
if (!p?.ok || !Array.isArray(p.value?.positions)) return { text: "Call account to see the position.", position: null };
|
|
3297
|
+
const position = p.value.positions.find((pos) => sameCoin(pos, coinName)) ?? null;
|
|
3298
|
+
return { text: position ? `Now: ${positionLine(position)}.` : `Now: no ${coinName} position.`, position };
|
|
3299
|
+
}
|
|
3300
|
+
async function paperTrade(input) {
|
|
3301
|
+
if (input.limit_px) throw new Error("The practice account fills market orders only: leave out limit_px");
|
|
3302
|
+
if (input.action === "close") {
|
|
3303
|
+
const r2 = await withSession(
|
|
3304
|
+
auth,
|
|
3305
|
+
[AGENT_SCOPES.ORDERS_WRITE],
|
|
3306
|
+
(token) => dataBridge.closePaperPosition(token, { symbol: input.coin, ...input.fraction !== void 0 && input.fraction < 1 ? { fraction: input.fraction } : {} })
|
|
3307
|
+
);
|
|
3308
|
+
if (!r2.ok) return pendingResult(r2.pending);
|
|
3309
|
+
if (!r2.value.length) return text(`Practice: no open ${input.coin} position, nothing closed.`, { paper: true, fills: [] });
|
|
3310
|
+
const after2 = await positionAfter(input.coin, true);
|
|
3311
|
+
const pnl = r2.value.reduce((sum, f) => sum + f.pnl - f.fee, 0);
|
|
3312
|
+
return text(`Practice: closed ${input.fraction && input.fraction < 1 ? `${Math.round(input.fraction * 100)}% of ` : ""}${input.coin} @ ${price(r2.value[r2.value.length - 1].price)}, ${pnl >= 0 ? "+" : "-"}$${Math.abs(pnl).toFixed(2)} after fees. ${after2.text}`, {
|
|
3313
|
+
paper: true,
|
|
3314
|
+
fills: r2.value,
|
|
3315
|
+
after: after2.position
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
if (!input.side || !input.margin_usd) throw new Error(`${input.action} needs side and margin_usd`);
|
|
3319
|
+
const protection = await protectionFor(input);
|
|
3320
|
+
if ("pending" in protection) return pendingResult(protection.pending);
|
|
3321
|
+
const r = await withSession(
|
|
3322
|
+
auth,
|
|
3323
|
+
[AGENT_SCOPES.ORDERS_WRITE],
|
|
3324
|
+
(token) => dataBridge.submitPaperIntent(token, {
|
|
3325
|
+
provider: "hyperliquid",
|
|
3326
|
+
symbol: input.coin,
|
|
3327
|
+
intentType: input.action === "open" ? "OPEN" : input.action === "add" ? "DCA" : "REVERSE",
|
|
3328
|
+
direction: input.side === "long" ? "LONG" : "SHORT",
|
|
3329
|
+
size: { mode: "MARGIN_USD", value: input.margin_usd },
|
|
3330
|
+
execution: { style: "MARKET" },
|
|
3331
|
+
...input.leverage ? { leverageOverride: input.leverage } : {},
|
|
3332
|
+
...protection.value ? { protection: protection.value } : {}
|
|
3333
|
+
})
|
|
3334
|
+
);
|
|
3335
|
+
if (!r.ok) return pendingResult(r.pending);
|
|
3336
|
+
const after = await positionAfter(input.coin, true);
|
|
3337
|
+
const filled = r.value.filter((f) => f.side !== null);
|
|
3338
|
+
const parts = [
|
|
3339
|
+
filled.length ? `Practice: ${input.action} ${input.side} ${input.coin} filled @ ${price(filled[filled.length - 1].price)}, $${input.margin_usd} margin${input.leverage ? ` at ${input.leverage}x` : ""}.` : `Practice: ${input.action} ${input.side} ${input.coin} sent, no fill reported.`
|
|
3340
|
+
];
|
|
3341
|
+
if (protection.value?.stopLoss) parts.push(`Stop ${price(protection.value.stopLoss.triggerPrice)}.`);
|
|
3342
|
+
if (protection.value?.takeProfit) parts.push(`Take profit ${price(protection.value.takeProfit.triggerPrice)}.`);
|
|
3343
|
+
parts.push(after.text);
|
|
3344
|
+
return text(parts.join(" "), { paper: true, fills: r.value, after: after.position });
|
|
3345
|
+
}
|
|
3346
|
+
async function protectionFor(input) {
|
|
3347
|
+
if (input.side === "short" && (input.tp_pct ?? 0) >= 100) throw new Error("A short can gain at most 100%: tp_pct must be under 100");
|
|
3348
|
+
if (!input.sl_pct && !input.tp_pct) return { value: void 0 };
|
|
3349
|
+
let ref = input.limit_px ?? null;
|
|
3350
|
+
if (ref === null) {
|
|
3351
|
+
const m = await withSession(auth, READ, (token) => dataBridge.getAgentMarket(token, { coins: [input.coin], limit: 1 }));
|
|
3352
|
+
if (!m.ok) return { pending: m.pending };
|
|
3353
|
+
ref = m.value.rows[0]?.px ?? null;
|
|
3354
|
+
}
|
|
3355
|
+
if (ref === null) throw new Error(`No price for ${input.coin} to place the stop from; trade without sl_pct/tp_pct, then use protect`);
|
|
3356
|
+
const long = input.side === "long";
|
|
3357
|
+
const at = (p, away) => Number((p * (1 + away / 100)).toPrecision(6));
|
|
3358
|
+
return {
|
|
3359
|
+
value: {
|
|
3360
|
+
...input.sl_pct ? { stopLoss: { kind: "MARKET", triggerPrice: at(ref, long ? -input.sl_pct : input.sl_pct) } } : {},
|
|
3361
|
+
...input.tp_pct ? { takeProfit: { kind: "MARKET", triggerPrice: at(ref, long ? input.tp_pct : -input.tp_pct) } } : {}
|
|
3362
|
+
}
|
|
3363
|
+
};
|
|
3364
|
+
}
|
|
3365
|
+
return { smBoard, smMoves, smCoin, market, account, trade, protect, trades, candles };
|
|
3366
|
+
}
|
|
3367
|
+
function leanToolsFor(tools, profile) {
|
|
3368
|
+
const reads = [tools.smBoard, tools.smMoves, tools.smCoin, tools.market];
|
|
3369
|
+
const list = profile === "signals" ? [...reads, tools.account, tools.trades, tools.candles] : [...reads, tools.account, tools.trades, tools.trade, tools.protect, tools.candles];
|
|
3370
|
+
return list;
|
|
3371
|
+
}
|
|
3372
|
+
|
|
2162
3373
|
// src/tools.ts
|
|
2163
|
-
var
|
|
2164
|
-
var pollLoginInputSchema =
|
|
2165
|
-
/**
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
3374
|
+
var import_zod7 = require("zod");
|
|
3375
|
+
var pollLoginInputSchema = import_zod7.z.object({
|
|
3376
|
+
/**
|
|
3377
|
+
* Optional, kept for older agents: the login in progress is always the one polled, and any other code (one that isn't
|
|
3378
|
+
* its user code) is refused.
|
|
3379
|
+
*/
|
|
3380
|
+
deviceCode: import_zod7.z.string().min(1).optional()
|
|
3381
|
+
});
|
|
3382
|
+
var startLoginOutputSchema = import_zod7.z.object({
|
|
3383
|
+
status: import_zod7.z.literal("pending"),
|
|
3384
|
+
userCode: import_zod7.z.string(),
|
|
3385
|
+
verificationUri: import_zod7.z.string().url(),
|
|
3386
|
+
verificationUriComplete: import_zod7.z.string().url(),
|
|
3387
|
+
expiresInSeconds: import_zod7.z.number().int().positive(),
|
|
3388
|
+
pollIntervalSeconds: import_zod7.z.number().int().positive(),
|
|
3389
|
+
requestedScope: import_zod7.z.array(agentScopeSchema).min(1),
|
|
3390
|
+
startedAt: import_zod7.z.string().datetime()
|
|
3391
|
+
});
|
|
3392
|
+
var ensureLoginInputSchema = import_zod7.z.object({
|
|
3393
|
+
clientName: import_zod7.z.string().min(1).max(64).optional(),
|
|
3394
|
+
scope: import_zod7.z.array(agentScopeSchema).min(1).optional(),
|
|
2182
3395
|
constraints: agentConstraintsSchema.optional()
|
|
2183
3396
|
});
|
|
2184
|
-
var providerSelectionInputSchema =
|
|
3397
|
+
var providerSelectionInputSchema = import_zod7.z.object({
|
|
2185
3398
|
provider: providerSchema.optional()
|
|
2186
3399
|
});
|
|
2187
|
-
var providerSetInputSchema =
|
|
3400
|
+
var providerSetInputSchema = import_zod7.z.object({
|
|
2188
3401
|
provider: providerSchema
|
|
2189
3402
|
});
|
|
2190
|
-
var activeProviderSchema =
|
|
2191
|
-
configured:
|
|
3403
|
+
var activeProviderSchema = import_zod7.z.object({
|
|
3404
|
+
configured: import_zod7.z.boolean(),
|
|
2192
3405
|
provider: providerSchema.nullable()
|
|
2193
3406
|
});
|
|
2194
|
-
var ensureLoginOutputSchema =
|
|
3407
|
+
var ensureLoginOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
2195
3408
|
agentSessionViewSchema.extend({
|
|
2196
|
-
status:
|
|
2197
|
-
connected:
|
|
3409
|
+
status: import_zod7.z.literal("connected"),
|
|
3410
|
+
connected: import_zod7.z.literal(true)
|
|
2198
3411
|
}),
|
|
2199
3412
|
startLoginOutputSchema.extend({
|
|
2200
|
-
status:
|
|
2201
|
-
connected:
|
|
2202
|
-
message:
|
|
3413
|
+
status: import_zod7.z.literal("pending"),
|
|
3414
|
+
connected: import_zod7.z.literal(false),
|
|
3415
|
+
message: import_zod7.z.string()
|
|
2203
3416
|
})
|
|
2204
3417
|
]);
|
|
2205
|
-
var pollLoginOutputSchema =
|
|
3418
|
+
var pollLoginOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
2206
3419
|
agentSessionViewSchema.extend({
|
|
2207
|
-
status:
|
|
2208
|
-
connected:
|
|
3420
|
+
status: import_zod7.z.literal("connected"),
|
|
3421
|
+
connected: import_zod7.z.literal(true)
|
|
2209
3422
|
}),
|
|
2210
|
-
|
|
2211
|
-
status:
|
|
2212
|
-
connected:
|
|
2213
|
-
message:
|
|
3423
|
+
import_zod7.z.object({
|
|
3424
|
+
status: import_zod7.z.enum(["pending", "rejected", "expired", "failed"]),
|
|
3425
|
+
connected: import_zod7.z.literal(false),
|
|
3426
|
+
message: import_zod7.z.string()
|
|
2214
3427
|
})
|
|
2215
3428
|
]);
|
|
2216
|
-
var walletStateToolOutputSchema =
|
|
2217
|
-
|
|
2218
|
-
status:
|
|
2219
|
-
connected:
|
|
3429
|
+
var walletStateToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3430
|
+
import_zod7.z.object({
|
|
3431
|
+
status: import_zod7.z.literal("connected"),
|
|
3432
|
+
connected: import_zod7.z.literal(true),
|
|
2220
3433
|
walletState: walletStateSchema
|
|
2221
3434
|
}),
|
|
2222
3435
|
startLoginOutputSchema.extend({
|
|
2223
|
-
status:
|
|
2224
|
-
connected:
|
|
2225
|
-
message:
|
|
3436
|
+
status: import_zod7.z.literal("pending"),
|
|
3437
|
+
connected: import_zod7.z.literal(false),
|
|
3438
|
+
message: import_zod7.z.string()
|
|
2226
3439
|
})
|
|
2227
3440
|
]);
|
|
2228
|
-
var portfolioToolOutputSchema =
|
|
2229
|
-
|
|
2230
|
-
status:
|
|
2231
|
-
connected:
|
|
3441
|
+
var portfolioToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3442
|
+
import_zod7.z.object({
|
|
3443
|
+
status: import_zod7.z.literal("connected"),
|
|
3444
|
+
connected: import_zod7.z.literal(true),
|
|
2232
3445
|
portfolio: portfolioSnapshotSchema
|
|
2233
3446
|
}),
|
|
2234
3447
|
startLoginOutputSchema.extend({
|
|
2235
|
-
status:
|
|
2236
|
-
connected:
|
|
2237
|
-
message:
|
|
3448
|
+
status: import_zod7.z.literal("pending"),
|
|
3449
|
+
connected: import_zod7.z.literal(false),
|
|
3450
|
+
message: import_zod7.z.string()
|
|
2238
3451
|
})
|
|
2239
3452
|
]);
|
|
2240
|
-
var openPositionsToolOutputSchema =
|
|
2241
|
-
|
|
2242
|
-
status:
|
|
2243
|
-
connected:
|
|
3453
|
+
var openPositionsToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3454
|
+
import_zod7.z.object({
|
|
3455
|
+
status: import_zod7.z.literal("connected"),
|
|
3456
|
+
connected: import_zod7.z.literal(true),
|
|
2244
3457
|
openPositions: openPositionsSnapshotSchema
|
|
2245
3458
|
}),
|
|
2246
3459
|
startLoginOutputSchema.extend({
|
|
2247
|
-
status:
|
|
2248
|
-
connected:
|
|
2249
|
-
message:
|
|
3460
|
+
status: import_zod7.z.literal("pending"),
|
|
3461
|
+
connected: import_zod7.z.literal(false),
|
|
3462
|
+
message: import_zod7.z.string()
|
|
2250
3463
|
})
|
|
2251
3464
|
]);
|
|
2252
|
-
var ledgerFillsToolOutputSchema =
|
|
2253
|
-
|
|
2254
|
-
status:
|
|
2255
|
-
connected:
|
|
3465
|
+
var ledgerFillsToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3466
|
+
import_zod7.z.object({
|
|
3467
|
+
status: import_zod7.z.literal("connected"),
|
|
3468
|
+
connected: import_zod7.z.literal(true),
|
|
2256
3469
|
fills: ledgerFillsPageSchema
|
|
2257
3470
|
}),
|
|
2258
3471
|
startLoginOutputSchema.extend({
|
|
2259
|
-
status:
|
|
2260
|
-
connected:
|
|
2261
|
-
message:
|
|
3472
|
+
status: import_zod7.z.literal("pending"),
|
|
3473
|
+
connected: import_zod7.z.literal(false),
|
|
3474
|
+
message: import_zod7.z.string()
|
|
2262
3475
|
})
|
|
2263
3476
|
]);
|
|
2264
|
-
var ledgerSummaryToolOutputSchema =
|
|
2265
|
-
|
|
2266
|
-
status:
|
|
2267
|
-
connected:
|
|
3477
|
+
var ledgerSummaryToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3478
|
+
import_zod7.z.object({
|
|
3479
|
+
status: import_zod7.z.literal("connected"),
|
|
3480
|
+
connected: import_zod7.z.literal(true),
|
|
2268
3481
|
summary: ledgerSummarySchema
|
|
2269
3482
|
}),
|
|
2270
3483
|
startLoginOutputSchema.extend({
|
|
2271
|
-
status:
|
|
2272
|
-
connected:
|
|
2273
|
-
message:
|
|
3484
|
+
status: import_zod7.z.literal("pending"),
|
|
3485
|
+
connected: import_zod7.z.literal(false),
|
|
3486
|
+
message: import_zod7.z.string()
|
|
2274
3487
|
})
|
|
2275
3488
|
]);
|
|
2276
|
-
var marketSnapshotToolOutputSchema =
|
|
2277
|
-
|
|
2278
|
-
status:
|
|
2279
|
-
connected:
|
|
3489
|
+
var marketSnapshotToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3490
|
+
import_zod7.z.object({
|
|
3491
|
+
status: import_zod7.z.literal("connected"),
|
|
3492
|
+
connected: import_zod7.z.literal(true),
|
|
2280
3493
|
snapshot: marketSnapshotSchema
|
|
2281
3494
|
}),
|
|
2282
3495
|
startLoginOutputSchema.extend({
|
|
2283
|
-
status:
|
|
2284
|
-
connected:
|
|
2285
|
-
message:
|
|
3496
|
+
status: import_zod7.z.literal("pending"),
|
|
3497
|
+
connected: import_zod7.z.literal(false),
|
|
3498
|
+
message: import_zod7.z.string()
|
|
2286
3499
|
})
|
|
2287
3500
|
]);
|
|
2288
|
-
var marketSnapshotChunkToolOutputSchema =
|
|
2289
|
-
|
|
2290
|
-
status:
|
|
2291
|
-
connected:
|
|
3501
|
+
var marketSnapshotChunkToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3502
|
+
import_zod7.z.object({
|
|
3503
|
+
status: import_zod7.z.literal("connected"),
|
|
3504
|
+
connected: import_zod7.z.literal(true),
|
|
2292
3505
|
snapshotChunk: marketSnapshotChunkSchema
|
|
2293
3506
|
}),
|
|
2294
3507
|
startLoginOutputSchema.extend({
|
|
2295
|
-
status:
|
|
2296
|
-
connected:
|
|
2297
|
-
message:
|
|
3508
|
+
status: import_zod7.z.literal("pending"),
|
|
3509
|
+
connected: import_zod7.z.literal(false),
|
|
3510
|
+
message: import_zod7.z.string()
|
|
2298
3511
|
})
|
|
2299
3512
|
]);
|
|
2300
|
-
var marketSymbolDetailToolOutputSchema =
|
|
2301
|
-
|
|
2302
|
-
status:
|
|
2303
|
-
connected:
|
|
3513
|
+
var marketSymbolDetailToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3514
|
+
import_zod7.z.object({
|
|
3515
|
+
status: import_zod7.z.literal("connected"),
|
|
3516
|
+
connected: import_zod7.z.literal(true),
|
|
2304
3517
|
detail: marketSymbolDetailSchema
|
|
2305
3518
|
}),
|
|
2306
3519
|
startLoginOutputSchema.extend({
|
|
2307
|
-
status:
|
|
2308
|
-
connected:
|
|
2309
|
-
message:
|
|
3520
|
+
status: import_zod7.z.literal("pending"),
|
|
3521
|
+
connected: import_zod7.z.literal(false),
|
|
3522
|
+
message: import_zod7.z.string()
|
|
2310
3523
|
})
|
|
2311
3524
|
]);
|
|
2312
3525
|
var tradeActionInputSchema = submitTradeIntentInputSchema.omit({
|
|
2313
3526
|
intentType: true
|
|
2314
3527
|
});
|
|
2315
|
-
var tradeActionToolOutputSchema =
|
|
2316
|
-
|
|
2317
|
-
status:
|
|
2318
|
-
connected:
|
|
3528
|
+
var tradeActionToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3529
|
+
import_zod7.z.object({
|
|
3530
|
+
status: import_zod7.z.literal("connected"),
|
|
3531
|
+
connected: import_zod7.z.literal(true),
|
|
2319
3532
|
submission: submitTradeIntentResultSchema
|
|
2320
3533
|
}),
|
|
2321
3534
|
startLoginOutputSchema.extend({
|
|
2322
|
-
status:
|
|
2323
|
-
connected:
|
|
2324
|
-
message:
|
|
3535
|
+
status: import_zod7.z.literal("pending"),
|
|
3536
|
+
connected: import_zod7.z.literal(false),
|
|
3537
|
+
message: import_zod7.z.string()
|
|
2325
3538
|
})
|
|
2326
3539
|
]);
|
|
2327
|
-
var closeTradeToolOutputSchema =
|
|
2328
|
-
|
|
2329
|
-
status:
|
|
2330
|
-
connected:
|
|
3540
|
+
var closeTradeToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3541
|
+
import_zod7.z.object({
|
|
3542
|
+
status: import_zod7.z.literal("connected"),
|
|
3543
|
+
connected: import_zod7.z.literal(true),
|
|
2331
3544
|
closure: closePositionResultSchema
|
|
2332
3545
|
}),
|
|
2333
3546
|
startLoginOutputSchema.extend({
|
|
2334
|
-
status:
|
|
2335
|
-
connected:
|
|
2336
|
-
message:
|
|
3547
|
+
status: import_zod7.z.literal("pending"),
|
|
3548
|
+
connected: import_zod7.z.literal(false),
|
|
3549
|
+
message: import_zod7.z.string()
|
|
2337
3550
|
})
|
|
2338
3551
|
]);
|
|
2339
|
-
var setTpSlToolOutputSchema =
|
|
2340
|
-
|
|
2341
|
-
status:
|
|
2342
|
-
connected:
|
|
3552
|
+
var setTpSlToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3553
|
+
import_zod7.z.object({
|
|
3554
|
+
status: import_zod7.z.literal("connected"),
|
|
3555
|
+
connected: import_zod7.z.literal(true),
|
|
2343
3556
|
protection: setTpSlResultSchema
|
|
2344
3557
|
}),
|
|
2345
3558
|
startLoginOutputSchema.extend({
|
|
2346
|
-
status:
|
|
2347
|
-
connected:
|
|
2348
|
-
message:
|
|
3559
|
+
status: import_zod7.z.literal("pending"),
|
|
3560
|
+
connected: import_zod7.z.literal(false),
|
|
3561
|
+
message: import_zod7.z.string()
|
|
2349
3562
|
})
|
|
2350
3563
|
]);
|
|
2351
|
-
var deleteTpSlToolOutputSchema =
|
|
2352
|
-
|
|
2353
|
-
status:
|
|
2354
|
-
connected:
|
|
3564
|
+
var deleteTpSlToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3565
|
+
import_zod7.z.object({
|
|
3566
|
+
status: import_zod7.z.literal("connected"),
|
|
3567
|
+
connected: import_zod7.z.literal(true),
|
|
2355
3568
|
deletion: deleteTpSlResultSchema
|
|
2356
3569
|
}),
|
|
2357
3570
|
startLoginOutputSchema.extend({
|
|
2358
|
-
status:
|
|
2359
|
-
connected:
|
|
2360
|
-
message:
|
|
3571
|
+
status: import_zod7.z.literal("pending"),
|
|
3572
|
+
connected: import_zod7.z.literal(false),
|
|
3573
|
+
message: import_zod7.z.string()
|
|
2361
3574
|
})
|
|
2362
3575
|
]);
|
|
2363
|
-
var openOrdersToolOutputSchema =
|
|
2364
|
-
|
|
2365
|
-
status:
|
|
2366
|
-
connected:
|
|
2367
|
-
orders:
|
|
3576
|
+
var openOrdersToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3577
|
+
import_zod7.z.object({
|
|
3578
|
+
status: import_zod7.z.literal("connected"),
|
|
3579
|
+
connected: import_zod7.z.literal(true),
|
|
3580
|
+
orders: import_zod7.z.array(openOrderSchema)
|
|
2368
3581
|
}),
|
|
2369
3582
|
startLoginOutputSchema.extend({
|
|
2370
|
-
status:
|
|
2371
|
-
connected:
|
|
2372
|
-
message:
|
|
3583
|
+
status: import_zod7.z.literal("pending"),
|
|
3584
|
+
connected: import_zod7.z.literal(false),
|
|
3585
|
+
message: import_zod7.z.string()
|
|
2373
3586
|
})
|
|
2374
3587
|
]);
|
|
2375
|
-
var cancelOrderToolOutputSchema =
|
|
2376
|
-
|
|
2377
|
-
status:
|
|
2378
|
-
connected:
|
|
3588
|
+
var cancelOrderToolOutputSchema = import_zod7.z.discriminatedUnion("status", [
|
|
3589
|
+
import_zod7.z.object({
|
|
3590
|
+
status: import_zod7.z.literal("connected"),
|
|
3591
|
+
connected: import_zod7.z.literal(true),
|
|
2379
3592
|
cancellation: cancelOrderResultSchema
|
|
2380
3593
|
}),
|
|
2381
3594
|
startLoginOutputSchema.extend({
|
|
2382
|
-
status:
|
|
2383
|
-
connected:
|
|
2384
|
-
message:
|
|
3595
|
+
status: import_zod7.z.literal("pending"),
|
|
3596
|
+
connected: import_zod7.z.literal(false),
|
|
3597
|
+
message: import_zod7.z.string()
|
|
2385
3598
|
})
|
|
2386
3599
|
]);
|
|
2387
|
-
var documentPathInputSchema =
|
|
2388
|
-
path:
|
|
2389
|
-
});
|
|
2390
|
-
var docsIndexSchema =
|
|
2391
|
-
root:
|
|
2392
|
-
documents:
|
|
2393
|
-
|
|
2394
|
-
path:
|
|
2395
|
-
section:
|
|
2396
|
-
title:
|
|
2397
|
-
bytes:
|
|
3600
|
+
var documentPathInputSchema = import_zod7.z.object({
|
|
3601
|
+
path: import_zod7.z.string().min(1)
|
|
3602
|
+
});
|
|
3603
|
+
var docsIndexSchema = import_zod7.z.object({
|
|
3604
|
+
root: import_zod7.z.literal("docs"),
|
|
3605
|
+
documents: import_zod7.z.array(
|
|
3606
|
+
import_zod7.z.object({
|
|
3607
|
+
path: import_zod7.z.string(),
|
|
3608
|
+
section: import_zod7.z.string(),
|
|
3609
|
+
title: import_zod7.z.string(),
|
|
3610
|
+
bytes: import_zod7.z.number().int().nonnegative()
|
|
2398
3611
|
})
|
|
2399
3612
|
)
|
|
2400
3613
|
});
|
|
2401
|
-
var documentSchema =
|
|
2402
|
-
path:
|
|
2403
|
-
section:
|
|
2404
|
-
title:
|
|
2405
|
-
body:
|
|
2406
|
-
bytes:
|
|
3614
|
+
var documentSchema = import_zod7.z.object({
|
|
3615
|
+
path: import_zod7.z.string(),
|
|
3616
|
+
section: import_zod7.z.string(),
|
|
3617
|
+
title: import_zod7.z.string(),
|
|
3618
|
+
body: import_zod7.z.string(),
|
|
3619
|
+
bytes: import_zod7.z.number().int().nonnegative()
|
|
2407
3620
|
});
|
|
2408
3621
|
function createHeliosDocsToolDefinitions(service) {
|
|
2409
3622
|
return [
|
|
@@ -2411,7 +3624,7 @@ function createHeliosDocsToolDefinitions(service) {
|
|
|
2411
3624
|
name: "helios_docs_get_index",
|
|
2412
3625
|
title: "Get Helios Docs Index",
|
|
2413
3626
|
description: "Lists the package-local Helios MCP and indicator .txt documentation available to the agent.",
|
|
2414
|
-
inputSchema:
|
|
3627
|
+
inputSchema: import_zod7.z.object({}),
|
|
2415
3628
|
async execute() {
|
|
2416
3629
|
const result = docsIndexSchema.parse(await service.getIndex());
|
|
2417
3630
|
return {
|
|
@@ -2494,7 +3707,7 @@ function createHeliosAuthToolDefinitions(service) {
|
|
|
2494
3707
|
{
|
|
2495
3708
|
name: "helios_auth_poll_login",
|
|
2496
3709
|
title: "Poll Helios Login",
|
|
2497
|
-
description: "Checks whether the Helios login was approved and finishes connecting.
|
|
3710
|
+
description: "Checks whether the Helios login in progress was approved and finishes connecting. No code is needed.",
|
|
2498
3711
|
inputSchema: pollLoginInputSchema,
|
|
2499
3712
|
async execute(input) {
|
|
2500
3713
|
const result = pollLoginOutputSchema.parse(await service.pollLogin(input.deviceCode));
|
|
@@ -2513,7 +3726,7 @@ function createHeliosAuthToolDefinitions(service) {
|
|
|
2513
3726
|
name: "helios_auth_whoami",
|
|
2514
3727
|
title: "Helios Login Status",
|
|
2515
3728
|
description: "Returns the delegated Helios session state currently stored by the MCP server.",
|
|
2516
|
-
inputSchema:
|
|
3729
|
+
inputSchema: import_zod7.z.object({}),
|
|
2517
3730
|
async execute() {
|
|
2518
3731
|
const result = agentSessionViewSchema.parse(await service.whoami());
|
|
2519
3732
|
return {
|
|
@@ -2535,7 +3748,7 @@ function createHeliosProviderToolDefinitions(service) {
|
|
|
2535
3748
|
name: "helios_provider_get_active",
|
|
2536
3749
|
title: "Get Helios Active Provider",
|
|
2537
3750
|
description: "Returns the provider currently pinned for this MCP session, if any.",
|
|
2538
|
-
inputSchema:
|
|
3751
|
+
inputSchema: import_zod7.z.object({}),
|
|
2539
3752
|
async execute() {
|
|
2540
3753
|
const result = activeProviderSchema.parse(await service.getActiveProvider());
|
|
2541
3754
|
return {
|
|
@@ -2575,7 +3788,7 @@ function createHeliosAccountToolDefinitions(service) {
|
|
|
2575
3788
|
name: "helios_account_get_wallet_state",
|
|
2576
3789
|
title: "Get Helios Wallet State",
|
|
2577
3790
|
description: "Returns the current Hyperliquid wallet snapshot for the connected Helios account.",
|
|
2578
|
-
inputSchema:
|
|
3791
|
+
inputSchema: import_zod7.z.object({}),
|
|
2579
3792
|
async execute() {
|
|
2580
3793
|
const result = walletStateToolOutputSchema.parse(await service.getWalletState());
|
|
2581
3794
|
const structuredContent = result.status === "connected" ? result : {
|
|
@@ -2596,7 +3809,7 @@ function createHeliosAccountToolDefinitions(service) {
|
|
|
2596
3809
|
{
|
|
2597
3810
|
name: "helios_portfolio_get",
|
|
2598
3811
|
title: "Get Helios Portfolio",
|
|
2599
|
-
description: "Returns
|
|
3812
|
+
description: "Returns balances, open positions and their TP/SL (protectiveOrders, each with the id helios_positions_delete_tp_sl takes). Provider may be omitted if one is already pinned with helios_provider_set_active.",
|
|
2600
3813
|
inputSchema: providerSelectionInputSchema,
|
|
2601
3814
|
async execute(input) {
|
|
2602
3815
|
const result = portfolioToolOutputSchema.parse(await service.getPortfolio(input));
|
|
@@ -2623,7 +3836,7 @@ function createHeliosLedgerToolDefinitions(service) {
|
|
|
2623
3836
|
name: "helios_positions_get_open",
|
|
2624
3837
|
title: "Get Helios Open Positions",
|
|
2625
3838
|
description: "Returns the current open Hyperliquid positions for the connected Helios account.",
|
|
2626
|
-
inputSchema:
|
|
3839
|
+
inputSchema: import_zod7.z.object({}),
|
|
2627
3840
|
async execute() {
|
|
2628
3841
|
const result = openPositionsToolOutputSchema.parse(await service.getOpenPositions());
|
|
2629
3842
|
const structuredContent = result.status === "connected" ? result : {
|
|
@@ -2833,7 +4046,7 @@ function createHeliosTradingToolDefinitions(service) {
|
|
|
2833
4046
|
content: [
|
|
2834
4047
|
{
|
|
2835
4048
|
type: "text",
|
|
2836
|
-
text: result.status
|
|
4049
|
+
text: result.status !== "connected" ? result.message : result.closure.outcome === "no_position" ? `No open ${result.closure.symbol} position: nothing was closed and no order was sent.` : `Sent a market order closing the full ${result.closure.provider ?? input.provider ?? "hyperliquid"} ${result.closure.symbol} position. Check helios_positions_get_open to confirm.`
|
|
2837
4050
|
}
|
|
2838
4051
|
],
|
|
2839
4052
|
structuredContent: result.status === "connected" ? result : {
|
|
@@ -2867,7 +4080,7 @@ function createHeliosTradingToolDefinitions(service) {
|
|
|
2867
4080
|
{
|
|
2868
4081
|
name: "helios_positions_delete_tp_sl",
|
|
2869
4082
|
title: "Delete Helios TP/SL",
|
|
2870
|
-
description: "Deletes
|
|
4083
|
+
description: "Deletes a take-profit or stop-loss. orderId is the id from helios_portfolio_get protectiveOrders, or the Hyperliquid order id of the TP/SL. Provider may be omitted if one is already pinned with helios_provider_set_active.",
|
|
2871
4084
|
inputSchema: deleteTpSlInputSchema,
|
|
2872
4085
|
async execute(input) {
|
|
2873
4086
|
const result = deleteTpSlToolOutputSchema.parse(await service.deleteTpSl(input));
|
|
@@ -2889,7 +4102,7 @@ function createHeliosTradingToolDefinitions(service) {
|
|
|
2889
4102
|
name: "helios_orders_get_open",
|
|
2890
4103
|
title: "Get Helios Open Orders",
|
|
2891
4104
|
description: "Lists the currently open Hyperliquid orders for the connected account.",
|
|
2892
|
-
inputSchema:
|
|
4105
|
+
inputSchema: import_zod7.z.object({}),
|
|
2893
4106
|
async execute() {
|
|
2894
4107
|
const result = openOrdersToolOutputSchema.parse(await service.getOpenOrders());
|
|
2895
4108
|
return {
|
|
@@ -2950,6 +4163,8 @@ After approving access, call helios_auth_ensure_login (or helios_auth_poll_login
|
|
|
2950
4163
|
}
|
|
2951
4164
|
|
|
2952
4165
|
// src/server.ts
|
|
4166
|
+
var MCP_PROFILES = ["lean", "signals", "full"];
|
|
4167
|
+
var MAX_TOOL_INPUT_ELEMENTS = 1e4;
|
|
2953
4168
|
async function createHeliosAgentMcpServer(options) {
|
|
2954
4169
|
if (!options.identityServiceUrl) {
|
|
2955
4170
|
throw new Error("identityServiceUrl is required");
|
|
@@ -2958,11 +4173,23 @@ async function createHeliosAgentMcpServer(options) {
|
|
|
2958
4173
|
throw new Error("dataBridgeUrl is required");
|
|
2959
4174
|
}
|
|
2960
4175
|
const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js");
|
|
2961
|
-
const server = new McpServer(
|
|
2962
|
-
|
|
2963
|
-
|
|
4176
|
+
const server = new McpServer(
|
|
4177
|
+
{
|
|
4178
|
+
name: "helios",
|
|
4179
|
+
version: "0.4.0"
|
|
4180
|
+
},
|
|
4181
|
+
// Caps the array elements plus object keys one tools/call may carry (GHSA-46jr-4wx8-v453: without it, one request
|
|
4182
|
+
// under the body size limit could run the hosted server out of memory while validating). The largest real input is
|
|
4183
|
+
// a snapshot over every symbol and field, a few hundred entries, so this is far above normal use.
|
|
4184
|
+
{ maxToolInputElements: MAX_TOOL_INPUT_ELEMENTS }
|
|
4185
|
+
);
|
|
4186
|
+
if (!options.hosted) {
|
|
4187
|
+
assertHeliosBaseUrl("identityServiceUrl (HELIOS_IDENTITY_SERVICE_URL)", options.identityServiceUrl);
|
|
4188
|
+
assertHeliosBaseUrl("dataBridgeUrl (HELIOS_DATA_BRIDGE_URL)", options.dataBridgeUrl);
|
|
4189
|
+
}
|
|
4190
|
+
const sessionStore = options.sessionStore ?? new FileAgentSessionStore(options.sessionPath ?? getDefaultHeliosAgentMcpSessionPath(), {
|
|
4191
|
+
issuer: { identityServiceUrl: options.identityServiceUrl, dataBridgeUrl: options.dataBridgeUrl }
|
|
2964
4192
|
});
|
|
2965
|
-
const sessionStore = options.sessionStore ?? new FileAgentSessionStore(options.sessionPath ?? getDefaultHeliosAgentMcpSessionPath());
|
|
2966
4193
|
const client = new HeliosAgentAuthClient({
|
|
2967
4194
|
baseUrl: options.identityServiceUrl,
|
|
2968
4195
|
fetch: options.fetch
|
|
@@ -3001,39 +4228,29 @@ async function createHeliosAgentMcpServer(options) {
|
|
|
3001
4228
|
const docsService = new HeliosAgentMcpDocsService({
|
|
3002
4229
|
docsRoot: options.docsRoot ?? getDefaultHeliosAgentMcpDocsRoot()
|
|
3003
4230
|
});
|
|
3004
|
-
const
|
|
3005
|
-
const
|
|
3006
|
-
const
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
registerTool(server, marketTools[1]);
|
|
3028
|
-
registerTool(server, marketTools[2]);
|
|
3029
|
-
registerTool(server, tradingTools[0]);
|
|
3030
|
-
registerTool(server, tradingTools[1]);
|
|
3031
|
-
registerTool(server, tradingTools[2]);
|
|
3032
|
-
registerTool(server, tradingTools[3]);
|
|
3033
|
-
registerTool(server, tradingTools[4]);
|
|
3034
|
-
registerTool(server, tradingTools[5]);
|
|
3035
|
-
registerTool(server, tradingTools[6]);
|
|
3036
|
-
registerTool(server, tradingTools[7]);
|
|
4231
|
+
const profile = options.profile ?? "full";
|
|
4232
|
+
const lean = createLeanToolDefinitions({ auth: authService, dataBridge: dataBridgeClient });
|
|
4233
|
+
for (const tool of leanToolsFor(lean, profile)) {
|
|
4234
|
+
registerTool(server, { ...tool, textIsResult: true });
|
|
4235
|
+
}
|
|
4236
|
+
if (profile === "full") {
|
|
4237
|
+
const docsTools = createHeliosDocsToolDefinitions(docsService);
|
|
4238
|
+
const authTools = createHeliosAuthToolDefinitions(authService);
|
|
4239
|
+
const ledgerTools = createHeliosLedgerToolDefinitions(ledgerService);
|
|
4240
|
+
const marketTools = createHeliosMarketToolDefinitions(marketService);
|
|
4241
|
+
if (!options.hosted) {
|
|
4242
|
+
registerTool(server, authTools[0]);
|
|
4243
|
+
registerTool(server, authTools[1]);
|
|
4244
|
+
registerTool(server, authTools[2]);
|
|
4245
|
+
}
|
|
4246
|
+
registerTool(server, authTools[3]);
|
|
4247
|
+
registerTool(server, marketTools[0]);
|
|
4248
|
+
registerTool(server, marketTools[1]);
|
|
4249
|
+
registerTool(server, marketTools[2]);
|
|
4250
|
+
registerTool(server, ledgerTools[1]);
|
|
4251
|
+
registerTool(server, docsTools[0]);
|
|
4252
|
+
registerTool(server, docsTools[1]);
|
|
4253
|
+
}
|
|
3037
4254
|
return {
|
|
3038
4255
|
server,
|
|
3039
4256
|
authService,
|
|
@@ -3047,9 +4264,10 @@ async function createHeliosAgentMcpServer(options) {
|
|
|
3047
4264
|
};
|
|
3048
4265
|
}
|
|
3049
4266
|
var ACTS_ON_ACCOUNT = /^helios_(trading_|positions_set_tp_sl|positions_delete_tp_sl|orders_cancel)/;
|
|
4267
|
+
var LEAN_ACTS = /* @__PURE__ */ new Set(["trade", "protect"]);
|
|
3050
4268
|
var CHANGES_SESSION = /^helios_(auth_start_login|auth_ensure_login|auth_poll_login|provider_set_active)$/;
|
|
3051
4269
|
function toolAnnotations(name) {
|
|
3052
|
-
if (ACTS_ON_ACCOUNT.test(name)) return { readOnlyHint: false, destructiveHint: true, openWorldHint: true };
|
|
4270
|
+
if (ACTS_ON_ACCOUNT.test(name) || LEAN_ACTS.has(name)) return { readOnlyHint: false, destructiveHint: true, openWorldHint: true };
|
|
3053
4271
|
if (CHANGES_SESSION.test(name)) return { readOnlyHint: false, destructiveHint: false, openWorldHint: false };
|
|
3054
4272
|
return { readOnlyHint: true, destructiveHint: false, openWorldHint: true };
|
|
3055
4273
|
}
|
|
@@ -3073,7 +4291,7 @@ function registerTool(server, tool) {
|
|
|
3073
4291
|
async (input) => {
|
|
3074
4292
|
const result = await tool.execute(tool.inputSchema.parse(input));
|
|
3075
4293
|
return {
|
|
3076
|
-
content: withDataText(tool.name, result.content, result.structuredContent),
|
|
4294
|
+
content: tool.textIsResult ? result.content : withDataText(tool.name, result.content, result.structuredContent),
|
|
3077
4295
|
structuredContent: result.structuredContent
|
|
3078
4296
|
};
|
|
3079
4297
|
}
|
|
@@ -3081,6 +4299,8 @@ function registerTool(server, tool) {
|
|
|
3081
4299
|
}
|
|
3082
4300
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3083
4301
|
0 && (module.exports = {
|
|
4302
|
+
DEFAULT_HELIOS_BASE_URL,
|
|
4303
|
+
DEFAULT_HELIOS_SESSION_ISSUER,
|
|
3084
4304
|
DEFAULT_SNAPSHOT_FIELDS,
|
|
3085
4305
|
DEFAULT_SNAPSHOT_TOP,
|
|
3086
4306
|
FileAgentSessionStore,
|
|
@@ -3092,7 +4312,14 @@ function registerTool(server, tool) {
|
|
|
3092
4312
|
HeliosAgentMcpMarketService,
|
|
3093
4313
|
HeliosAgentMcpProviderService,
|
|
3094
4314
|
HeliosAgentMcpTradingService,
|
|
4315
|
+
MAX_TOOL_INPUT_ELEMENTS,
|
|
4316
|
+
MCP_PROFILES,
|
|
3095
4317
|
MemoryAgentMcpSessionStore,
|
|
4318
|
+
TRADE_KEY_WINDOW_SECONDS,
|
|
4319
|
+
accountInputSchema,
|
|
4320
|
+
ago,
|
|
4321
|
+
assertHeliosBaseUrl,
|
|
4322
|
+
candlesInputSchema,
|
|
3096
4323
|
compactSymbolDetail,
|
|
3097
4324
|
createHeliosAccountToolDefinitions,
|
|
3098
4325
|
createHeliosAgentMcpServer,
|
|
@@ -3102,13 +4329,39 @@ function registerTool(server, tool) {
|
|
|
3102
4329
|
createHeliosMarketToolDefinitions,
|
|
3103
4330
|
createHeliosProviderToolDefinitions,
|
|
3104
4331
|
createHeliosTradingToolDefinitions,
|
|
4332
|
+
createLeanToolDefinitions,
|
|
4333
|
+
entryText,
|
|
3105
4334
|
formatHeliosLoginPrompt,
|
|
3106
4335
|
getDefaultHeliosAgentMcpDocsRoot,
|
|
3107
4336
|
getDefaultHeliosAgentMcpSessionPath,
|
|
4337
|
+
isSameHeliosIssuer,
|
|
4338
|
+
leanToolsFor,
|
|
4339
|
+
marketInputSchema,
|
|
4340
|
+
moveText,
|
|
4341
|
+
pct,
|
|
3108
4342
|
pendingAgentLoginSchema,
|
|
4343
|
+
positionLine,
|
|
4344
|
+
price,
|
|
4345
|
+
protectInputSchema,
|
|
4346
|
+
renderAccount,
|
|
4347
|
+
renderBoard,
|
|
4348
|
+
renderCandles,
|
|
4349
|
+
renderCoin,
|
|
4350
|
+
renderMarket,
|
|
4351
|
+
renderMoves,
|
|
4352
|
+
renderTrades,
|
|
3109
4353
|
retryAfterUnauthorizedRefresh,
|
|
4354
|
+
smBoardInputSchema,
|
|
4355
|
+
smCoinInputSchema,
|
|
4356
|
+
smMovesInputSchema,
|
|
3110
4357
|
toolAnnotations,
|
|
3111
4358
|
topOfSnapshot,
|
|
4359
|
+
tradeInputSchema,
|
|
4360
|
+
tradeKeyFor,
|
|
4361
|
+
trader,
|
|
4362
|
+
tradesInputSchema,
|
|
4363
|
+
usd,
|
|
3112
4364
|
withDataText,
|
|
3113
|
-
withSnapshotDefaults
|
|
4365
|
+
withSnapshotDefaults,
|
|
4366
|
+
wrongSideTpSl
|
|
3114
4367
|
});
|