@pgnio/helios-mcp 0.2.0 → 0.2.2
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/dist/chunk-EIAUO3XE.mjs +2955 -0
- package/dist/chunk-NCF7ASJB.mjs +2986 -0
- package/dist/chunk-XE6HRID3.mjs +2992 -0
- package/dist/cli.js +40 -13
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +17 -4
- package/dist/index.d.ts +17 -4
- package/dist/index.js +44 -15
- package/dist/index.mjs +5 -3
- package/package.json +1 -1
|
@@ -0,0 +1,2955 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// ../agent/dist/index.mjs
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { z as z2 } from "zod";
|
|
8
|
+
import { z as z3 } from "zod";
|
|
9
|
+
var __defProp2 = Object.defineProperty;
|
|
10
|
+
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
12
|
+
var AGENT_SCOPES = {
|
|
13
|
+
ACCOUNT_READ: "account:read",
|
|
14
|
+
MARKET_READ: "market:read",
|
|
15
|
+
ORDERS_WRITE: "orders:write",
|
|
16
|
+
ORDERS_CANCEL: "orders:cancel"
|
|
17
|
+
};
|
|
18
|
+
var AGENT_SCOPE_VALUES = [
|
|
19
|
+
AGENT_SCOPES.ACCOUNT_READ,
|
|
20
|
+
AGENT_SCOPES.MARKET_READ,
|
|
21
|
+
AGENT_SCOPES.ORDERS_WRITE,
|
|
22
|
+
AGENT_SCOPES.ORDERS_CANCEL
|
|
23
|
+
];
|
|
24
|
+
var agentScopeSchema = z.enum(AGENT_SCOPE_VALUES);
|
|
25
|
+
var agentGrantStatusSchema = z.enum(["active", "revoked"]);
|
|
26
|
+
var agentDeviceRequestStatusSchema = z.enum(["pending", "approved", "rejected", "exchanged", "expired"]);
|
|
27
|
+
var agentConstraintsSchema = z.record(z.string(), z.unknown()).default({});
|
|
28
|
+
var agentGrantMetadataSchema = z.object({
|
|
29
|
+
id: z.string(),
|
|
30
|
+
userId: z.string(),
|
|
31
|
+
clientName: z.string(),
|
|
32
|
+
status: agentGrantStatusSchema,
|
|
33
|
+
scope: z.array(agentScopeSchema).min(1),
|
|
34
|
+
constraints: agentConstraintsSchema,
|
|
35
|
+
tokenVersion: z.number().int().nonnegative(),
|
|
36
|
+
createdAt: z.string(),
|
|
37
|
+
approvedAt: z.string().optional(),
|
|
38
|
+
lastUsedAt: z.string().optional(),
|
|
39
|
+
revokedAt: z.string().optional()
|
|
40
|
+
});
|
|
41
|
+
var agentDeviceStartInputSchema = z.object({
|
|
42
|
+
clientName: z.string().min(1).max(64),
|
|
43
|
+
scope: z.array(agentScopeSchema).min(1),
|
|
44
|
+
constraints: agentConstraintsSchema.optional()
|
|
45
|
+
});
|
|
46
|
+
var agentDeviceStartOutputSchema = z.object({
|
|
47
|
+
deviceCode: z.string(),
|
|
48
|
+
userCode: z.string(),
|
|
49
|
+
verificationUri: z.string().url(),
|
|
50
|
+
verificationUriComplete: z.string().url(),
|
|
51
|
+
expiresInSeconds: z.number().int().positive(),
|
|
52
|
+
pollIntervalSeconds: z.number().int().positive()
|
|
53
|
+
});
|
|
54
|
+
var agentDeviceTokenInputSchema = z.object({
|
|
55
|
+
deviceCode: z.string().min(1)
|
|
56
|
+
});
|
|
57
|
+
var agentDeviceRequestViewSchema = z.object({
|
|
58
|
+
userCode: z.string(),
|
|
59
|
+
clientName: z.string(),
|
|
60
|
+
scope: z.array(agentScopeSchema).min(1),
|
|
61
|
+
constraints: agentConstraintsSchema,
|
|
62
|
+
status: agentDeviceRequestStatusSchema,
|
|
63
|
+
expiresAt: z.string()
|
|
64
|
+
});
|
|
65
|
+
var agentTokenResponseSchema = z.object({
|
|
66
|
+
accessToken: z.string(),
|
|
67
|
+
refreshToken: z.string(),
|
|
68
|
+
tokenType: z.literal("Bearer"),
|
|
69
|
+
expiresInSeconds: z.number().int().positive(),
|
|
70
|
+
scope: z.array(agentScopeSchema).min(1),
|
|
71
|
+
ownerUserId: z.string(),
|
|
72
|
+
agentGrantId: z.string()
|
|
73
|
+
});
|
|
74
|
+
var agentRefreshInputSchema = z.object({
|
|
75
|
+
refreshToken: z.string().min(1)
|
|
76
|
+
});
|
|
77
|
+
var agentApprovalResponseSchema = z.object({
|
|
78
|
+
request: agentDeviceRequestViewSchema,
|
|
79
|
+
grant: agentGrantMetadataSchema
|
|
80
|
+
});
|
|
81
|
+
var agentSessionStateSchema = z.object({
|
|
82
|
+
status: z.enum(["pending", "authorized", "rejected", "revoked"]),
|
|
83
|
+
request: agentDeviceRequestViewSchema.optional(),
|
|
84
|
+
grant: agentGrantMetadataSchema.optional()
|
|
85
|
+
});
|
|
86
|
+
var providerSchema = z.enum(["hyperliquid"]);
|
|
87
|
+
var walletStateSchema = z.object({
|
|
88
|
+
provider: z.literal("hyperliquid"),
|
|
89
|
+
accountValue: z.number(),
|
|
90
|
+
availableBalance: z.number(),
|
|
91
|
+
withdrawable: z.number(),
|
|
92
|
+
totalMarginUsed: z.number(),
|
|
93
|
+
totalNotionalPosition: z.number(),
|
|
94
|
+
crossMarginAccountValue: z.number(),
|
|
95
|
+
isActivated: z.boolean(),
|
|
96
|
+
fetchedAt: z.string().datetime()
|
|
97
|
+
});
|
|
98
|
+
var openPositionSchema = z.object({
|
|
99
|
+
symbol: z.string(),
|
|
100
|
+
side: z.enum(["LONG", "SHORT"]),
|
|
101
|
+
size: z.number(),
|
|
102
|
+
entryPrice: z.number(),
|
|
103
|
+
markPrice: z.number(),
|
|
104
|
+
unrealizedPnl: z.number(),
|
|
105
|
+
margin: z.number(),
|
|
106
|
+
leverage: z.number(),
|
|
107
|
+
liquidationPrice: z.number().optional()
|
|
108
|
+
});
|
|
109
|
+
var openPositionsSnapshotSchema = z.object({
|
|
110
|
+
provider: z.literal("hyperliquid"),
|
|
111
|
+
fetchedAt: z.string().datetime(),
|
|
112
|
+
totalUnrealizedPnl: z.number(),
|
|
113
|
+
positions: z.array(openPositionSchema)
|
|
114
|
+
});
|
|
115
|
+
var protectiveOrderKindSchema = z.enum(["STOP_LOSS", "TAKE_PROFIT"]);
|
|
116
|
+
var protectiveOrderSideSchema = z.enum(["BUY", "SELL"]);
|
|
117
|
+
var protectiveOrderPositionSideSchema = z.enum(["BOTH", "LONG", "SHORT"]);
|
|
118
|
+
var protectiveOrderSchema = z.object({
|
|
119
|
+
id: z.string(),
|
|
120
|
+
binanceOrderId: z.number().int().nonnegative().optional(),
|
|
121
|
+
clientOrderId: z.string().optional(),
|
|
122
|
+
symbol: z.string(),
|
|
123
|
+
side: protectiveOrderSideSchema,
|
|
124
|
+
positionSide: protectiveOrderPositionSideSchema,
|
|
125
|
+
status: z.string(),
|
|
126
|
+
kind: protectiveOrderKindSchema,
|
|
127
|
+
groupId: z.string(),
|
|
128
|
+
stopPrice: z.string().optional(),
|
|
129
|
+
parentClientOrderId: z.string().optional(),
|
|
130
|
+
assetId: z.number().int().nonnegative().optional(),
|
|
131
|
+
createdAt: z.string().datetime()
|
|
132
|
+
});
|
|
133
|
+
var hyperliquidPortfolioSnapshotSchema = z.object({
|
|
134
|
+
provider: z.literal("hyperliquid"),
|
|
135
|
+
totalBalance: z.number(),
|
|
136
|
+
availableBalance: z.number(),
|
|
137
|
+
totalMargin: z.number(),
|
|
138
|
+
unrealizedPnl: z.number(),
|
|
139
|
+
positions: z.array(openPositionSchema),
|
|
140
|
+
protectiveOrders: z.array(protectiveOrderSchema)
|
|
141
|
+
});
|
|
142
|
+
var portfolioInputSchema = z.object({
|
|
143
|
+
provider: providerSchema
|
|
144
|
+
});
|
|
145
|
+
var portfolioSnapshotSchema = hyperliquidPortfolioSnapshotSchema;
|
|
146
|
+
var ledgerWindowSchema = z.enum(["24h", "7d", "30d"]);
|
|
147
|
+
var ledgerFillsInputSchema = z.object({
|
|
148
|
+
window: ledgerWindowSchema.optional(),
|
|
149
|
+
limit: z.number().int().positive().max(200).optional(),
|
|
150
|
+
before: z.number().int().nonnegative().optional()
|
|
151
|
+
});
|
|
152
|
+
var ledgerFillSchema = z.object({
|
|
153
|
+
coin: z.string(),
|
|
154
|
+
px: z.number(),
|
|
155
|
+
sz: z.number(),
|
|
156
|
+
side: z.enum(["B", "A"]),
|
|
157
|
+
time: z.number().int().nonnegative(),
|
|
158
|
+
dir: z.string(),
|
|
159
|
+
closedPnl: z.number(),
|
|
160
|
+
fee: z.number(),
|
|
161
|
+
crossed: z.boolean(),
|
|
162
|
+
oid: z.number().int().nonnegative(),
|
|
163
|
+
tid: z.number().int().nonnegative()
|
|
164
|
+
});
|
|
165
|
+
var ledgerFillsPageSchema = z.object({
|
|
166
|
+
window: ledgerWindowSchema,
|
|
167
|
+
limit: z.number().int().positive(),
|
|
168
|
+
before: z.number().int().nonnegative().optional(),
|
|
169
|
+
hasMore: z.boolean(),
|
|
170
|
+
nextBefore: z.number().int().nonnegative().optional(),
|
|
171
|
+
fetchedAt: z.string().datetime(),
|
|
172
|
+
fills: z.array(ledgerFillSchema)
|
|
173
|
+
});
|
|
174
|
+
var ledgerPeriodSummarySchema = z.object({
|
|
175
|
+
realizedPnl: z.number(),
|
|
176
|
+
totalFees: z.number(),
|
|
177
|
+
fillCount: z.number().int().nonnegative(),
|
|
178
|
+
closeCount: z.number().int().nonnegative(),
|
|
179
|
+
winCount: z.number().int().nonnegative(),
|
|
180
|
+
lossCount: z.number().int().nonnegative(),
|
|
181
|
+
averageWin: z.number(),
|
|
182
|
+
averageLoss: z.number()
|
|
183
|
+
});
|
|
184
|
+
var ledgerSummaryInputSchema = z.object({
|
|
185
|
+
window: ledgerWindowSchema.optional()
|
|
186
|
+
});
|
|
187
|
+
var ledgerSummarySchema = ledgerPeriodSummarySchema.extend({
|
|
188
|
+
window: ledgerWindowSchema,
|
|
189
|
+
generatedAt: z.string().datetime(),
|
|
190
|
+
periods: z.object({
|
|
191
|
+
"24h": ledgerPeriodSummarySchema,
|
|
192
|
+
"7d": ledgerPeriodSummarySchema
|
|
193
|
+
})
|
|
194
|
+
});
|
|
195
|
+
var marketSnapshotScopeSchema = z.enum(["selected", "all"]);
|
|
196
|
+
var marketSnapshotResolvedScopeSchema = z.enum(["selected", "all", "explicit"]);
|
|
197
|
+
var marketSnapshotValueSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
|
198
|
+
var marketSnapshotInputSchema = z.object({
|
|
199
|
+
scope: marketSnapshotScopeSchema.optional(),
|
|
200
|
+
symbols: z.array(z.string().min(1)).min(1).optional()
|
|
201
|
+
});
|
|
202
|
+
var marketSnapshotChunkInputSchema = marketSnapshotInputSchema.extend({
|
|
203
|
+
offset: z.number().int().nonnegative().optional(),
|
|
204
|
+
limit: z.number().int().positive().max(100).optional()
|
|
205
|
+
});
|
|
206
|
+
var marketFieldBlockSchema = z.object({
|
|
207
|
+
fieldIds: z.array(z.string()).min(1),
|
|
208
|
+
values: z.array(marketSnapshotValueSchema),
|
|
209
|
+
symbolLists: z.record(z.string(), z.array(z.string())).optional()
|
|
210
|
+
});
|
|
211
|
+
var marketMatrixSchema = z.object({
|
|
212
|
+
fieldIds: z.array(z.string()).min(1),
|
|
213
|
+
rows: z.array(z.array(marketSnapshotValueSchema))
|
|
214
|
+
});
|
|
215
|
+
var marketFieldSemanticsSchema = z.object({
|
|
216
|
+
nullMeans: z.literal("unavailable"),
|
|
217
|
+
percentScale: z.literal("already_percent"),
|
|
218
|
+
percentFields: z.array(z.string()).min(1),
|
|
219
|
+
changeMapPercentKeys: z.array(z.string()).min(1)
|
|
220
|
+
});
|
|
221
|
+
var marketSnapshotSchema = z.object({
|
|
222
|
+
schema: z.literal("helios.market_snapshot.v1"),
|
|
223
|
+
view: z.literal("market_overview"),
|
|
224
|
+
scope: marketSnapshotResolvedScopeSchema,
|
|
225
|
+
symbolCount: z.number().int().nonnegative(),
|
|
226
|
+
generatedAt: z.string().datetime(),
|
|
227
|
+
returnedSymbols: z.array(z.string()),
|
|
228
|
+
semantics: marketFieldSemanticsSchema,
|
|
229
|
+
market: marketFieldBlockSchema,
|
|
230
|
+
table: marketMatrixSchema,
|
|
231
|
+
legend: z.record(z.string(), z.union([z.string(), z.array(z.string())]))
|
|
232
|
+
});
|
|
233
|
+
var marketSnapshotChunkSchema = z.object({
|
|
234
|
+
schema: z.literal("helios.market_snapshot_chunk.v1"),
|
|
235
|
+
view: z.literal("market_overview_chunk"),
|
|
236
|
+
scope: marketSnapshotResolvedScopeSchema,
|
|
237
|
+
totalSymbolCount: z.number().int().nonnegative(),
|
|
238
|
+
generatedAt: z.string().datetime(),
|
|
239
|
+
offset: z.number().int().nonnegative(),
|
|
240
|
+
limit: z.number().int().positive(),
|
|
241
|
+
chunkSymbolCount: z.number().int().nonnegative(),
|
|
242
|
+
hasMore: z.boolean(),
|
|
243
|
+
nextOffset: z.number().int().nonnegative().optional(),
|
|
244
|
+
returnedSymbols: z.array(z.string()),
|
|
245
|
+
semantics: marketFieldSemanticsSchema,
|
|
246
|
+
market: marketFieldBlockSchema,
|
|
247
|
+
table: marketMatrixSchema,
|
|
248
|
+
legend: z.record(z.string(), z.union([z.string(), z.array(z.string())]))
|
|
249
|
+
});
|
|
250
|
+
var marketSymbolDetailInputSchema = z.object({
|
|
251
|
+
symbol: z.string().min(1)
|
|
252
|
+
});
|
|
253
|
+
var marketSymbolDetailSchema = z.object({
|
|
254
|
+
schema: z.literal("helios.market_symbol_detail.v1"),
|
|
255
|
+
generatedAt: z.string().datetime(),
|
|
256
|
+
symbol: z.string(),
|
|
257
|
+
semantics: marketFieldSemanticsSchema,
|
|
258
|
+
market: marketFieldBlockSchema,
|
|
259
|
+
row: z.record(z.string(), marketSnapshotValueSchema),
|
|
260
|
+
symbolCtx: z.record(z.string(), marketSnapshotValueSchema),
|
|
261
|
+
changeMap: z.record(z.string(), z.number().nullable()),
|
|
262
|
+
metrics: z.record(z.string(), z.unknown()).nullable(),
|
|
263
|
+
legend: z.record(z.string(), z.union([z.string(), z.array(z.string())]))
|
|
264
|
+
});
|
|
265
|
+
var tradingDirectionSchema = z.enum(["LONG", "SHORT"]);
|
|
266
|
+
var tradingExecutionStyleSchema = z.enum(["MARKET", "LIMIT_TAKER", "LIMIT_MAKER", "LIMIT_GTC"]);
|
|
267
|
+
var tradingSizeSchema = z.object({
|
|
268
|
+
mode: z.enum(["MARGIN_USD", "BASE_QUANTITY"]),
|
|
269
|
+
value: z.number().positive()
|
|
270
|
+
});
|
|
271
|
+
var tradingProtectionLegSchema = z.object({
|
|
272
|
+
kind: z.enum(["LIMIT", "MARKET"]),
|
|
273
|
+
triggerPrice: z.number().positive(),
|
|
274
|
+
limitPrice: z.number().positive().optional()
|
|
275
|
+
});
|
|
276
|
+
var tradingProtectionSchema = z.object({
|
|
277
|
+
takeProfit: tradingProtectionLegSchema.optional(),
|
|
278
|
+
stopLoss: tradingProtectionLegSchema.optional()
|
|
279
|
+
});
|
|
280
|
+
var tradeIntentTypeSchema = z.enum(["OPEN", "DCA", "REVERSE", "CLOSE"]);
|
|
281
|
+
var submitTradeIntentInputSchema = z.object({
|
|
282
|
+
provider: providerSchema.optional(),
|
|
283
|
+
symbol: z.string().regex(/^[a-zA-Z0-9]+(?:USDT)?$/),
|
|
284
|
+
intentType: tradeIntentTypeSchema,
|
|
285
|
+
direction: tradingDirectionSchema,
|
|
286
|
+
size: tradingSizeSchema,
|
|
287
|
+
execution: z.object({
|
|
288
|
+
style: tradingExecutionStyleSchema,
|
|
289
|
+
timeInForce: z.enum(["GTC", "IOC", "FOK", "GTX"]).optional(),
|
|
290
|
+
limitPrice: z.number().positive().optional()
|
|
291
|
+
}),
|
|
292
|
+
maxSlippageBps: z.number().int().nonnegative().max(1e3).optional(),
|
|
293
|
+
leverageOverride: z.number().positive().optional(),
|
|
294
|
+
marginModeOverride: z.enum(["ISOLATED", "CROSS"]).optional(),
|
|
295
|
+
reduceOnly: z.boolean().optional(),
|
|
296
|
+
protection: tradingProtectionSchema.optional(),
|
|
297
|
+
/** One per user action, reused on retries: the data-bridge never places a second order for the same key. */
|
|
298
|
+
idempotencyKey: z.string().regex(/^[A-Za-z0-9_-]{8,64}$/).optional()
|
|
299
|
+
});
|
|
300
|
+
var submitTradeIntentResultSchema = z.object({
|
|
301
|
+
status: z.literal("submitted"),
|
|
302
|
+
/** `replayed`: an earlier attempt of this action already placed the order; nothing new was sent. */
|
|
303
|
+
outcome: z.enum(["placed", "replayed"]).optional(),
|
|
304
|
+
clientOrderId: z.string().optional(),
|
|
305
|
+
orderStatus: z.string().optional(),
|
|
306
|
+
/** TP/SL legs Hyperliquid refused while the entry went through: the position is open without them. */
|
|
307
|
+
rejectedProtection: z.array(z.object({ kind: z.string(), reason: z.string() })).optional(),
|
|
308
|
+
provider: providerSchema.optional(),
|
|
309
|
+
symbol: z.string(),
|
|
310
|
+
intentType: tradeIntentTypeSchema,
|
|
311
|
+
direction: tradingDirectionSchema,
|
|
312
|
+
size: tradingSizeSchema,
|
|
313
|
+
execution: z.object({
|
|
314
|
+
style: tradingExecutionStyleSchema,
|
|
315
|
+
timeInForce: z.enum(["GTC", "IOC", "FOK", "GTX"]).optional(),
|
|
316
|
+
limitPrice: z.number().positive().optional()
|
|
317
|
+
}),
|
|
318
|
+
maxSlippageBps: z.number().int().nonnegative().max(1e3).optional(),
|
|
319
|
+
leverageOverride: z.number().positive().optional(),
|
|
320
|
+
marginModeOverride: z.enum(["ISOLATED", "CROSS"]).optional(),
|
|
321
|
+
reduceOnly: z.boolean(),
|
|
322
|
+
hasProtection: z.boolean()
|
|
323
|
+
});
|
|
324
|
+
var closePositionInputSchema = z.object({
|
|
325
|
+
provider: providerSchema.optional(),
|
|
326
|
+
symbol: z.string().regex(/^[a-zA-Z0-9]+(?:USDT)?$/),
|
|
327
|
+
maxSlippageBps: z.number().int().nonnegative().max(1e3).optional()
|
|
328
|
+
});
|
|
329
|
+
var closePositionResultSchema = z.object({
|
|
330
|
+
status: z.literal("submitted"),
|
|
331
|
+
provider: providerSchema.optional(),
|
|
332
|
+
symbol: z.string(),
|
|
333
|
+
mode: z.literal("FULL"),
|
|
334
|
+
maxSlippageBps: z.number().int().nonnegative().max(1e3).optional()
|
|
335
|
+
});
|
|
336
|
+
var setTpSlInputSchema = z.object({
|
|
337
|
+
provider: providerSchema.optional(),
|
|
338
|
+
symbol: z.string().regex(/^[a-zA-Z0-9]+(?:USDT)?$/),
|
|
339
|
+
applyTo: z.enum(["FULL_POSITION", "CUSTOM_QUANTITY"]),
|
|
340
|
+
quantity: z.number().positive().optional(),
|
|
341
|
+
parentClientOrderId: z.string(),
|
|
342
|
+
takeProfit: tradingProtectionLegSchema.optional(),
|
|
343
|
+
stopLoss: tradingProtectionLegSchema.optional()
|
|
344
|
+
});
|
|
345
|
+
var setTpSlResultSchema = z.object({
|
|
346
|
+
status: z.literal("submitted"),
|
|
347
|
+
provider: providerSchema.optional(),
|
|
348
|
+
symbol: z.string(),
|
|
349
|
+
applyTo: z.enum(["FULL_POSITION", "CUSTOM_QUANTITY"]),
|
|
350
|
+
quantity: z.number().positive().optional(),
|
|
351
|
+
parentClientOrderId: z.string(),
|
|
352
|
+
hasTakeProfit: z.boolean(),
|
|
353
|
+
hasStopLoss: z.boolean()
|
|
354
|
+
});
|
|
355
|
+
var deleteTpSlInputSchema = z.object({
|
|
356
|
+
provider: providerSchema.optional(),
|
|
357
|
+
symbol: z.string().regex(/^[a-zA-Z0-9]+(?:USDT)?$/),
|
|
358
|
+
orderId: z.string()
|
|
359
|
+
});
|
|
360
|
+
var deleteTpSlResultSchema = z.object({
|
|
361
|
+
status: z.literal("deleted"),
|
|
362
|
+
provider: providerSchema.optional(),
|
|
363
|
+
symbol: z.string(),
|
|
364
|
+
orderId: z.string()
|
|
365
|
+
});
|
|
366
|
+
var openOrderSchema = z.object({
|
|
367
|
+
coin: z.string(),
|
|
368
|
+
side: z.enum(["B", "A"]),
|
|
369
|
+
limitPx: z.string(),
|
|
370
|
+
sz: z.string(),
|
|
371
|
+
oid: z.number().int().nonnegative(),
|
|
372
|
+
timestamp: z.number().int().nonnegative(),
|
|
373
|
+
origSz: z.string(),
|
|
374
|
+
orderType: z.string(),
|
|
375
|
+
tif: z.string().nullable(),
|
|
376
|
+
reduceOnly: z.boolean(),
|
|
377
|
+
isTrigger: z.boolean(),
|
|
378
|
+
triggerPx: z.string(),
|
|
379
|
+
cloid: z.string().nullable(),
|
|
380
|
+
assetId: z.number().int().nonnegative().optional()
|
|
381
|
+
});
|
|
382
|
+
var cancelOrderInputSchema = z.object({
|
|
383
|
+
symbol: z.string().regex(/^[a-zA-Z0-9]+(?:USDT)?$/),
|
|
384
|
+
oid: z.number().int().nonnegative()
|
|
385
|
+
});
|
|
386
|
+
var cancelOrderResultSchema = z.object({
|
|
387
|
+
status: z.literal("cancelled"),
|
|
388
|
+
symbol: z.string(),
|
|
389
|
+
oid: z.number().int().nonnegative()
|
|
390
|
+
});
|
|
391
|
+
var heliosAgentErrorResponseSchema = z2.object({
|
|
392
|
+
error: z2.string().optional(),
|
|
393
|
+
errorDescription: z2.string().optional(),
|
|
394
|
+
message: z2.string().optional()
|
|
395
|
+
});
|
|
396
|
+
var HeliosAgentApiError = class extends Error {
|
|
397
|
+
constructor(options) {
|
|
398
|
+
super(options.message);
|
|
399
|
+
__publicField2(this, "status");
|
|
400
|
+
__publicField2(this, "code");
|
|
401
|
+
__publicField2(this, "errorDescription");
|
|
402
|
+
__publicField2(this, "payload");
|
|
403
|
+
this.name = "HeliosAgentApiError";
|
|
404
|
+
this.status = options.status;
|
|
405
|
+
this.code = options.code;
|
|
406
|
+
this.errorDescription = options.errorDescription;
|
|
407
|
+
this.payload = options.payload;
|
|
408
|
+
if (options.cause !== void 0) {
|
|
409
|
+
Object.defineProperty(this, "cause", {
|
|
410
|
+
configurable: true,
|
|
411
|
+
enumerable: false,
|
|
412
|
+
value: options.cause,
|
|
413
|
+
writable: true
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
function isHeliosAgentApiError(error) {
|
|
419
|
+
return error instanceof HeliosAgentApiError;
|
|
420
|
+
}
|
|
421
|
+
var HeliosAgentAuthClient = class {
|
|
422
|
+
constructor(options) {
|
|
423
|
+
__publicField2(this, "baseUrl");
|
|
424
|
+
__publicField2(this, "fetchImpl");
|
|
425
|
+
__publicField2(this, "headers");
|
|
426
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
427
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
428
|
+
this.headers = options.headers ?? {};
|
|
429
|
+
}
|
|
430
|
+
async startLogin(input) {
|
|
431
|
+
const parsed = agentDeviceStartInputSchema.parse(input);
|
|
432
|
+
return this.post("/api/agents/device/start", parsed, agentDeviceStartOutputSchema);
|
|
433
|
+
}
|
|
434
|
+
async exchangeDeviceCode(deviceCode) {
|
|
435
|
+
const parsed = agentDeviceStartOutputSchema.shape.deviceCode.parse(deviceCode);
|
|
436
|
+
return this.post("/api/agents/device/token", { deviceCode: parsed }, agentTokenResponseSchema);
|
|
437
|
+
}
|
|
438
|
+
async refreshSession(refreshToken) {
|
|
439
|
+
const parsed = agentRefreshInputSchema.parse({ refreshToken });
|
|
440
|
+
return this.post("/api/agents/auth/refresh", parsed, agentTokenResponseSchema);
|
|
441
|
+
}
|
|
442
|
+
async post(path3, body, schema) {
|
|
443
|
+
let response;
|
|
444
|
+
try {
|
|
445
|
+
response = await this.fetchImpl(`${this.baseUrl}${path3}`, {
|
|
446
|
+
method: "POST",
|
|
447
|
+
headers: {
|
|
448
|
+
Accept: "application/json",
|
|
449
|
+
"Content-Type": "application/json",
|
|
450
|
+
...this.headers
|
|
451
|
+
},
|
|
452
|
+
body: JSON.stringify(body)
|
|
453
|
+
});
|
|
454
|
+
} catch (error) {
|
|
455
|
+
throw new HeliosAgentApiError({
|
|
456
|
+
status: 0,
|
|
457
|
+
code: "network_error",
|
|
458
|
+
message: error instanceof Error ? error.message : "Network error",
|
|
459
|
+
cause: error
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
const payload = await this.parseJson(response);
|
|
463
|
+
if (!response.ok) {
|
|
464
|
+
const parsed = heliosAgentErrorResponseSchema.safeParse(payload);
|
|
465
|
+
throw new HeliosAgentApiError({
|
|
466
|
+
status: response.status,
|
|
467
|
+
code: parsed.success ? parsed.data.error ?? "request_failed" : "request_failed",
|
|
468
|
+
message: parsed.success ? parsed.data.errorDescription ?? parsed.data.message ?? `Request failed with status ${response.status}` : `Request failed with status ${response.status}`,
|
|
469
|
+
errorDescription: parsed.success ? parsed.data.errorDescription : void 0,
|
|
470
|
+
payload
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
return schema.parse(payload);
|
|
474
|
+
}
|
|
475
|
+
async parseJson(response) {
|
|
476
|
+
const text = await response.text();
|
|
477
|
+
if (!text) {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
return JSON.parse(text);
|
|
482
|
+
} catch (error) {
|
|
483
|
+
throw new HeliosAgentApiError({
|
|
484
|
+
status: response.status,
|
|
485
|
+
code: "invalid_json",
|
|
486
|
+
message: "Received an invalid JSON response from Helios",
|
|
487
|
+
cause: error
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
var HeliosAgentDataBridgeClient = class {
|
|
493
|
+
constructor(options) {
|
|
494
|
+
__publicField2(this, "baseUrl");
|
|
495
|
+
__publicField2(this, "fetchImpl");
|
|
496
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
497
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
498
|
+
}
|
|
499
|
+
async getWalletState(accessToken) {
|
|
500
|
+
const payload = await this.request(accessToken, "account.getWalletState");
|
|
501
|
+
return walletStateSchema.parse(this.unwrapResult(payload));
|
|
502
|
+
}
|
|
503
|
+
async getOpenPositions(accessToken) {
|
|
504
|
+
const payload = await this.request(accessToken, "positions.getOpen");
|
|
505
|
+
return openPositionsSnapshotSchema.parse(this.unwrapResult(payload));
|
|
506
|
+
}
|
|
507
|
+
async getPortfolio(accessToken, input) {
|
|
508
|
+
const parsedInput = portfolioInputSchema.parse(input);
|
|
509
|
+
const payload = await this.request(accessToken, "positions.getPortfolio", parsedInput);
|
|
510
|
+
const result = this.unwrapResult(payload);
|
|
511
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
512
|
+
throw new HeliosAgentApiError({
|
|
513
|
+
status: 200,
|
|
514
|
+
code: "invalid_response",
|
|
515
|
+
message: "Helios data bridge returned an invalid portfolio payload",
|
|
516
|
+
payload
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
return portfolioSnapshotSchema.parse({
|
|
520
|
+
...result,
|
|
521
|
+
provider: parsedInput.provider
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
async getLedgerFills(accessToken, input = {}) {
|
|
525
|
+
const parsedInput = ledgerFillsInputSchema.parse(input);
|
|
526
|
+
const payload = await this.request(accessToken, "ledger.getFills", parsedInput);
|
|
527
|
+
return ledgerFillsPageSchema.parse(this.unwrapResult(payload));
|
|
528
|
+
}
|
|
529
|
+
async getLedgerSummary(accessToken, input = {}) {
|
|
530
|
+
const parsedInput = ledgerSummaryInputSchema.parse(input);
|
|
531
|
+
const payload = await this.request(accessToken, "ledger.getSummary", parsedInput);
|
|
532
|
+
return ledgerSummarySchema.parse(this.unwrapResult(payload));
|
|
533
|
+
}
|
|
534
|
+
async getMarketSnapshot(accessToken, input = {}) {
|
|
535
|
+
const parsedInput = marketSnapshotInputSchema.parse(input);
|
|
536
|
+
const payload = await this.request(accessToken, "market.getSnapshot", parsedInput);
|
|
537
|
+
return marketSnapshotSchema.parse(this.unwrapResult(payload));
|
|
538
|
+
}
|
|
539
|
+
async getMarketSnapshotChunk(accessToken, input = {}) {
|
|
540
|
+
const parsedInput = marketSnapshotChunkInputSchema.parse(input);
|
|
541
|
+
const payload = await this.request(accessToken, "market.getSnapshotChunk", parsedInput);
|
|
542
|
+
return marketSnapshotChunkSchema.parse(this.unwrapResult(payload));
|
|
543
|
+
}
|
|
544
|
+
async getMarketSymbolDetail(accessToken, input) {
|
|
545
|
+
const parsedInput = marketSymbolDetailInputSchema.parse(input);
|
|
546
|
+
const payload = await this.request(accessToken, "market.getSymbolDetail", parsedInput);
|
|
547
|
+
return marketSymbolDetailSchema.parse(this.unwrapResult(payload));
|
|
548
|
+
}
|
|
549
|
+
async submitTradeIntent(accessToken, input) {
|
|
550
|
+
const parsedInput = submitTradeIntentInputSchema.parse(input);
|
|
551
|
+
const payload = await this.request(accessToken, "trading.submitIntent", parsedInput, "POST");
|
|
552
|
+
const answer = this.unwrapResult(payload) ?? {};
|
|
553
|
+
return submitTradeIntentResultSchema.parse({
|
|
554
|
+
status: "submitted",
|
|
555
|
+
...answer.outcome === "placed" || answer.outcome === "replayed" ? { outcome: answer.outcome } : {},
|
|
556
|
+
...typeof answer.clientOrderId === "string" ? { clientOrderId: answer.clientOrderId } : {},
|
|
557
|
+
...typeof answer.orderStatus === "string" ? { orderStatus: answer.orderStatus } : {},
|
|
558
|
+
...Array.isArray(answer.rejectedProtection) ? { rejectedProtection: answer.rejectedProtection } : {},
|
|
559
|
+
provider: parsedInput.provider,
|
|
560
|
+
symbol: parsedInput.symbol,
|
|
561
|
+
intentType: parsedInput.intentType,
|
|
562
|
+
direction: parsedInput.direction,
|
|
563
|
+
size: parsedInput.size,
|
|
564
|
+
execution: parsedInput.execution,
|
|
565
|
+
maxSlippageBps: parsedInput.maxSlippageBps,
|
|
566
|
+
leverageOverride: parsedInput.leverageOverride,
|
|
567
|
+
marginModeOverride: parsedInput.marginModeOverride,
|
|
568
|
+
reduceOnly: Boolean(parsedInput.reduceOnly),
|
|
569
|
+
hasProtection: Boolean(parsedInput.protection?.takeProfit || parsedInput.protection?.stopLoss)
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
async closePosition(accessToken, input) {
|
|
573
|
+
const parsedInput = closePositionInputSchema.parse(input);
|
|
574
|
+
await this.request(
|
|
575
|
+
accessToken,
|
|
576
|
+
"positions.close",
|
|
577
|
+
{
|
|
578
|
+
...parsedInput.provider !== void 0 ? { provider: parsedInput.provider } : {},
|
|
579
|
+
symbol: parsedInput.symbol,
|
|
580
|
+
mode: "FULL",
|
|
581
|
+
...parsedInput.maxSlippageBps !== void 0 ? { maxSlippageBps: parsedInput.maxSlippageBps } : {}
|
|
582
|
+
},
|
|
583
|
+
"POST"
|
|
584
|
+
);
|
|
585
|
+
return closePositionResultSchema.parse({
|
|
586
|
+
status: "submitted",
|
|
587
|
+
provider: parsedInput.provider,
|
|
588
|
+
symbol: parsedInput.symbol,
|
|
589
|
+
mode: "FULL",
|
|
590
|
+
maxSlippageBps: parsedInput.maxSlippageBps
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
async setTpSl(accessToken, input) {
|
|
594
|
+
const parsedInput = setTpSlInputSchema.parse(input);
|
|
595
|
+
await this.request(accessToken, "positions.setTpSl", parsedInput, "POST");
|
|
596
|
+
return setTpSlResultSchema.parse({
|
|
597
|
+
status: "submitted",
|
|
598
|
+
provider: parsedInput.provider,
|
|
599
|
+
symbol: parsedInput.symbol,
|
|
600
|
+
applyTo: parsedInput.applyTo,
|
|
601
|
+
quantity: parsedInput.quantity,
|
|
602
|
+
parentClientOrderId: parsedInput.parentClientOrderId,
|
|
603
|
+
hasTakeProfit: Boolean(parsedInput.takeProfit),
|
|
604
|
+
hasStopLoss: Boolean(parsedInput.stopLoss)
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
async deleteTpSl(accessToken, input) {
|
|
608
|
+
const parsedInput = deleteTpSlInputSchema.parse(input);
|
|
609
|
+
await this.request(accessToken, "positions.deleteTpSl", parsedInput, "POST");
|
|
610
|
+
return deleteTpSlResultSchema.parse({
|
|
611
|
+
status: "deleted",
|
|
612
|
+
provider: parsedInput.provider,
|
|
613
|
+
symbol: parsedInput.symbol,
|
|
614
|
+
orderId: parsedInput.orderId
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
async getOpenOrders(accessToken) {
|
|
618
|
+
const payload = await this.request(accessToken, "positions.getOpenOrders");
|
|
619
|
+
return openOrderSchema.array().parse(this.unwrapResult(payload));
|
|
620
|
+
}
|
|
621
|
+
async cancelOrder(accessToken, input) {
|
|
622
|
+
const parsedInput = cancelOrderInputSchema.parse(input);
|
|
623
|
+
await this.request(accessToken, "positions.cancelOrder", parsedInput, "POST");
|
|
624
|
+
return cancelOrderResultSchema.parse({
|
|
625
|
+
status: "cancelled",
|
|
626
|
+
symbol: parsedInput.symbol,
|
|
627
|
+
oid: parsedInput.oid
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
async request(accessToken, procedure, input, method = "GET") {
|
|
631
|
+
let response;
|
|
632
|
+
try {
|
|
633
|
+
response = await this.fetchImpl(this.buildProcedureUrl(procedure, method === "GET" ? input : void 0), {
|
|
634
|
+
method,
|
|
635
|
+
headers: {
|
|
636
|
+
Accept: "application/json",
|
|
637
|
+
...method === "POST" ? { "Content-Type": "application/json" } : {},
|
|
638
|
+
Authorization: `Bearer ${accessToken}`
|
|
639
|
+
},
|
|
640
|
+
...method === "POST" && input !== void 0 ? { body: JSON.stringify({ json: input }) } : {}
|
|
641
|
+
});
|
|
642
|
+
} catch (error) {
|
|
643
|
+
throw new HeliosAgentApiError({
|
|
644
|
+
status: 0,
|
|
645
|
+
code: "network_error",
|
|
646
|
+
message: error instanceof Error ? error.message : "Network error",
|
|
647
|
+
cause: error
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
const payload = await this.parseJson(response);
|
|
651
|
+
if (!response.ok) {
|
|
652
|
+
throw new HeliosAgentApiError({
|
|
653
|
+
status: response.status,
|
|
654
|
+
code: this.extractErrorCode(payload),
|
|
655
|
+
message: this.extractErrorMessage(payload, response.status),
|
|
656
|
+
payload
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
return payload;
|
|
660
|
+
}
|
|
661
|
+
buildProcedureUrl(procedure, input) {
|
|
662
|
+
const url = new URL(`${this.baseUrl}/api/trpc/${procedure}`);
|
|
663
|
+
if (input !== void 0) {
|
|
664
|
+
url.searchParams.set("input", JSON.stringify({ json: input }));
|
|
665
|
+
}
|
|
666
|
+
return url.toString();
|
|
667
|
+
}
|
|
668
|
+
async parseJson(response) {
|
|
669
|
+
const text = await response.text();
|
|
670
|
+
if (!text) {
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
try {
|
|
674
|
+
return JSON.parse(text);
|
|
675
|
+
} catch (error) {
|
|
676
|
+
throw new HeliosAgentApiError({
|
|
677
|
+
status: response.status,
|
|
678
|
+
code: "invalid_json",
|
|
679
|
+
message: "Received an invalid JSON response from Helios data bridge",
|
|
680
|
+
cause: error
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
unwrapResult(payload) {
|
|
685
|
+
if (!payload || typeof payload !== "object") {
|
|
686
|
+
throw new HeliosAgentApiError({
|
|
687
|
+
status: 200,
|
|
688
|
+
code: "invalid_response",
|
|
689
|
+
message: "Helios data bridge returned an invalid response payload",
|
|
690
|
+
payload
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
const result = payload.result;
|
|
694
|
+
if (!result) {
|
|
695
|
+
throw new HeliosAgentApiError({
|
|
696
|
+
status: 200,
|
|
697
|
+
code: "invalid_response",
|
|
698
|
+
message: "Helios data bridge response is missing a result payload",
|
|
699
|
+
payload
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
const data = result.data;
|
|
703
|
+
if (data && typeof data === "object" && "json" in data) {
|
|
704
|
+
return data.json;
|
|
705
|
+
}
|
|
706
|
+
return data;
|
|
707
|
+
}
|
|
708
|
+
extractErrorCode(payload) {
|
|
709
|
+
if (payload && typeof payload === "object") {
|
|
710
|
+
const errorShape = this.unwrapTrpcError(payload);
|
|
711
|
+
if (errorShape?.data?.code) {
|
|
712
|
+
return errorShape.data.code;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return "request_failed";
|
|
716
|
+
}
|
|
717
|
+
extractErrorMessage(payload, status) {
|
|
718
|
+
if (payload && typeof payload === "object") {
|
|
719
|
+
const errorShape = this.unwrapTrpcError(payload);
|
|
720
|
+
if (errorShape?.message) {
|
|
721
|
+
return errorShape.message;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return `Request failed with status ${status}`;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Unwrap a tRPC error envelope, handling the superjson `{ json: ... }` wrapper
|
|
728
|
+
* that the data-bridge applies via its transformer configuration.
|
|
729
|
+
*/
|
|
730
|
+
unwrapTrpcError(payload) {
|
|
731
|
+
const envelope = payload.error;
|
|
732
|
+
if (!envelope || typeof envelope !== "object") {
|
|
733
|
+
return void 0;
|
|
734
|
+
}
|
|
735
|
+
if ("json" in envelope) {
|
|
736
|
+
const inner = envelope.json;
|
|
737
|
+
if (inner && typeof inner === "object") {
|
|
738
|
+
return inner;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return envelope;
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
var DEFAULT_AGENT_REFRESH_WINDOW_MS = 60 * 1e3;
|
|
745
|
+
var agentConnectionStatusSchema = z3.enum([
|
|
746
|
+
"disconnected",
|
|
747
|
+
"pending",
|
|
748
|
+
"connected",
|
|
749
|
+
"rejected",
|
|
750
|
+
"expired",
|
|
751
|
+
"failed"
|
|
752
|
+
]);
|
|
753
|
+
var storedAgentSessionSchema = z3.object({
|
|
754
|
+
accessToken: z3.string(),
|
|
755
|
+
refreshToken: z3.string(),
|
|
756
|
+
tokenType: agentTokenResponseSchema.shape.tokenType,
|
|
757
|
+
ownerUserId: z3.string(),
|
|
758
|
+
agentGrantId: z3.string(),
|
|
759
|
+
scope: z3.array(agentScopeSchema).min(1),
|
|
760
|
+
accessTokenExpiresAt: z3.string().datetime(),
|
|
761
|
+
createdAt: z3.string().datetime(),
|
|
762
|
+
lastRefreshedAt: z3.string().datetime()
|
|
763
|
+
});
|
|
764
|
+
var agentSessionViewSchema = z3.object({
|
|
765
|
+
connected: z3.boolean(),
|
|
766
|
+
status: agentConnectionStatusSchema,
|
|
767
|
+
ownerUserId: z3.string().optional(),
|
|
768
|
+
agentGrantId: z3.string().optional(),
|
|
769
|
+
scope: z3.array(agentScopeSchema).optional(),
|
|
770
|
+
accessTokenExpiresAt: z3.string().datetime().optional(),
|
|
771
|
+
lastRefreshedAt: z3.string().datetime().optional()
|
|
772
|
+
});
|
|
773
|
+
function createStoredAgentSession(tokenResponse, now = /* @__PURE__ */ new Date()) {
|
|
774
|
+
const createdAt = now.toISOString();
|
|
775
|
+
return {
|
|
776
|
+
accessToken: tokenResponse.accessToken,
|
|
777
|
+
refreshToken: tokenResponse.refreshToken,
|
|
778
|
+
tokenType: tokenResponse.tokenType,
|
|
779
|
+
ownerUserId: tokenResponse.ownerUserId,
|
|
780
|
+
agentGrantId: tokenResponse.agentGrantId,
|
|
781
|
+
scope: tokenResponse.scope,
|
|
782
|
+
accessTokenExpiresAt: new Date(now.getTime() + tokenResponse.expiresInSeconds * 1e3).toISOString(),
|
|
783
|
+
createdAt,
|
|
784
|
+
lastRefreshedAt: createdAt
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function sanitizeAgentSession(session, status = session ? "connected" : "disconnected") {
|
|
788
|
+
if (!session) {
|
|
789
|
+
return {
|
|
790
|
+
connected: false,
|
|
791
|
+
status: status === "connected" ? "disconnected" : status
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
return {
|
|
795
|
+
connected: true,
|
|
796
|
+
status,
|
|
797
|
+
ownerUserId: session.ownerUserId,
|
|
798
|
+
agentGrantId: session.agentGrantId,
|
|
799
|
+
scope: session.scope,
|
|
800
|
+
accessTokenExpiresAt: session.accessTokenExpiresAt,
|
|
801
|
+
lastRefreshedAt: session.lastRefreshedAt
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function shouldRefreshAgentSession(session, now = /* @__PURE__ */ new Date(), refreshWindowMs = DEFAULT_AGENT_REFRESH_WINDOW_MS) {
|
|
805
|
+
return new Date(session.accessTokenExpiresAt).getTime() - now.getTime() <= refreshWindowMs;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// src/auth-service.ts
|
|
809
|
+
var HOSTED_RECONNECT_MESSAGE = "Helios no longer accepts this connection (it expired or was revoked). Reconnect Helios in your AI app to continue.";
|
|
810
|
+
var HeliosAgentMcpAuthService = class {
|
|
811
|
+
constructor(options) {
|
|
812
|
+
__publicField(this, "client");
|
|
813
|
+
__publicField(this, "sessionStore");
|
|
814
|
+
__publicField(this, "refreshWindowMs");
|
|
815
|
+
__publicField(this, "now");
|
|
816
|
+
__publicField(this, "deviceLogin");
|
|
817
|
+
this.client = options.client;
|
|
818
|
+
this.sessionStore = options.sessionStore;
|
|
819
|
+
this.refreshWindowMs = options.refreshWindowMs ?? 60 * 1e3;
|
|
820
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
821
|
+
this.deviceLogin = options.deviceLogin ?? true;
|
|
822
|
+
}
|
|
823
|
+
async startLogin(input) {
|
|
824
|
+
const response = await this.startDeviceLogin(input);
|
|
825
|
+
const result = this.buildStartLoginResult(response, input.scope);
|
|
826
|
+
await this.sessionStore.setPendingLogin(result);
|
|
827
|
+
return result;
|
|
828
|
+
}
|
|
829
|
+
async resetSessionAndStartLogin(input) {
|
|
830
|
+
await this.sessionStore.clearSession();
|
|
831
|
+
await this.sessionStore.clearPendingLogin();
|
|
832
|
+
const response = await this.startDeviceLogin(input);
|
|
833
|
+
const result = this.buildStartLoginResult(response, input.scope);
|
|
834
|
+
await this.sessionStore.setPendingLogin(result);
|
|
835
|
+
return result;
|
|
836
|
+
}
|
|
837
|
+
async startDeviceLogin(input) {
|
|
838
|
+
if (!this.deviceLogin) {
|
|
839
|
+
throw new Error(HOSTED_RECONNECT_MESSAGE);
|
|
840
|
+
}
|
|
841
|
+
return this.client.startLogin(input);
|
|
842
|
+
}
|
|
843
|
+
buildStartLoginResult(response, requestedScope) {
|
|
844
|
+
return {
|
|
845
|
+
status: "pending",
|
|
846
|
+
...response,
|
|
847
|
+
requestedScope,
|
|
848
|
+
startedAt: this.now().toISOString()
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
async startRelogin(options = {}) {
|
|
852
|
+
const requestedScope = options.scope ?? [AGENT_SCOPES.ACCOUNT_READ, AGENT_SCOPES.MARKET_READ];
|
|
853
|
+
const started = await this.resetSessionAndStartLogin({
|
|
854
|
+
clientName: options.clientName ?? "Helios MCP",
|
|
855
|
+
scope: requestedScope,
|
|
856
|
+
constraints: options.constraints
|
|
857
|
+
});
|
|
858
|
+
return {
|
|
859
|
+
...started,
|
|
860
|
+
connected: false,
|
|
861
|
+
message: formatHeliosLoginPrompt(started)
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
async clearSessionState() {
|
|
865
|
+
await this.sessionStore.clearSession();
|
|
866
|
+
await this.sessionStore.clearPendingLogin();
|
|
867
|
+
}
|
|
868
|
+
async invalidateIfUnauthorized(error, options = {}) {
|
|
869
|
+
if (!isHeliosAgentApiError(error) || error.status !== 401) {
|
|
870
|
+
return null;
|
|
871
|
+
}
|
|
872
|
+
return this.startRelogin(options);
|
|
873
|
+
}
|
|
874
|
+
async refreshSessionAfterUnauthorized(error) {
|
|
875
|
+
if (!isHeliosAgentApiError(error) || error.status !== 401) {
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
const session = await this.sessionStore.getSession();
|
|
879
|
+
if (!session) {
|
|
880
|
+
return null;
|
|
881
|
+
}
|
|
882
|
+
return this.refreshStoredSession(session);
|
|
883
|
+
}
|
|
884
|
+
async ensureLogin(options = {}) {
|
|
885
|
+
const requestedScope = options.scope ?? [AGENT_SCOPES.ACCOUNT_READ, AGENT_SCOPES.MARKET_READ];
|
|
886
|
+
const requestedConstraints = options.constraints ?? {};
|
|
887
|
+
const session = await this.getValidSession();
|
|
888
|
+
if (session) {
|
|
889
|
+
return {
|
|
890
|
+
...sanitizeAgentSession(session),
|
|
891
|
+
connected: true,
|
|
892
|
+
status: "connected"
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
const pendingLogin = await this.getPendingLogin({
|
|
896
|
+
requestedScope,
|
|
897
|
+
requestedConstraints
|
|
898
|
+
});
|
|
899
|
+
if (pendingLogin) {
|
|
900
|
+
return {
|
|
901
|
+
...pendingLogin,
|
|
902
|
+
connected: false,
|
|
903
|
+
message: formatHeliosLoginPrompt(pendingLogin)
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
const started = await this.startLogin({
|
|
907
|
+
clientName: options.clientName ?? "Helios MCP",
|
|
908
|
+
scope: requestedScope,
|
|
909
|
+
constraints: options.constraints
|
|
910
|
+
});
|
|
911
|
+
return {
|
|
912
|
+
...started,
|
|
913
|
+
connected: false,
|
|
914
|
+
message: formatHeliosLoginPrompt(started)
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
async pollLogin(deviceCode) {
|
|
918
|
+
try {
|
|
919
|
+
const response = await this.client.exchangeDeviceCode(deviceCode);
|
|
920
|
+
const session = createStoredAgentSession(response, this.now());
|
|
921
|
+
await this.sessionStore.setSession(session);
|
|
922
|
+
await this.sessionStore.clearPendingLogin();
|
|
923
|
+
return {
|
|
924
|
+
...sanitizeAgentSession(session),
|
|
925
|
+
connected: true,
|
|
926
|
+
status: "connected"
|
|
927
|
+
};
|
|
928
|
+
} catch (error) {
|
|
929
|
+
if (isHeliosAgentApiError(error)) {
|
|
930
|
+
if (error.code === "authorization_pending") {
|
|
931
|
+
return {
|
|
932
|
+
status: "pending",
|
|
933
|
+
connected: false,
|
|
934
|
+
message: error.message
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
if (error.code === "access_denied") {
|
|
938
|
+
await this.clearPendingLoginIfMatches(deviceCode);
|
|
939
|
+
return {
|
|
940
|
+
status: "rejected",
|
|
941
|
+
connected: false,
|
|
942
|
+
message: error.message
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
if (error.code === "expired_token") {
|
|
946
|
+
await this.clearPendingLoginIfMatches(deviceCode);
|
|
947
|
+
return {
|
|
948
|
+
status: "expired",
|
|
949
|
+
connected: false,
|
|
950
|
+
message: error.message
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
return {
|
|
954
|
+
status: "failed",
|
|
955
|
+
connected: false,
|
|
956
|
+
message: error.message
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
return {
|
|
960
|
+
status: "failed",
|
|
961
|
+
connected: false,
|
|
962
|
+
message: error instanceof Error ? error.message : "Unexpected Helios login error"
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
async whoami() {
|
|
967
|
+
const session = await this.getValidSession();
|
|
968
|
+
return sanitizeAgentSession(session);
|
|
969
|
+
}
|
|
970
|
+
async getAccessToken() {
|
|
971
|
+
const session = await this.getValidSession();
|
|
972
|
+
return session?.accessToken ?? null;
|
|
973
|
+
}
|
|
974
|
+
async requireSession() {
|
|
975
|
+
const session = await this.getValidSession();
|
|
976
|
+
if (!session) {
|
|
977
|
+
throw new Error("No Helios delegated agent session is connected");
|
|
978
|
+
}
|
|
979
|
+
return session;
|
|
980
|
+
}
|
|
981
|
+
async getValidSession() {
|
|
982
|
+
const session = await this.sessionStore.getSession();
|
|
983
|
+
if (!session) {
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
if (!shouldRefreshAgentSession(session, this.now(), this.refreshWindowMs)) {
|
|
987
|
+
return session;
|
|
988
|
+
}
|
|
989
|
+
return this.refreshStoredSession(session);
|
|
990
|
+
}
|
|
991
|
+
async refreshStoredSession(session) {
|
|
992
|
+
try {
|
|
993
|
+
const refreshed = await this.client.refreshSession(session.refreshToken);
|
|
994
|
+
const nextSession = createStoredAgentSession(refreshed, this.now());
|
|
995
|
+
await this.sessionStore.setSession(nextSession);
|
|
996
|
+
return nextSession;
|
|
997
|
+
} catch (error) {
|
|
998
|
+
if (isHeliosAgentApiError(error) && error.code === "invalid_grant") {
|
|
999
|
+
await this.sessionStore.clearSession();
|
|
1000
|
+
await this.sessionStore.clearPendingLogin();
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
1003
|
+
throw error;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
async getPendingLogin(options) {
|
|
1007
|
+
const pendingLogin = await this.sessionStore.getPendingLogin();
|
|
1008
|
+
if (!pendingLogin) {
|
|
1009
|
+
return null;
|
|
1010
|
+
}
|
|
1011
|
+
const expiresAt = new Date(pendingLogin.startedAt).getTime() + pendingLogin.expiresInSeconds * 1e3;
|
|
1012
|
+
if (this.now().getTime() >= expiresAt) {
|
|
1013
|
+
await this.sessionStore.clearPendingLogin();
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
if (!isPendingLoginCompatible(pendingLogin, options.requestedScope, options.requestedConstraints)) {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
return pendingLogin;
|
|
1020
|
+
}
|
|
1021
|
+
async clearPendingLoginIfMatches(deviceCode) {
|
|
1022
|
+
const pendingLogin = await this.sessionStore.getPendingLogin();
|
|
1023
|
+
if (pendingLogin?.deviceCode === deviceCode) {
|
|
1024
|
+
await this.sessionStore.clearPendingLogin();
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
async function retryAfterUnauthorizedRefresh(options) {
|
|
1029
|
+
const refreshedSession = await options.authService.refreshSessionAfterUnauthorized(options.error);
|
|
1030
|
+
if (!refreshedSession) {
|
|
1031
|
+
return options.authService.invalidateIfUnauthorized(options.error, options.loginOptions);
|
|
1032
|
+
}
|
|
1033
|
+
try {
|
|
1034
|
+
return await options.retry(refreshedSession.accessToken);
|
|
1035
|
+
} catch (retryError) {
|
|
1036
|
+
const relogin = await options.authService.invalidateIfUnauthorized(retryError, options.loginOptions);
|
|
1037
|
+
if (relogin) {
|
|
1038
|
+
return relogin;
|
|
1039
|
+
}
|
|
1040
|
+
throw retryError;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
function formatHeliosLoginPrompt(result) {
|
|
1044
|
+
return `Log in to Helios by opening this URL in your browser:
|
|
1045
|
+
|
|
1046
|
+
${result.verificationUriComplete}
|
|
1047
|
+
|
|
1048
|
+
User code: ${result.userCode}
|
|
1049
|
+
|
|
1050
|
+
Requested scopes: ${result.requestedScope.join(", ")}
|
|
1051
|
+
|
|
1052
|
+
After approving access, ask me to continue and I will finish connecting.`;
|
|
1053
|
+
}
|
|
1054
|
+
function isPendingLoginCompatible(pendingLogin, requestedScope, requestedConstraints) {
|
|
1055
|
+
const pendingScope = new Set(pendingLogin.requestedScope);
|
|
1056
|
+
const scopeCompatible = requestedScope.every((scope) => pendingScope.has(scope));
|
|
1057
|
+
if (!scopeCompatible) {
|
|
1058
|
+
return false;
|
|
1059
|
+
}
|
|
1060
|
+
return Object.keys(requestedConstraints ?? {}).length === 0;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// src/account-service.ts
|
|
1064
|
+
var HeliosAgentMcpAccountService = class {
|
|
1065
|
+
constructor(options) {
|
|
1066
|
+
__publicField(this, "authService");
|
|
1067
|
+
__publicField(this, "dataBridgeClient");
|
|
1068
|
+
__publicField(this, "providerService");
|
|
1069
|
+
this.authService = options.authService;
|
|
1070
|
+
this.dataBridgeClient = options.dataBridgeClient;
|
|
1071
|
+
this.providerService = options.providerService;
|
|
1072
|
+
}
|
|
1073
|
+
async getWalletState() {
|
|
1074
|
+
const loginOptions = this.accountReadOptions();
|
|
1075
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1076
|
+
if (login.status !== "connected") {
|
|
1077
|
+
return {
|
|
1078
|
+
...login,
|
|
1079
|
+
status: "pending",
|
|
1080
|
+
connected: false
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
const session = await this.authService.requireSession();
|
|
1084
|
+
try {
|
|
1085
|
+
const walletState = await this.dataBridgeClient.getWalletState(session.accessToken);
|
|
1086
|
+
return {
|
|
1087
|
+
status: "connected",
|
|
1088
|
+
connected: true,
|
|
1089
|
+
walletState
|
|
1090
|
+
};
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1093
|
+
authService: this.authService,
|
|
1094
|
+
error,
|
|
1095
|
+
loginOptions,
|
|
1096
|
+
retry: async (accessToken) => {
|
|
1097
|
+
const walletState = await this.dataBridgeClient.getWalletState(accessToken);
|
|
1098
|
+
return {
|
|
1099
|
+
status: "connected",
|
|
1100
|
+
connected: true,
|
|
1101
|
+
walletState
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
if (recovery) {
|
|
1106
|
+
return recovery;
|
|
1107
|
+
}
|
|
1108
|
+
throw error;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
async getPortfolio(input = {}) {
|
|
1112
|
+
const loginOptions = this.accountReadOptions();
|
|
1113
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1114
|
+
if (login.status !== "connected") {
|
|
1115
|
+
return {
|
|
1116
|
+
...login,
|
|
1117
|
+
status: "pending",
|
|
1118
|
+
connected: false
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
const session = await this.authService.requireSession();
|
|
1122
|
+
try {
|
|
1123
|
+
const provider = await this.providerService.resolveProvider(input.provider);
|
|
1124
|
+
const portfolio = await this.dataBridgeClient.getPortfolio(session.accessToken, { provider });
|
|
1125
|
+
return {
|
|
1126
|
+
status: "connected",
|
|
1127
|
+
connected: true,
|
|
1128
|
+
portfolio
|
|
1129
|
+
};
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1132
|
+
authService: this.authService,
|
|
1133
|
+
error,
|
|
1134
|
+
loginOptions,
|
|
1135
|
+
retry: async (accessToken) => {
|
|
1136
|
+
const provider = await this.providerService.resolveProvider(input.provider);
|
|
1137
|
+
const portfolio = await this.dataBridgeClient.getPortfolio(accessToken, { provider });
|
|
1138
|
+
return {
|
|
1139
|
+
status: "connected",
|
|
1140
|
+
connected: true,
|
|
1141
|
+
portfolio
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
if (recovery) {
|
|
1146
|
+
return recovery;
|
|
1147
|
+
}
|
|
1148
|
+
throw error;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
accountReadOptions() {
|
|
1152
|
+
return {
|
|
1153
|
+
clientName: "Helios MCP",
|
|
1154
|
+
scope: [AGENT_SCOPES.ACCOUNT_READ]
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
|
|
1159
|
+
// src/docs-service.ts
|
|
1160
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
1161
|
+
import path from "path";
|
|
1162
|
+
var HeliosAgentMcpDocsService = class {
|
|
1163
|
+
constructor(options) {
|
|
1164
|
+
__publicField(this, "docsRoot");
|
|
1165
|
+
this.docsRoot = options.docsRoot;
|
|
1166
|
+
}
|
|
1167
|
+
async getIndex() {
|
|
1168
|
+
const documents = listDocumentPaths(this.docsRoot).map((documentPath) => this.readDocumentMetadata(documentPath));
|
|
1169
|
+
return {
|
|
1170
|
+
root: "docs",
|
|
1171
|
+
documents
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
async getDocument(input) {
|
|
1175
|
+
const documentPath = resolveDocumentPath(this.docsRoot, input.path);
|
|
1176
|
+
const body = readFileSync(documentPath, "utf8");
|
|
1177
|
+
const stats = statSync(documentPath);
|
|
1178
|
+
const relativePath = toPosixPath(path.relative(this.docsRoot, documentPath));
|
|
1179
|
+
return {
|
|
1180
|
+
path: relativePath,
|
|
1181
|
+
section: getDocumentSection(relativePath),
|
|
1182
|
+
title: extractDocumentTitle(body, relativePath),
|
|
1183
|
+
body,
|
|
1184
|
+
bytes: stats.size
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
readDocumentMetadata(documentPath) {
|
|
1188
|
+
const body = readFileSync(documentPath, "utf8");
|
|
1189
|
+
const stats = statSync(documentPath);
|
|
1190
|
+
const relativePath = toPosixPath(path.relative(this.docsRoot, documentPath));
|
|
1191
|
+
return {
|
|
1192
|
+
path: relativePath,
|
|
1193
|
+
section: getDocumentSection(relativePath),
|
|
1194
|
+
title: extractDocumentTitle(body, relativePath),
|
|
1195
|
+
bytes: stats.size
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
function getDefaultHeliosAgentMcpDocsRoot() {
|
|
1200
|
+
return path.resolve(__dirname, "..", "docs");
|
|
1201
|
+
}
|
|
1202
|
+
function listDocumentPaths(root) {
|
|
1203
|
+
return walk(root).filter((filePath) => filePath.endsWith(".txt")).sort((left, right) => left.localeCompare(right));
|
|
1204
|
+
}
|
|
1205
|
+
function walk(currentPath) {
|
|
1206
|
+
const entries = readdirSync(currentPath, { withFileTypes: true });
|
|
1207
|
+
const files = [];
|
|
1208
|
+
for (const entry of entries) {
|
|
1209
|
+
const nextPath = path.join(currentPath, entry.name);
|
|
1210
|
+
if (entry.isDirectory()) {
|
|
1211
|
+
files.push(...walk(nextPath));
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
if (entry.isFile()) {
|
|
1215
|
+
files.push(nextPath);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return files;
|
|
1219
|
+
}
|
|
1220
|
+
function resolveDocumentPath(root, relativeDocumentPath) {
|
|
1221
|
+
const normalizedPath = relativeDocumentPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
1222
|
+
const resolvedPath = path.resolve(root, normalizedPath);
|
|
1223
|
+
const relativePath = path.relative(root, resolvedPath);
|
|
1224
|
+
if (!normalizedPath) {
|
|
1225
|
+
throw new Error("Document path is required.");
|
|
1226
|
+
}
|
|
1227
|
+
if (path.isAbsolute(normalizedPath) || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
1228
|
+
throw new Error(`Document path "${relativeDocumentPath}" is outside the docs root.`);
|
|
1229
|
+
}
|
|
1230
|
+
if (!resolvedPath.endsWith(".txt")) {
|
|
1231
|
+
throw new Error(`Document path "${relativeDocumentPath}" must point to a .txt file.`);
|
|
1232
|
+
}
|
|
1233
|
+
return resolvedPath;
|
|
1234
|
+
}
|
|
1235
|
+
function extractDocumentTitle(body, relativePath) {
|
|
1236
|
+
const lines = body.split(/\r?\n/);
|
|
1237
|
+
for (const line of lines) {
|
|
1238
|
+
const trimmed = line.trim();
|
|
1239
|
+
if (!trimmed || /^=+$/.test(trimmed)) {
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
if (trimmed.startsWith("TITLE:")) {
|
|
1243
|
+
return trimmed.slice("TITLE:".length).trim();
|
|
1244
|
+
}
|
|
1245
|
+
return trimmed;
|
|
1246
|
+
}
|
|
1247
|
+
return path.basename(relativePath, ".txt");
|
|
1248
|
+
}
|
|
1249
|
+
function getDocumentSection(relativePath) {
|
|
1250
|
+
return relativePath.split("/")[0] ?? "docs";
|
|
1251
|
+
}
|
|
1252
|
+
function toPosixPath(value) {
|
|
1253
|
+
return value.split(path.sep).join("/");
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// src/ledger-service.ts
|
|
1257
|
+
var HeliosAgentMcpLedgerService = class {
|
|
1258
|
+
constructor(options) {
|
|
1259
|
+
__publicField(this, "authService");
|
|
1260
|
+
__publicField(this, "dataBridgeClient");
|
|
1261
|
+
this.authService = options.authService;
|
|
1262
|
+
this.dataBridgeClient = options.dataBridgeClient;
|
|
1263
|
+
}
|
|
1264
|
+
async getOpenPositions() {
|
|
1265
|
+
const loginOptions = this.accountReadOptions();
|
|
1266
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1267
|
+
if (login.status !== "connected") {
|
|
1268
|
+
return asPending(login);
|
|
1269
|
+
}
|
|
1270
|
+
const session = await this.authService.requireSession();
|
|
1271
|
+
try {
|
|
1272
|
+
const openPositions = await this.dataBridgeClient.getOpenPositions(session.accessToken);
|
|
1273
|
+
return {
|
|
1274
|
+
status: "connected",
|
|
1275
|
+
connected: true,
|
|
1276
|
+
openPositions
|
|
1277
|
+
};
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1280
|
+
authService: this.authService,
|
|
1281
|
+
error,
|
|
1282
|
+
loginOptions,
|
|
1283
|
+
retry: async (accessToken) => {
|
|
1284
|
+
const openPositions = await this.dataBridgeClient.getOpenPositions(accessToken);
|
|
1285
|
+
return {
|
|
1286
|
+
status: "connected",
|
|
1287
|
+
connected: true,
|
|
1288
|
+
openPositions
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
if (recovery) {
|
|
1293
|
+
return recovery;
|
|
1294
|
+
}
|
|
1295
|
+
throw error;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
async getFills(input = {}) {
|
|
1299
|
+
const loginOptions = this.accountReadOptions();
|
|
1300
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1301
|
+
if (login.status !== "connected") {
|
|
1302
|
+
return asPending(login);
|
|
1303
|
+
}
|
|
1304
|
+
const session = await this.authService.requireSession();
|
|
1305
|
+
try {
|
|
1306
|
+
const fills = await this.dataBridgeClient.getLedgerFills(session.accessToken, input);
|
|
1307
|
+
return {
|
|
1308
|
+
status: "connected",
|
|
1309
|
+
connected: true,
|
|
1310
|
+
fills
|
|
1311
|
+
};
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1314
|
+
authService: this.authService,
|
|
1315
|
+
error,
|
|
1316
|
+
loginOptions,
|
|
1317
|
+
retry: async (accessToken) => {
|
|
1318
|
+
const fills = await this.dataBridgeClient.getLedgerFills(accessToken, input);
|
|
1319
|
+
return {
|
|
1320
|
+
status: "connected",
|
|
1321
|
+
connected: true,
|
|
1322
|
+
fills
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
});
|
|
1326
|
+
if (recovery) {
|
|
1327
|
+
return recovery;
|
|
1328
|
+
}
|
|
1329
|
+
throw error;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
async getSummary(input = {}) {
|
|
1333
|
+
const loginOptions = this.accountReadOptions();
|
|
1334
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1335
|
+
if (login.status !== "connected") {
|
|
1336
|
+
return asPending(login);
|
|
1337
|
+
}
|
|
1338
|
+
const session = await this.authService.requireSession();
|
|
1339
|
+
try {
|
|
1340
|
+
const summary = await this.dataBridgeClient.getLedgerSummary(session.accessToken, input);
|
|
1341
|
+
return {
|
|
1342
|
+
status: "connected",
|
|
1343
|
+
connected: true,
|
|
1344
|
+
summary
|
|
1345
|
+
};
|
|
1346
|
+
} catch (error) {
|
|
1347
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1348
|
+
authService: this.authService,
|
|
1349
|
+
error,
|
|
1350
|
+
loginOptions,
|
|
1351
|
+
retry: async (accessToken) => {
|
|
1352
|
+
const summary = await this.dataBridgeClient.getLedgerSummary(accessToken, input);
|
|
1353
|
+
return {
|
|
1354
|
+
status: "connected",
|
|
1355
|
+
connected: true,
|
|
1356
|
+
summary
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1360
|
+
if (recovery) {
|
|
1361
|
+
return recovery;
|
|
1362
|
+
}
|
|
1363
|
+
throw error;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
accountReadOptions() {
|
|
1367
|
+
return {
|
|
1368
|
+
clientName: "Helios MCP",
|
|
1369
|
+
scope: [AGENT_SCOPES.ACCOUNT_READ]
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
function asPending(login) {
|
|
1374
|
+
return {
|
|
1375
|
+
...login,
|
|
1376
|
+
status: "pending",
|
|
1377
|
+
connected: false
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// src/market-service.ts
|
|
1382
|
+
var HeliosAgentMcpMarketService = class {
|
|
1383
|
+
constructor(options) {
|
|
1384
|
+
__publicField(this, "authService");
|
|
1385
|
+
__publicField(this, "dataBridgeClient");
|
|
1386
|
+
this.authService = options.authService;
|
|
1387
|
+
this.dataBridgeClient = options.dataBridgeClient;
|
|
1388
|
+
}
|
|
1389
|
+
async getSnapshot(input = {}) {
|
|
1390
|
+
const loginOptions = {
|
|
1391
|
+
clientName: "Helios MCP",
|
|
1392
|
+
scope: [AGENT_SCOPES.MARKET_READ]
|
|
1393
|
+
};
|
|
1394
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1395
|
+
if (login.status !== "connected") {
|
|
1396
|
+
return {
|
|
1397
|
+
...login,
|
|
1398
|
+
status: "pending",
|
|
1399
|
+
connected: false
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
const session = await this.authService.requireSession();
|
|
1403
|
+
try {
|
|
1404
|
+
const snapshot = await this.dataBridgeClient.getMarketSnapshot(session.accessToken, input);
|
|
1405
|
+
return {
|
|
1406
|
+
status: "connected",
|
|
1407
|
+
connected: true,
|
|
1408
|
+
snapshot
|
|
1409
|
+
};
|
|
1410
|
+
} catch (error) {
|
|
1411
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1412
|
+
authService: this.authService,
|
|
1413
|
+
error,
|
|
1414
|
+
loginOptions,
|
|
1415
|
+
retry: async (accessToken) => {
|
|
1416
|
+
const snapshot = await this.dataBridgeClient.getMarketSnapshot(accessToken, input);
|
|
1417
|
+
return {
|
|
1418
|
+
status: "connected",
|
|
1419
|
+
connected: true,
|
|
1420
|
+
snapshot
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
});
|
|
1424
|
+
if (recovery) {
|
|
1425
|
+
return recovery;
|
|
1426
|
+
}
|
|
1427
|
+
throw error;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
async getSnapshotChunk(input = {}) {
|
|
1431
|
+
const loginOptions = {
|
|
1432
|
+
clientName: "Helios MCP",
|
|
1433
|
+
scope: [AGENT_SCOPES.MARKET_READ]
|
|
1434
|
+
};
|
|
1435
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1436
|
+
if (login.status !== "connected") {
|
|
1437
|
+
return {
|
|
1438
|
+
...login,
|
|
1439
|
+
status: "pending",
|
|
1440
|
+
connected: false
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
const session = await this.authService.requireSession();
|
|
1444
|
+
try {
|
|
1445
|
+
const snapshotChunk = await this.dataBridgeClient.getMarketSnapshotChunk(session.accessToken, input);
|
|
1446
|
+
return {
|
|
1447
|
+
status: "connected",
|
|
1448
|
+
connected: true,
|
|
1449
|
+
snapshotChunk
|
|
1450
|
+
};
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1453
|
+
authService: this.authService,
|
|
1454
|
+
error,
|
|
1455
|
+
loginOptions,
|
|
1456
|
+
retry: async (accessToken) => {
|
|
1457
|
+
const snapshotChunk = await this.dataBridgeClient.getMarketSnapshotChunk(accessToken, input);
|
|
1458
|
+
return {
|
|
1459
|
+
status: "connected",
|
|
1460
|
+
connected: true,
|
|
1461
|
+
snapshotChunk
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
if (recovery) {
|
|
1466
|
+
return recovery;
|
|
1467
|
+
}
|
|
1468
|
+
throw error;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
async getSymbolDetail(input) {
|
|
1472
|
+
const loginOptions = {
|
|
1473
|
+
clientName: "Helios MCP",
|
|
1474
|
+
scope: [AGENT_SCOPES.MARKET_READ]
|
|
1475
|
+
};
|
|
1476
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1477
|
+
if (login.status !== "connected") {
|
|
1478
|
+
return {
|
|
1479
|
+
...login,
|
|
1480
|
+
status: "pending",
|
|
1481
|
+
connected: false
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
const session = await this.authService.requireSession();
|
|
1485
|
+
try {
|
|
1486
|
+
const detail = await this.dataBridgeClient.getMarketSymbolDetail(session.accessToken, input);
|
|
1487
|
+
return {
|
|
1488
|
+
status: "connected",
|
|
1489
|
+
connected: true,
|
|
1490
|
+
detail
|
|
1491
|
+
};
|
|
1492
|
+
} catch (error) {
|
|
1493
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1494
|
+
authService: this.authService,
|
|
1495
|
+
error,
|
|
1496
|
+
loginOptions,
|
|
1497
|
+
retry: async (accessToken) => {
|
|
1498
|
+
const detail = await this.dataBridgeClient.getMarketSymbolDetail(accessToken, input);
|
|
1499
|
+
return {
|
|
1500
|
+
status: "connected",
|
|
1501
|
+
connected: true,
|
|
1502
|
+
detail
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
if (recovery) {
|
|
1507
|
+
return recovery;
|
|
1508
|
+
}
|
|
1509
|
+
throw error;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
|
|
1514
|
+
// src/provider-service.ts
|
|
1515
|
+
var HeliosAgentMcpProviderService = class {
|
|
1516
|
+
constructor(options) {
|
|
1517
|
+
__publicField(this, "sessionStore");
|
|
1518
|
+
this.sessionStore = options.sessionStore;
|
|
1519
|
+
}
|
|
1520
|
+
async getActiveProvider() {
|
|
1521
|
+
const provider = await this.sessionStore.getActiveProvider();
|
|
1522
|
+
if (!provider) {
|
|
1523
|
+
return {
|
|
1524
|
+
configured: false,
|
|
1525
|
+
provider: null
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
return {
|
|
1529
|
+
configured: true,
|
|
1530
|
+
provider
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
async setActiveProvider(provider) {
|
|
1534
|
+
await this.sessionStore.setActiveProvider(provider);
|
|
1535
|
+
return {
|
|
1536
|
+
configured: true,
|
|
1537
|
+
provider
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
async resolveProvider(explicitProvider) {
|
|
1541
|
+
if (explicitProvider) {
|
|
1542
|
+
return explicitProvider;
|
|
1543
|
+
}
|
|
1544
|
+
const activeProvider = await this.sessionStore.getActiveProvider();
|
|
1545
|
+
if (activeProvider) {
|
|
1546
|
+
return activeProvider;
|
|
1547
|
+
}
|
|
1548
|
+
throw new Error("No active provider configured. Call helios_provider_set_active or pass provider explicitly.");
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
|
|
1552
|
+
// src/session-store.ts
|
|
1553
|
+
import { chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
1554
|
+
import { homedir } from "os";
|
|
1555
|
+
import path2 from "path";
|
|
1556
|
+
import { z as z4 } from "zod";
|
|
1557
|
+
function getDefaultHeliosAgentMcpSessionPath() {
|
|
1558
|
+
return path2.join(homedir(), ".helios-agent-mcp", "session.json");
|
|
1559
|
+
}
|
|
1560
|
+
var pendingAgentLoginSchema = z4.object({
|
|
1561
|
+
status: z4.literal("pending"),
|
|
1562
|
+
deviceCode: z4.string(),
|
|
1563
|
+
userCode: z4.string(),
|
|
1564
|
+
verificationUri: z4.string().url(),
|
|
1565
|
+
verificationUriComplete: z4.string().url(),
|
|
1566
|
+
expiresInSeconds: z4.number().int().positive(),
|
|
1567
|
+
pollIntervalSeconds: z4.number().int().positive(),
|
|
1568
|
+
requestedScope: z4.array(agentScopeSchema).min(1),
|
|
1569
|
+
startedAt: z4.string().datetime()
|
|
1570
|
+
});
|
|
1571
|
+
var mcpSessionStateSchema = z4.object({
|
|
1572
|
+
session: storedAgentSessionSchema.nullish(),
|
|
1573
|
+
pendingLogin: pendingAgentLoginSchema.nullish(),
|
|
1574
|
+
// A provider pinned before Binance was retired ('binance') reads as unpinned, so the agent must pick again, rather than
|
|
1575
|
+
// failing the whole file (which would drop the session) or silently switching it to Hyperliquid.
|
|
1576
|
+
activeProvider: providerSchema.nullish().catch(null)
|
|
1577
|
+
});
|
|
1578
|
+
var FileAgentSessionStore = class {
|
|
1579
|
+
constructor(filePath = getDefaultHeliosAgentMcpSessionPath()) {
|
|
1580
|
+
this.filePath = filePath;
|
|
1581
|
+
}
|
|
1582
|
+
async getSession() {
|
|
1583
|
+
return (await this.readState()).session;
|
|
1584
|
+
}
|
|
1585
|
+
async getPendingLogin() {
|
|
1586
|
+
return (await this.readState()).pendingLogin;
|
|
1587
|
+
}
|
|
1588
|
+
async getActiveProvider() {
|
|
1589
|
+
return (await this.readState()).activeProvider;
|
|
1590
|
+
}
|
|
1591
|
+
async setSession(session) {
|
|
1592
|
+
const state = await this.readState();
|
|
1593
|
+
await this.writeState({
|
|
1594
|
+
...state,
|
|
1595
|
+
session
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
async setPendingLogin(pendingLogin) {
|
|
1599
|
+
const state = await this.readState();
|
|
1600
|
+
await this.writeState({
|
|
1601
|
+
...state,
|
|
1602
|
+
pendingLogin
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
async setActiveProvider(activeProvider) {
|
|
1606
|
+
const state = await this.readState();
|
|
1607
|
+
await this.writeState({
|
|
1608
|
+
...state,
|
|
1609
|
+
activeProvider
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
async clearSession() {
|
|
1613
|
+
const state = await this.readState();
|
|
1614
|
+
await this.writeStateOrDelete({
|
|
1615
|
+
...state,
|
|
1616
|
+
session: null
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
async clearPendingLogin() {
|
|
1620
|
+
const state = await this.readState();
|
|
1621
|
+
await this.writeStateOrDelete({
|
|
1622
|
+
...state,
|
|
1623
|
+
pendingLogin: null
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
async clearActiveProvider() {
|
|
1627
|
+
const state = await this.readState();
|
|
1628
|
+
await this.writeStateOrDelete({
|
|
1629
|
+
...state,
|
|
1630
|
+
activeProvider: null
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
async readState() {
|
|
1634
|
+
try {
|
|
1635
|
+
const raw = await readFile(this.filePath, "utf8");
|
|
1636
|
+
const parsed = JSON.parse(raw);
|
|
1637
|
+
if (parsed && typeof parsed === "object" && ("session" in parsed || "pendingLogin" in parsed || "activeProvider" in parsed)) {
|
|
1638
|
+
const envelope = mcpSessionStateSchema.safeParse(parsed);
|
|
1639
|
+
if (envelope.success) {
|
|
1640
|
+
return {
|
|
1641
|
+
session: envelope.data.session ?? null,
|
|
1642
|
+
pendingLogin: envelope.data.pendingLogin ?? null,
|
|
1643
|
+
activeProvider: envelope.data.activeProvider ?? null
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
const legacySession = storedAgentSessionSchema.safeParse(parsed);
|
|
1648
|
+
if (legacySession.success) {
|
|
1649
|
+
return {
|
|
1650
|
+
session: legacySession.data,
|
|
1651
|
+
pendingLogin: null,
|
|
1652
|
+
activeProvider: null
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
return {
|
|
1656
|
+
session: null,
|
|
1657
|
+
pendingLogin: null,
|
|
1658
|
+
activeProvider: null
|
|
1659
|
+
};
|
|
1660
|
+
} catch (error) {
|
|
1661
|
+
if (isMissingFileError(error)) {
|
|
1662
|
+
return {
|
|
1663
|
+
session: null,
|
|
1664
|
+
pendingLogin: null,
|
|
1665
|
+
activeProvider: null
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
if (error instanceof Error && error.name === "ZodError") {
|
|
1669
|
+
return {
|
|
1670
|
+
session: null,
|
|
1671
|
+
pendingLogin: null,
|
|
1672
|
+
activeProvider: null
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
throw error;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
async writeState(state) {
|
|
1679
|
+
await mkdir(path2.dirname(this.filePath), { recursive: true });
|
|
1680
|
+
await writeFile(this.filePath, `${JSON.stringify(state, null, 2)}
|
|
1681
|
+
`, { encoding: "utf8", mode: 384 });
|
|
1682
|
+
await chmod(this.filePath, 384).catch(() => void 0);
|
|
1683
|
+
}
|
|
1684
|
+
async writeStateOrDelete(state) {
|
|
1685
|
+
if (!state.session && !state.pendingLogin && !state.activeProvider) {
|
|
1686
|
+
await this.deleteFile();
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
await this.writeState(state);
|
|
1690
|
+
}
|
|
1691
|
+
async deleteFile() {
|
|
1692
|
+
try {
|
|
1693
|
+
await rm(this.filePath, { force: true });
|
|
1694
|
+
} catch (error) {
|
|
1695
|
+
if (!isMissingFileError(error)) {
|
|
1696
|
+
throw error;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
var MemoryAgentMcpSessionStore = class {
|
|
1702
|
+
constructor(session = null, activeProvider = "hyperliquid") {
|
|
1703
|
+
this.session = session;
|
|
1704
|
+
this.activeProvider = activeProvider;
|
|
1705
|
+
__publicField(this, "pendingLogin", null);
|
|
1706
|
+
}
|
|
1707
|
+
async getSession() {
|
|
1708
|
+
return this.session;
|
|
1709
|
+
}
|
|
1710
|
+
async setSession(session) {
|
|
1711
|
+
this.session = session;
|
|
1712
|
+
}
|
|
1713
|
+
async clearSession() {
|
|
1714
|
+
this.session = null;
|
|
1715
|
+
}
|
|
1716
|
+
async getActiveProvider() {
|
|
1717
|
+
return this.activeProvider;
|
|
1718
|
+
}
|
|
1719
|
+
async setActiveProvider(provider) {
|
|
1720
|
+
this.activeProvider = provider;
|
|
1721
|
+
}
|
|
1722
|
+
async clearActiveProvider() {
|
|
1723
|
+
this.activeProvider = null;
|
|
1724
|
+
}
|
|
1725
|
+
async getPendingLogin() {
|
|
1726
|
+
return this.pendingLogin;
|
|
1727
|
+
}
|
|
1728
|
+
async setPendingLogin(pendingLogin) {
|
|
1729
|
+
this.pendingLogin = pendingLogin;
|
|
1730
|
+
}
|
|
1731
|
+
async clearPendingLogin() {
|
|
1732
|
+
this.pendingLogin = null;
|
|
1733
|
+
}
|
|
1734
|
+
};
|
|
1735
|
+
function isMissingFileError(error) {
|
|
1736
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
// src/trading-service.ts
|
|
1740
|
+
import { randomUUID } from "crypto";
|
|
1741
|
+
var HeliosAgentMcpTradingService = class {
|
|
1742
|
+
constructor(options) {
|
|
1743
|
+
__publicField(this, "authService");
|
|
1744
|
+
__publicField(this, "dataBridgeClient");
|
|
1745
|
+
__publicField(this, "providerService");
|
|
1746
|
+
this.authService = options.authService;
|
|
1747
|
+
this.dataBridgeClient = options.dataBridgeClient;
|
|
1748
|
+
this.providerService = options.providerService;
|
|
1749
|
+
}
|
|
1750
|
+
async open(input) {
|
|
1751
|
+
return this.submitIntent("OPEN", input);
|
|
1752
|
+
}
|
|
1753
|
+
async dca(input) {
|
|
1754
|
+
return this.submitIntent("DCA", input);
|
|
1755
|
+
}
|
|
1756
|
+
async reverse(input) {
|
|
1757
|
+
return this.submitIntent("REVERSE", input);
|
|
1758
|
+
}
|
|
1759
|
+
async close(input) {
|
|
1760
|
+
const loginOptions = {
|
|
1761
|
+
clientName: "Helios MCP",
|
|
1762
|
+
scope: [AGENT_SCOPES.ORDERS_WRITE]
|
|
1763
|
+
};
|
|
1764
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1765
|
+
if (login.status !== "connected") {
|
|
1766
|
+
return asPending2(login);
|
|
1767
|
+
}
|
|
1768
|
+
const session = await this.authService.requireSession();
|
|
1769
|
+
try {
|
|
1770
|
+
const provider = await this.providerService.resolveProvider(input.provider);
|
|
1771
|
+
const closure = await this.dataBridgeClient.closePosition(session.accessToken, {
|
|
1772
|
+
...input,
|
|
1773
|
+
provider
|
|
1774
|
+
});
|
|
1775
|
+
return {
|
|
1776
|
+
status: "connected",
|
|
1777
|
+
connected: true,
|
|
1778
|
+
closure
|
|
1779
|
+
};
|
|
1780
|
+
} catch (error) {
|
|
1781
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1782
|
+
authService: this.authService,
|
|
1783
|
+
error,
|
|
1784
|
+
loginOptions,
|
|
1785
|
+
retry: async (accessToken) => {
|
|
1786
|
+
const provider = await this.providerService.resolveProvider(input.provider);
|
|
1787
|
+
const closure = await this.dataBridgeClient.closePosition(accessToken, {
|
|
1788
|
+
...input,
|
|
1789
|
+
provider
|
|
1790
|
+
});
|
|
1791
|
+
return {
|
|
1792
|
+
status: "connected",
|
|
1793
|
+
connected: true,
|
|
1794
|
+
closure
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
});
|
|
1798
|
+
if (recovery) {
|
|
1799
|
+
return recovery;
|
|
1800
|
+
}
|
|
1801
|
+
throw error;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
async setTpSl(input) {
|
|
1805
|
+
const loginOptions = {
|
|
1806
|
+
clientName: "Helios MCP",
|
|
1807
|
+
scope: [AGENT_SCOPES.ORDERS_WRITE]
|
|
1808
|
+
};
|
|
1809
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1810
|
+
if (login.status !== "connected") {
|
|
1811
|
+
return asPending2(login);
|
|
1812
|
+
}
|
|
1813
|
+
const session = await this.authService.requireSession();
|
|
1814
|
+
try {
|
|
1815
|
+
const provider = await this.providerService.resolveProvider(input.provider);
|
|
1816
|
+
const protection = await this.dataBridgeClient.setTpSl(session.accessToken, {
|
|
1817
|
+
...input,
|
|
1818
|
+
provider
|
|
1819
|
+
});
|
|
1820
|
+
return {
|
|
1821
|
+
status: "connected",
|
|
1822
|
+
connected: true,
|
|
1823
|
+
protection
|
|
1824
|
+
};
|
|
1825
|
+
} catch (error) {
|
|
1826
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1827
|
+
authService: this.authService,
|
|
1828
|
+
error,
|
|
1829
|
+
loginOptions,
|
|
1830
|
+
retry: async (accessToken) => {
|
|
1831
|
+
const provider = await this.providerService.resolveProvider(input.provider);
|
|
1832
|
+
const protection = await this.dataBridgeClient.setTpSl(accessToken, {
|
|
1833
|
+
...input,
|
|
1834
|
+
provider
|
|
1835
|
+
});
|
|
1836
|
+
return {
|
|
1837
|
+
status: "connected",
|
|
1838
|
+
connected: true,
|
|
1839
|
+
protection
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
});
|
|
1843
|
+
if (recovery) {
|
|
1844
|
+
return recovery;
|
|
1845
|
+
}
|
|
1846
|
+
throw error;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
async deleteTpSl(input) {
|
|
1850
|
+
const loginOptions = {
|
|
1851
|
+
clientName: "Helios MCP",
|
|
1852
|
+
scope: [AGENT_SCOPES.ORDERS_CANCEL]
|
|
1853
|
+
};
|
|
1854
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1855
|
+
if (login.status !== "connected") {
|
|
1856
|
+
return asPending2(login);
|
|
1857
|
+
}
|
|
1858
|
+
const session = await this.authService.requireSession();
|
|
1859
|
+
try {
|
|
1860
|
+
const provider = await this.providerService.resolveProvider(input.provider);
|
|
1861
|
+
const deletion = await this.dataBridgeClient.deleteTpSl(session.accessToken, {
|
|
1862
|
+
...input,
|
|
1863
|
+
provider
|
|
1864
|
+
});
|
|
1865
|
+
return {
|
|
1866
|
+
status: "connected",
|
|
1867
|
+
connected: true,
|
|
1868
|
+
deletion
|
|
1869
|
+
};
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1872
|
+
authService: this.authService,
|
|
1873
|
+
error,
|
|
1874
|
+
loginOptions,
|
|
1875
|
+
retry: async (accessToken) => {
|
|
1876
|
+
const provider = await this.providerService.resolveProvider(input.provider);
|
|
1877
|
+
const deletion = await this.dataBridgeClient.deleteTpSl(accessToken, {
|
|
1878
|
+
...input,
|
|
1879
|
+
provider
|
|
1880
|
+
});
|
|
1881
|
+
return {
|
|
1882
|
+
status: "connected",
|
|
1883
|
+
connected: true,
|
|
1884
|
+
deletion
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
});
|
|
1888
|
+
if (recovery) {
|
|
1889
|
+
return recovery;
|
|
1890
|
+
}
|
|
1891
|
+
throw error;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
async getOpenOrders() {
|
|
1895
|
+
const loginOptions = {
|
|
1896
|
+
clientName: "Helios MCP",
|
|
1897
|
+
scope: [AGENT_SCOPES.ACCOUNT_READ]
|
|
1898
|
+
};
|
|
1899
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1900
|
+
if (login.status !== "connected") {
|
|
1901
|
+
return asPending2(login);
|
|
1902
|
+
}
|
|
1903
|
+
const session = await this.authService.requireSession();
|
|
1904
|
+
try {
|
|
1905
|
+
const orders = await this.dataBridgeClient.getOpenOrders(session.accessToken);
|
|
1906
|
+
return {
|
|
1907
|
+
status: "connected",
|
|
1908
|
+
connected: true,
|
|
1909
|
+
orders
|
|
1910
|
+
};
|
|
1911
|
+
} catch (error) {
|
|
1912
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1913
|
+
authService: this.authService,
|
|
1914
|
+
error,
|
|
1915
|
+
loginOptions,
|
|
1916
|
+
retry: async (accessToken) => {
|
|
1917
|
+
const orders = await this.dataBridgeClient.getOpenOrders(accessToken);
|
|
1918
|
+
return {
|
|
1919
|
+
status: "connected",
|
|
1920
|
+
connected: true,
|
|
1921
|
+
orders
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
1925
|
+
if (recovery) {
|
|
1926
|
+
return recovery;
|
|
1927
|
+
}
|
|
1928
|
+
throw error;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
async cancelOrder(input) {
|
|
1932
|
+
const loginOptions = {
|
|
1933
|
+
clientName: "Helios MCP",
|
|
1934
|
+
scope: [AGENT_SCOPES.ORDERS_CANCEL]
|
|
1935
|
+
};
|
|
1936
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1937
|
+
if (login.status !== "connected") {
|
|
1938
|
+
return asPending2(login);
|
|
1939
|
+
}
|
|
1940
|
+
const session = await this.authService.requireSession();
|
|
1941
|
+
try {
|
|
1942
|
+
const cancellation = await this.dataBridgeClient.cancelOrder(session.accessToken, input);
|
|
1943
|
+
return {
|
|
1944
|
+
status: "connected",
|
|
1945
|
+
connected: true,
|
|
1946
|
+
cancellation
|
|
1947
|
+
};
|
|
1948
|
+
} catch (error) {
|
|
1949
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1950
|
+
authService: this.authService,
|
|
1951
|
+
error,
|
|
1952
|
+
loginOptions,
|
|
1953
|
+
retry: async (accessToken) => {
|
|
1954
|
+
const cancellation = await this.dataBridgeClient.cancelOrder(accessToken, input);
|
|
1955
|
+
return {
|
|
1956
|
+
status: "connected",
|
|
1957
|
+
connected: true,
|
|
1958
|
+
cancellation
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
});
|
|
1962
|
+
if (recovery) {
|
|
1963
|
+
return recovery;
|
|
1964
|
+
}
|
|
1965
|
+
throw error;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
async submitIntent(intentType, input) {
|
|
1969
|
+
const loginOptions = {
|
|
1970
|
+
clientName: "Helios MCP",
|
|
1971
|
+
scope: [AGENT_SCOPES.ORDERS_WRITE]
|
|
1972
|
+
};
|
|
1973
|
+
const login = await this.authService.ensureLogin(loginOptions);
|
|
1974
|
+
if (login.status !== "connected") {
|
|
1975
|
+
return asPending2(login);
|
|
1976
|
+
}
|
|
1977
|
+
const session = await this.authService.requireSession();
|
|
1978
|
+
const idempotencyKey = input.idempotencyKey ?? randomUUID().replace(/-/g, "");
|
|
1979
|
+
try {
|
|
1980
|
+
const provider = await this.providerService.resolveProvider(input.provider);
|
|
1981
|
+
const submission = await this.dataBridgeClient.submitTradeIntent(session.accessToken, {
|
|
1982
|
+
...input,
|
|
1983
|
+
provider,
|
|
1984
|
+
intentType,
|
|
1985
|
+
idempotencyKey
|
|
1986
|
+
});
|
|
1987
|
+
return {
|
|
1988
|
+
status: "connected",
|
|
1989
|
+
connected: true,
|
|
1990
|
+
submission
|
|
1991
|
+
};
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
const recovery = await retryAfterUnauthorizedRefresh({
|
|
1994
|
+
authService: this.authService,
|
|
1995
|
+
error,
|
|
1996
|
+
loginOptions,
|
|
1997
|
+
retry: async (accessToken) => {
|
|
1998
|
+
const provider = await this.providerService.resolveProvider(input.provider);
|
|
1999
|
+
const submission = await this.dataBridgeClient.submitTradeIntent(accessToken, {
|
|
2000
|
+
...input,
|
|
2001
|
+
provider,
|
|
2002
|
+
intentType,
|
|
2003
|
+
idempotencyKey
|
|
2004
|
+
});
|
|
2005
|
+
return {
|
|
2006
|
+
status: "connected",
|
|
2007
|
+
connected: true,
|
|
2008
|
+
submission
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
});
|
|
2012
|
+
if (recovery) {
|
|
2013
|
+
return recovery;
|
|
2014
|
+
}
|
|
2015
|
+
throw error;
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
};
|
|
2019
|
+
function asPending2(login) {
|
|
2020
|
+
return {
|
|
2021
|
+
...login,
|
|
2022
|
+
status: "pending",
|
|
2023
|
+
connected: false
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// src/tools.ts
|
|
2028
|
+
import { z as z5 } from "zod";
|
|
2029
|
+
var pollLoginInputSchema = z5.object({
|
|
2030
|
+
deviceCode: z5.string().min(1)
|
|
2031
|
+
});
|
|
2032
|
+
var startLoginOutputSchema = z5.object({
|
|
2033
|
+
status: z5.literal("pending"),
|
|
2034
|
+
deviceCode: z5.string(),
|
|
2035
|
+
userCode: z5.string(),
|
|
2036
|
+
verificationUri: z5.string().url(),
|
|
2037
|
+
verificationUriComplete: z5.string().url(),
|
|
2038
|
+
expiresInSeconds: z5.number().int().positive(),
|
|
2039
|
+
pollIntervalSeconds: z5.number().int().positive(),
|
|
2040
|
+
requestedScope: z5.array(agentScopeSchema).min(1),
|
|
2041
|
+
startedAt: z5.string().datetime()
|
|
2042
|
+
});
|
|
2043
|
+
var ensureLoginInputSchema = z5.object({
|
|
2044
|
+
clientName: z5.string().min(1).max(64).optional(),
|
|
2045
|
+
scope: z5.array(agentScopeSchema).min(1).optional(),
|
|
2046
|
+
constraints: agentConstraintsSchema.optional()
|
|
2047
|
+
});
|
|
2048
|
+
var providerSelectionInputSchema = z5.object({
|
|
2049
|
+
provider: providerSchema.optional()
|
|
2050
|
+
});
|
|
2051
|
+
var providerSetInputSchema = z5.object({
|
|
2052
|
+
provider: providerSchema
|
|
2053
|
+
});
|
|
2054
|
+
var activeProviderSchema = z5.object({
|
|
2055
|
+
configured: z5.boolean(),
|
|
2056
|
+
provider: providerSchema.nullable()
|
|
2057
|
+
});
|
|
2058
|
+
var ensureLoginOutputSchema = z5.discriminatedUnion("status", [
|
|
2059
|
+
agentSessionViewSchema.extend({
|
|
2060
|
+
status: z5.literal("connected"),
|
|
2061
|
+
connected: z5.literal(true)
|
|
2062
|
+
}),
|
|
2063
|
+
startLoginOutputSchema.extend({
|
|
2064
|
+
status: z5.literal("pending"),
|
|
2065
|
+
connected: z5.literal(false),
|
|
2066
|
+
message: z5.string()
|
|
2067
|
+
})
|
|
2068
|
+
]);
|
|
2069
|
+
var pollLoginOutputSchema = z5.discriminatedUnion("status", [
|
|
2070
|
+
agentSessionViewSchema.extend({
|
|
2071
|
+
status: z5.literal("connected"),
|
|
2072
|
+
connected: z5.literal(true)
|
|
2073
|
+
}),
|
|
2074
|
+
z5.object({
|
|
2075
|
+
status: z5.enum(["pending", "rejected", "expired", "failed"]),
|
|
2076
|
+
connected: z5.literal(false),
|
|
2077
|
+
message: z5.string()
|
|
2078
|
+
})
|
|
2079
|
+
]);
|
|
2080
|
+
var walletStateToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2081
|
+
z5.object({
|
|
2082
|
+
status: z5.literal("connected"),
|
|
2083
|
+
connected: z5.literal(true),
|
|
2084
|
+
walletState: walletStateSchema
|
|
2085
|
+
}),
|
|
2086
|
+
startLoginOutputSchema.extend({
|
|
2087
|
+
status: z5.literal("pending"),
|
|
2088
|
+
connected: z5.literal(false),
|
|
2089
|
+
message: z5.string()
|
|
2090
|
+
})
|
|
2091
|
+
]);
|
|
2092
|
+
var portfolioToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2093
|
+
z5.object({
|
|
2094
|
+
status: z5.literal("connected"),
|
|
2095
|
+
connected: z5.literal(true),
|
|
2096
|
+
portfolio: portfolioSnapshotSchema
|
|
2097
|
+
}),
|
|
2098
|
+
startLoginOutputSchema.extend({
|
|
2099
|
+
status: z5.literal("pending"),
|
|
2100
|
+
connected: z5.literal(false),
|
|
2101
|
+
message: z5.string()
|
|
2102
|
+
})
|
|
2103
|
+
]);
|
|
2104
|
+
var openPositionsToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2105
|
+
z5.object({
|
|
2106
|
+
status: z5.literal("connected"),
|
|
2107
|
+
connected: z5.literal(true),
|
|
2108
|
+
openPositions: openPositionsSnapshotSchema
|
|
2109
|
+
}),
|
|
2110
|
+
startLoginOutputSchema.extend({
|
|
2111
|
+
status: z5.literal("pending"),
|
|
2112
|
+
connected: z5.literal(false),
|
|
2113
|
+
message: z5.string()
|
|
2114
|
+
})
|
|
2115
|
+
]);
|
|
2116
|
+
var ledgerFillsToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2117
|
+
z5.object({
|
|
2118
|
+
status: z5.literal("connected"),
|
|
2119
|
+
connected: z5.literal(true),
|
|
2120
|
+
fills: ledgerFillsPageSchema
|
|
2121
|
+
}),
|
|
2122
|
+
startLoginOutputSchema.extend({
|
|
2123
|
+
status: z5.literal("pending"),
|
|
2124
|
+
connected: z5.literal(false),
|
|
2125
|
+
message: z5.string()
|
|
2126
|
+
})
|
|
2127
|
+
]);
|
|
2128
|
+
var ledgerSummaryToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2129
|
+
z5.object({
|
|
2130
|
+
status: z5.literal("connected"),
|
|
2131
|
+
connected: z5.literal(true),
|
|
2132
|
+
summary: ledgerSummarySchema
|
|
2133
|
+
}),
|
|
2134
|
+
startLoginOutputSchema.extend({
|
|
2135
|
+
status: z5.literal("pending"),
|
|
2136
|
+
connected: z5.literal(false),
|
|
2137
|
+
message: z5.string()
|
|
2138
|
+
})
|
|
2139
|
+
]);
|
|
2140
|
+
var marketSnapshotToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2141
|
+
z5.object({
|
|
2142
|
+
status: z5.literal("connected"),
|
|
2143
|
+
connected: z5.literal(true),
|
|
2144
|
+
snapshot: marketSnapshotSchema
|
|
2145
|
+
}),
|
|
2146
|
+
startLoginOutputSchema.extend({
|
|
2147
|
+
status: z5.literal("pending"),
|
|
2148
|
+
connected: z5.literal(false),
|
|
2149
|
+
message: z5.string()
|
|
2150
|
+
})
|
|
2151
|
+
]);
|
|
2152
|
+
var marketSnapshotChunkToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2153
|
+
z5.object({
|
|
2154
|
+
status: z5.literal("connected"),
|
|
2155
|
+
connected: z5.literal(true),
|
|
2156
|
+
snapshotChunk: marketSnapshotChunkSchema
|
|
2157
|
+
}),
|
|
2158
|
+
startLoginOutputSchema.extend({
|
|
2159
|
+
status: z5.literal("pending"),
|
|
2160
|
+
connected: z5.literal(false),
|
|
2161
|
+
message: z5.string()
|
|
2162
|
+
})
|
|
2163
|
+
]);
|
|
2164
|
+
var marketSymbolDetailToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2165
|
+
z5.object({
|
|
2166
|
+
status: z5.literal("connected"),
|
|
2167
|
+
connected: z5.literal(true),
|
|
2168
|
+
detail: marketSymbolDetailSchema
|
|
2169
|
+
}),
|
|
2170
|
+
startLoginOutputSchema.extend({
|
|
2171
|
+
status: z5.literal("pending"),
|
|
2172
|
+
connected: z5.literal(false),
|
|
2173
|
+
message: z5.string()
|
|
2174
|
+
})
|
|
2175
|
+
]);
|
|
2176
|
+
var tradeActionInputSchema = submitTradeIntentInputSchema.omit({
|
|
2177
|
+
intentType: true
|
|
2178
|
+
});
|
|
2179
|
+
var tradeActionToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2180
|
+
z5.object({
|
|
2181
|
+
status: z5.literal("connected"),
|
|
2182
|
+
connected: z5.literal(true),
|
|
2183
|
+
submission: submitTradeIntentResultSchema
|
|
2184
|
+
}),
|
|
2185
|
+
startLoginOutputSchema.extend({
|
|
2186
|
+
status: z5.literal("pending"),
|
|
2187
|
+
connected: z5.literal(false),
|
|
2188
|
+
message: z5.string()
|
|
2189
|
+
})
|
|
2190
|
+
]);
|
|
2191
|
+
var closeTradeToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2192
|
+
z5.object({
|
|
2193
|
+
status: z5.literal("connected"),
|
|
2194
|
+
connected: z5.literal(true),
|
|
2195
|
+
closure: closePositionResultSchema
|
|
2196
|
+
}),
|
|
2197
|
+
startLoginOutputSchema.extend({
|
|
2198
|
+
status: z5.literal("pending"),
|
|
2199
|
+
connected: z5.literal(false),
|
|
2200
|
+
message: z5.string()
|
|
2201
|
+
})
|
|
2202
|
+
]);
|
|
2203
|
+
var setTpSlToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2204
|
+
z5.object({
|
|
2205
|
+
status: z5.literal("connected"),
|
|
2206
|
+
connected: z5.literal(true),
|
|
2207
|
+
protection: setTpSlResultSchema
|
|
2208
|
+
}),
|
|
2209
|
+
startLoginOutputSchema.extend({
|
|
2210
|
+
status: z5.literal("pending"),
|
|
2211
|
+
connected: z5.literal(false),
|
|
2212
|
+
message: z5.string()
|
|
2213
|
+
})
|
|
2214
|
+
]);
|
|
2215
|
+
var deleteTpSlToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2216
|
+
z5.object({
|
|
2217
|
+
status: z5.literal("connected"),
|
|
2218
|
+
connected: z5.literal(true),
|
|
2219
|
+
deletion: deleteTpSlResultSchema
|
|
2220
|
+
}),
|
|
2221
|
+
startLoginOutputSchema.extend({
|
|
2222
|
+
status: z5.literal("pending"),
|
|
2223
|
+
connected: z5.literal(false),
|
|
2224
|
+
message: z5.string()
|
|
2225
|
+
})
|
|
2226
|
+
]);
|
|
2227
|
+
var openOrdersToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2228
|
+
z5.object({
|
|
2229
|
+
status: z5.literal("connected"),
|
|
2230
|
+
connected: z5.literal(true),
|
|
2231
|
+
orders: z5.array(openOrderSchema)
|
|
2232
|
+
}),
|
|
2233
|
+
startLoginOutputSchema.extend({
|
|
2234
|
+
status: z5.literal("pending"),
|
|
2235
|
+
connected: z5.literal(false),
|
|
2236
|
+
message: z5.string()
|
|
2237
|
+
})
|
|
2238
|
+
]);
|
|
2239
|
+
var cancelOrderToolOutputSchema = z5.discriminatedUnion("status", [
|
|
2240
|
+
z5.object({
|
|
2241
|
+
status: z5.literal("connected"),
|
|
2242
|
+
connected: z5.literal(true),
|
|
2243
|
+
cancellation: cancelOrderResultSchema
|
|
2244
|
+
}),
|
|
2245
|
+
startLoginOutputSchema.extend({
|
|
2246
|
+
status: z5.literal("pending"),
|
|
2247
|
+
connected: z5.literal(false),
|
|
2248
|
+
message: z5.string()
|
|
2249
|
+
})
|
|
2250
|
+
]);
|
|
2251
|
+
var documentPathInputSchema = z5.object({
|
|
2252
|
+
path: z5.string().min(1)
|
|
2253
|
+
});
|
|
2254
|
+
var docsIndexSchema = z5.object({
|
|
2255
|
+
root: z5.literal("docs"),
|
|
2256
|
+
documents: z5.array(
|
|
2257
|
+
z5.object({
|
|
2258
|
+
path: z5.string(),
|
|
2259
|
+
section: z5.string(),
|
|
2260
|
+
title: z5.string(),
|
|
2261
|
+
bytes: z5.number().int().nonnegative()
|
|
2262
|
+
})
|
|
2263
|
+
)
|
|
2264
|
+
});
|
|
2265
|
+
var documentSchema = z5.object({
|
|
2266
|
+
path: z5.string(),
|
|
2267
|
+
section: z5.string(),
|
|
2268
|
+
title: z5.string(),
|
|
2269
|
+
body: z5.string(),
|
|
2270
|
+
bytes: z5.number().int().nonnegative()
|
|
2271
|
+
});
|
|
2272
|
+
function createHeliosDocsToolDefinitions(service) {
|
|
2273
|
+
return [
|
|
2274
|
+
{
|
|
2275
|
+
name: "helios_docs_get_index",
|
|
2276
|
+
title: "Get Helios Docs Index",
|
|
2277
|
+
description: "Lists the package-local Helios MCP and indicator .txt documentation available to the agent.",
|
|
2278
|
+
inputSchema: z5.object({}),
|
|
2279
|
+
async execute() {
|
|
2280
|
+
const result = docsIndexSchema.parse(await service.getIndex());
|
|
2281
|
+
return {
|
|
2282
|
+
content: [
|
|
2283
|
+
{
|
|
2284
|
+
type: "text",
|
|
2285
|
+
text: `Helios docs index: ${result.documents.length} documents across ${new Set(result.documents.map((document) => document.section)).size} sections.`
|
|
2286
|
+
}
|
|
2287
|
+
],
|
|
2288
|
+
structuredContent: result
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
},
|
|
2292
|
+
{
|
|
2293
|
+
name: "helios_docs_get_document",
|
|
2294
|
+
title: "Get Helios Doc",
|
|
2295
|
+
description: "Returns one package-local Helios .txt document by relative docs path.",
|
|
2296
|
+
inputSchema: documentPathInputSchema,
|
|
2297
|
+
async execute(input) {
|
|
2298
|
+
const result = documentSchema.parse(await service.getDocument(input));
|
|
2299
|
+
return {
|
|
2300
|
+
content: [
|
|
2301
|
+
{
|
|
2302
|
+
type: "text",
|
|
2303
|
+
text: result.body
|
|
2304
|
+
}
|
|
2305
|
+
],
|
|
2306
|
+
structuredContent: result
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
];
|
|
2311
|
+
}
|
|
2312
|
+
function createHeliosAuthToolDefinitions(service) {
|
|
2313
|
+
return [
|
|
2314
|
+
{
|
|
2315
|
+
name: "helios_auth_start_login",
|
|
2316
|
+
title: "Start Helios Login",
|
|
2317
|
+
description: "Starts the delegated Helios login flow and returns the approval URL for the user.",
|
|
2318
|
+
inputSchema: agentDeviceStartInputSchema,
|
|
2319
|
+
async execute(input) {
|
|
2320
|
+
const result = startLoginOutputSchema.parse(await service.startLogin(input));
|
|
2321
|
+
const manualResult = {
|
|
2322
|
+
...result,
|
|
2323
|
+
...withManualLoginFields(result)
|
|
2324
|
+
};
|
|
2325
|
+
return {
|
|
2326
|
+
content: [
|
|
2327
|
+
{
|
|
2328
|
+
type: "text",
|
|
2329
|
+
text: buildManualLoginText(result)
|
|
2330
|
+
}
|
|
2331
|
+
],
|
|
2332
|
+
structuredContent: manualResult
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
},
|
|
2336
|
+
{
|
|
2337
|
+
name: "helios_auth_ensure_login",
|
|
2338
|
+
title: "Ensure Helios Login",
|
|
2339
|
+
description: "Ensures a delegated Helios session exists, or starts a login flow and returns a ready-to-display approval prompt.",
|
|
2340
|
+
inputSchema: ensureLoginInputSchema,
|
|
2341
|
+
async execute(input) {
|
|
2342
|
+
const result = ensureLoginOutputSchema.parse(await service.ensureLogin(input));
|
|
2343
|
+
const structuredContent = result.status === "connected" ? result : {
|
|
2344
|
+
...result,
|
|
2345
|
+
...withManualLoginFields(result)
|
|
2346
|
+
};
|
|
2347
|
+
return {
|
|
2348
|
+
content: [
|
|
2349
|
+
{
|
|
2350
|
+
type: "text",
|
|
2351
|
+
text: result.status === "connected" ? `Connected to Helios as user ${result.ownerUserId}.` : result.message
|
|
2352
|
+
}
|
|
2353
|
+
],
|
|
2354
|
+
structuredContent
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
},
|
|
2358
|
+
{
|
|
2359
|
+
name: "helios_auth_poll_login",
|
|
2360
|
+
title: "Poll Helios Login",
|
|
2361
|
+
description: "Polls the device flow until the delegated Helios login is approved or rejected.",
|
|
2362
|
+
inputSchema: pollLoginInputSchema,
|
|
2363
|
+
async execute(input) {
|
|
2364
|
+
const result = pollLoginOutputSchema.parse(await service.pollLogin(input.deviceCode));
|
|
2365
|
+
return {
|
|
2366
|
+
content: [
|
|
2367
|
+
{
|
|
2368
|
+
type: "text",
|
|
2369
|
+
text: result.status === "connected" ? `Connected to Helios as user ${result.ownerUserId}.` : `Helios login is ${result.status}: ${result.message}`
|
|
2370
|
+
}
|
|
2371
|
+
],
|
|
2372
|
+
structuredContent: result
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
},
|
|
2376
|
+
{
|
|
2377
|
+
name: "helios_auth_whoami",
|
|
2378
|
+
title: "Helios Login Status",
|
|
2379
|
+
description: "Returns the delegated Helios session state currently stored by the MCP server.",
|
|
2380
|
+
inputSchema: z5.object({}),
|
|
2381
|
+
async execute() {
|
|
2382
|
+
const result = agentSessionViewSchema.parse(await service.whoami());
|
|
2383
|
+
return {
|
|
2384
|
+
content: [
|
|
2385
|
+
{
|
|
2386
|
+
type: "text",
|
|
2387
|
+
text: result.connected ? `Connected to Helios as user ${result.ownerUserId}.` : "No delegated Helios session is connected."
|
|
2388
|
+
}
|
|
2389
|
+
],
|
|
2390
|
+
structuredContent: result
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
];
|
|
2395
|
+
}
|
|
2396
|
+
function createHeliosProviderToolDefinitions(service) {
|
|
2397
|
+
return [
|
|
2398
|
+
{
|
|
2399
|
+
name: "helios_provider_get_active",
|
|
2400
|
+
title: "Get Helios Active Provider",
|
|
2401
|
+
description: "Returns the provider currently pinned for this MCP session, if any.",
|
|
2402
|
+
inputSchema: z5.object({}),
|
|
2403
|
+
async execute() {
|
|
2404
|
+
const result = activeProviderSchema.parse(await service.getActiveProvider());
|
|
2405
|
+
return {
|
|
2406
|
+
content: [
|
|
2407
|
+
{
|
|
2408
|
+
type: "text",
|
|
2409
|
+
text: result.configured ? `Helios active provider is ${result.provider}.` : "No Helios active provider is configured for this session."
|
|
2410
|
+
}
|
|
2411
|
+
],
|
|
2412
|
+
structuredContent: result
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
},
|
|
2416
|
+
{
|
|
2417
|
+
name: "helios_provider_set_active",
|
|
2418
|
+
title: "Set Helios Active Provider",
|
|
2419
|
+
description: "Pins the active provider for this MCP session so portfolio and trading tools can omit provider.",
|
|
2420
|
+
inputSchema: providerSetInputSchema,
|
|
2421
|
+
async execute(input) {
|
|
2422
|
+
const result = activeProviderSchema.parse(await service.setActiveProvider(input.provider));
|
|
2423
|
+
return {
|
|
2424
|
+
content: [
|
|
2425
|
+
{
|
|
2426
|
+
type: "text",
|
|
2427
|
+
text: `Helios active provider set to ${result.provider}.`
|
|
2428
|
+
}
|
|
2429
|
+
],
|
|
2430
|
+
structuredContent: result
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
];
|
|
2435
|
+
}
|
|
2436
|
+
function createHeliosAccountToolDefinitions(service) {
|
|
2437
|
+
return [
|
|
2438
|
+
{
|
|
2439
|
+
name: "helios_account_get_wallet_state",
|
|
2440
|
+
title: "Get Helios Wallet State",
|
|
2441
|
+
description: "Returns the current Hyperliquid wallet snapshot for the connected Helios account.",
|
|
2442
|
+
inputSchema: z5.object({}),
|
|
2443
|
+
async execute() {
|
|
2444
|
+
const result = walletStateToolOutputSchema.parse(await service.getWalletState());
|
|
2445
|
+
const structuredContent = result.status === "connected" ? result : {
|
|
2446
|
+
...result,
|
|
2447
|
+
...withManualLoginFields(result)
|
|
2448
|
+
};
|
|
2449
|
+
return {
|
|
2450
|
+
content: [
|
|
2451
|
+
{
|
|
2452
|
+
type: "text",
|
|
2453
|
+
text: result.status === "connected" ? `Helios wallet state: available balance ${result.walletState.availableBalance}, account value ${result.walletState.accountValue}.` : result.message
|
|
2454
|
+
}
|
|
2455
|
+
],
|
|
2456
|
+
structuredContent
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
},
|
|
2460
|
+
{
|
|
2461
|
+
name: "helios_portfolio_get",
|
|
2462
|
+
title: "Get Helios Portfolio",
|
|
2463
|
+
description: "Returns a provider-specific portfolio snapshot. Provider may be omitted if one is already pinned with helios_provider_set_active.",
|
|
2464
|
+
inputSchema: providerSelectionInputSchema,
|
|
2465
|
+
async execute(input) {
|
|
2466
|
+
const result = portfolioToolOutputSchema.parse(await service.getPortfolio(input));
|
|
2467
|
+
const structuredContent = result.status === "connected" ? result : {
|
|
2468
|
+
...result,
|
|
2469
|
+
...withManualLoginFields(result)
|
|
2470
|
+
};
|
|
2471
|
+
return {
|
|
2472
|
+
content: [
|
|
2473
|
+
{
|
|
2474
|
+
type: "text",
|
|
2475
|
+
text: result.status === "connected" ? buildPortfolioText(result.portfolio) : result.message
|
|
2476
|
+
}
|
|
2477
|
+
],
|
|
2478
|
+
structuredContent
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
];
|
|
2483
|
+
}
|
|
2484
|
+
function createHeliosLedgerToolDefinitions(service) {
|
|
2485
|
+
return [
|
|
2486
|
+
{
|
|
2487
|
+
name: "helios_positions_get_open",
|
|
2488
|
+
title: "Get Helios Open Positions",
|
|
2489
|
+
description: "Returns the current open Hyperliquid positions for the connected Helios account.",
|
|
2490
|
+
inputSchema: z5.object({}),
|
|
2491
|
+
async execute() {
|
|
2492
|
+
const result = openPositionsToolOutputSchema.parse(await service.getOpenPositions());
|
|
2493
|
+
const structuredContent = result.status === "connected" ? result : {
|
|
2494
|
+
...result,
|
|
2495
|
+
...withManualLoginFields(result)
|
|
2496
|
+
};
|
|
2497
|
+
return {
|
|
2498
|
+
content: [
|
|
2499
|
+
{
|
|
2500
|
+
type: "text",
|
|
2501
|
+
text: result.status === "connected" ? `Helios open positions: ${result.openPositions.positions.length} positions, total unrealized PnL ${result.openPositions.totalUnrealizedPnl}.` : result.message
|
|
2502
|
+
}
|
|
2503
|
+
],
|
|
2504
|
+
structuredContent
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
},
|
|
2508
|
+
{
|
|
2509
|
+
name: "helios_ledger_get_fills",
|
|
2510
|
+
title: "Get Helios Ledger Fills",
|
|
2511
|
+
description: "Returns recent Hyperliquid fills/trade history for the connected Helios account.",
|
|
2512
|
+
inputSchema: ledgerFillsInputSchema,
|
|
2513
|
+
async execute(input) {
|
|
2514
|
+
const result = ledgerFillsToolOutputSchema.parse(await service.getFills(input));
|
|
2515
|
+
const structuredContent = result.status === "connected" ? result : {
|
|
2516
|
+
...result,
|
|
2517
|
+
...withManualLoginFields(result)
|
|
2518
|
+
};
|
|
2519
|
+
return {
|
|
2520
|
+
content: [
|
|
2521
|
+
{
|
|
2522
|
+
type: "text",
|
|
2523
|
+
text: result.status === "connected" ? `Helios ledger fills: ${result.fills.fills.length} fills from ${result.fills.window}${result.fills.hasMore ? ", more available" : ""}.` : result.message
|
|
2524
|
+
}
|
|
2525
|
+
],
|
|
2526
|
+
structuredContent
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
},
|
|
2530
|
+
{
|
|
2531
|
+
name: "helios_ledger_get_summary",
|
|
2532
|
+
title: "Get Helios Ledger Summary",
|
|
2533
|
+
description: "Returns realized PnL, fees, and win/loss metrics over a recent Hyperliquid fill window.",
|
|
2534
|
+
inputSchema: ledgerSummaryInputSchema,
|
|
2535
|
+
async execute(input) {
|
|
2536
|
+
const result = ledgerSummaryToolOutputSchema.parse(await service.getSummary(input));
|
|
2537
|
+
const structuredContent = result.status === "connected" ? result : {
|
|
2538
|
+
...result,
|
|
2539
|
+
...withManualLoginFields(result)
|
|
2540
|
+
};
|
|
2541
|
+
return {
|
|
2542
|
+
content: [
|
|
2543
|
+
{
|
|
2544
|
+
type: "text",
|
|
2545
|
+
text: result.status === "connected" ? `Helios ledger summary (${result.summary.window}): realized PnL ${result.summary.realizedPnl}, fees ${result.summary.totalFees}, wins ${result.summary.winCount}, losses ${result.summary.lossCount}.` : result.message
|
|
2546
|
+
}
|
|
2547
|
+
],
|
|
2548
|
+
structuredContent
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
];
|
|
2553
|
+
}
|
|
2554
|
+
function createHeliosMarketToolDefinitions(service) {
|
|
2555
|
+
return [
|
|
2556
|
+
{
|
|
2557
|
+
name: "helios_market_get_snapshot",
|
|
2558
|
+
title: "Get Helios Market Snapshot",
|
|
2559
|
+
description: "Returns a compact market snapshot for the selected, all, or explicitly requested symbols.",
|
|
2560
|
+
inputSchema: marketSnapshotInputSchema,
|
|
2561
|
+
async execute(input) {
|
|
2562
|
+
const result = marketSnapshotToolOutputSchema.parse(await service.getSnapshot(input));
|
|
2563
|
+
const structuredContent = result.status === "connected" ? result : {
|
|
2564
|
+
...result,
|
|
2565
|
+
...withManualLoginFields(result)
|
|
2566
|
+
};
|
|
2567
|
+
return {
|
|
2568
|
+
content: [
|
|
2569
|
+
{
|
|
2570
|
+
type: "text",
|
|
2571
|
+
text: result.status === "connected" ? `Helios market snapshot: ${result.snapshot.symbolCount} symbols (${result.snapshot.scope}) at ${result.snapshot.generatedAt}. Timeframe return fields are already percentages; do not multiply by 100. Null means unavailable, not zero.` : result.message
|
|
2572
|
+
}
|
|
2573
|
+
],
|
|
2574
|
+
structuredContent
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
},
|
|
2578
|
+
{
|
|
2579
|
+
name: "helios_market_get_snapshot_chunk",
|
|
2580
|
+
title: "Get Helios Market Snapshot Chunk",
|
|
2581
|
+
description: "Returns one chunk of the Helios market snapshot for batch analysis when the full snapshot is too large.",
|
|
2582
|
+
inputSchema: marketSnapshotChunkInputSchema,
|
|
2583
|
+
async execute(input) {
|
|
2584
|
+
const result = marketSnapshotChunkToolOutputSchema.parse(await service.getSnapshotChunk(input));
|
|
2585
|
+
const structuredContent = result.status === "connected" ? result : {
|
|
2586
|
+
...result,
|
|
2587
|
+
...withManualLoginFields(result)
|
|
2588
|
+
};
|
|
2589
|
+
return {
|
|
2590
|
+
content: [
|
|
2591
|
+
{
|
|
2592
|
+
type: "text",
|
|
2593
|
+
text: result.status === "connected" ? `Helios market snapshot chunk: ${result.snapshotChunk.chunkSymbolCount}/${result.snapshotChunk.totalSymbolCount} symbols at offset ${result.snapshotChunk.offset} (limit ${result.snapshotChunk.limit}). ${result.snapshotChunk.hasMore ? `Read next chunk at offset ${result.snapshotChunk.nextOffset}.` : "No more chunks remain."} Timeframe return fields are already percentages; do not multiply by 100. Null means unavailable, not zero.` : result.message
|
|
2594
|
+
}
|
|
2595
|
+
],
|
|
2596
|
+
structuredContent
|
|
2597
|
+
};
|
|
2598
|
+
}
|
|
2599
|
+
},
|
|
2600
|
+
{
|
|
2601
|
+
name: "helios_market_get_symbol_detail",
|
|
2602
|
+
title: "Get Helios Symbol Detail",
|
|
2603
|
+
description: "Returns expanded market detail for one symbol from the current Helios market snapshot.",
|
|
2604
|
+
inputSchema: marketSymbolDetailInputSchema,
|
|
2605
|
+
async execute(input) {
|
|
2606
|
+
const result = marketSymbolDetailToolOutputSchema.parse(await service.getSymbolDetail(input));
|
|
2607
|
+
const structuredContent = result.status === "connected" ? result : {
|
|
2608
|
+
...result,
|
|
2609
|
+
...withManualLoginFields(result)
|
|
2610
|
+
};
|
|
2611
|
+
return {
|
|
2612
|
+
content: [
|
|
2613
|
+
{
|
|
2614
|
+
type: "text",
|
|
2615
|
+
text: result.status === "connected" ? `Helios symbol detail: ${result.detail.symbol} at ${result.detail.generatedAt}. Timeframe return fields are already percentages; do not multiply by 100. Null means unavailable, not zero.` : result.message
|
|
2616
|
+
}
|
|
2617
|
+
],
|
|
2618
|
+
structuredContent
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
];
|
|
2623
|
+
}
|
|
2624
|
+
function createHeliosTradingToolDefinitions(service) {
|
|
2625
|
+
return [
|
|
2626
|
+
{
|
|
2627
|
+
name: "helios_trading_open",
|
|
2628
|
+
title: "Open Helios Position",
|
|
2629
|
+
description: "Submits a Helios OPEN trading intent. Provider may be omitted if one is already pinned with helios_provider_set_active.",
|
|
2630
|
+
inputSchema: tradeActionInputSchema,
|
|
2631
|
+
async execute(input) {
|
|
2632
|
+
const result = tradeActionToolOutputSchema.parse(await service.open(input));
|
|
2633
|
+
return {
|
|
2634
|
+
content: [
|
|
2635
|
+
{
|
|
2636
|
+
type: "text",
|
|
2637
|
+
text: result.status === "connected" ? `Submitted Helios OPEN intent for ${result.submission.provider ?? input.provider} ${result.submission.symbol} ${result.submission.direction} with ${result.submission.execution.style} execution.` : result.message
|
|
2638
|
+
}
|
|
2639
|
+
],
|
|
2640
|
+
structuredContent: result.status === "connected" ? result : {
|
|
2641
|
+
...result,
|
|
2642
|
+
...withManualLoginFields(result)
|
|
2643
|
+
}
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
},
|
|
2647
|
+
{
|
|
2648
|
+
name: "helios_trading_dca",
|
|
2649
|
+
title: "DCA Helios Position",
|
|
2650
|
+
description: "Submits a Helios DCA trading intent. Provider may be omitted if one is already pinned with helios_provider_set_active.",
|
|
2651
|
+
inputSchema: tradeActionInputSchema,
|
|
2652
|
+
async execute(input) {
|
|
2653
|
+
const result = tradeActionToolOutputSchema.parse(await service.dca(input));
|
|
2654
|
+
return {
|
|
2655
|
+
content: [
|
|
2656
|
+
{
|
|
2657
|
+
type: "text",
|
|
2658
|
+
text: result.status === "connected" ? `Submitted Helios DCA intent for ${result.submission.provider ?? input.provider} ${result.submission.symbol} ${result.submission.direction} with ${result.submission.execution.style} execution.` : result.message
|
|
2659
|
+
}
|
|
2660
|
+
],
|
|
2661
|
+
structuredContent: result.status === "connected" ? result : {
|
|
2662
|
+
...result,
|
|
2663
|
+
...withManualLoginFields(result)
|
|
2664
|
+
}
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
},
|
|
2668
|
+
{
|
|
2669
|
+
name: "helios_trading_reverse",
|
|
2670
|
+
title: "Reverse Helios Position",
|
|
2671
|
+
description: "Submits a Helios REVERSE trading intent. Provider may be omitted if one is already pinned with helios_provider_set_active.",
|
|
2672
|
+
inputSchema: tradeActionInputSchema,
|
|
2673
|
+
async execute(input) {
|
|
2674
|
+
const result = tradeActionToolOutputSchema.parse(await service.reverse(input));
|
|
2675
|
+
return {
|
|
2676
|
+
content: [
|
|
2677
|
+
{
|
|
2678
|
+
type: "text",
|
|
2679
|
+
text: result.status === "connected" ? `Submitted Helios REVERSE intent for ${result.submission.provider ?? input.provider} ${result.submission.symbol} ${result.submission.direction} with ${result.submission.execution.style} execution.` : result.message
|
|
2680
|
+
}
|
|
2681
|
+
],
|
|
2682
|
+
structuredContent: result.status === "connected" ? result : {
|
|
2683
|
+
...result,
|
|
2684
|
+
...withManualLoginFields(result)
|
|
2685
|
+
}
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
},
|
|
2689
|
+
{
|
|
2690
|
+
name: "helios_trading_close",
|
|
2691
|
+
title: "Close Helios Position",
|
|
2692
|
+
description: "Closes the full Helios position using the dedicated backend close route. Provider may be omitted if one is already pinned with helios_provider_set_active.",
|
|
2693
|
+
inputSchema: closePositionInputSchema,
|
|
2694
|
+
async execute(input) {
|
|
2695
|
+
const result = closeTradeToolOutputSchema.parse(await service.close(input));
|
|
2696
|
+
return {
|
|
2697
|
+
content: [
|
|
2698
|
+
{
|
|
2699
|
+
type: "text",
|
|
2700
|
+
text: result.status === "connected" ? `Submitted Helios full close for ${result.closure.provider ?? input.provider} ${result.closure.symbol}.` : result.message
|
|
2701
|
+
}
|
|
2702
|
+
],
|
|
2703
|
+
structuredContent: result.status === "connected" ? result : {
|
|
2704
|
+
...result,
|
|
2705
|
+
...withManualLoginFields(result)
|
|
2706
|
+
}
|
|
2707
|
+
};
|
|
2708
|
+
}
|
|
2709
|
+
},
|
|
2710
|
+
{
|
|
2711
|
+
name: "helios_positions_set_tp_sl",
|
|
2712
|
+
title: "Set Helios TP/SL",
|
|
2713
|
+
description: "Creates or replaces take-profit and stop-loss protection for an existing Helios position. Provider may be omitted if one is already pinned with helios_provider_set_active.",
|
|
2714
|
+
inputSchema: setTpSlInputSchema,
|
|
2715
|
+
async execute(input) {
|
|
2716
|
+
const result = setTpSlToolOutputSchema.parse(await service.setTpSl(input));
|
|
2717
|
+
return {
|
|
2718
|
+
content: [
|
|
2719
|
+
{
|
|
2720
|
+
type: "text",
|
|
2721
|
+
text: result.status === "connected" ? `Submitted ${result.protection.provider ?? input.provider} TP/SL protection update for ${result.protection.symbol}.` : result.message
|
|
2722
|
+
}
|
|
2723
|
+
],
|
|
2724
|
+
structuredContent: result.status === "connected" ? result : {
|
|
2725
|
+
...result,
|
|
2726
|
+
...withManualLoginFields(result)
|
|
2727
|
+
}
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
},
|
|
2731
|
+
{
|
|
2732
|
+
name: "helios_positions_delete_tp_sl",
|
|
2733
|
+
title: "Delete Helios TP/SL",
|
|
2734
|
+
description: "Deletes an existing Helios take-profit or stop-loss order by order id. Provider may be omitted if one is already pinned with helios_provider_set_active.",
|
|
2735
|
+
inputSchema: deleteTpSlInputSchema,
|
|
2736
|
+
async execute(input) {
|
|
2737
|
+
const result = deleteTpSlToolOutputSchema.parse(await service.deleteTpSl(input));
|
|
2738
|
+
return {
|
|
2739
|
+
content: [
|
|
2740
|
+
{
|
|
2741
|
+
type: "text",
|
|
2742
|
+
text: result.status === "connected" ? `Deleted ${result.deletion.provider ?? input.provider} TP/SL order ${result.deletion.orderId} for ${result.deletion.symbol}.` : result.message
|
|
2743
|
+
}
|
|
2744
|
+
],
|
|
2745
|
+
structuredContent: result.status === "connected" ? result : {
|
|
2746
|
+
...result,
|
|
2747
|
+
...withManualLoginFields(result)
|
|
2748
|
+
}
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
},
|
|
2752
|
+
{
|
|
2753
|
+
name: "helios_orders_get_open",
|
|
2754
|
+
title: "Get Helios Open Orders",
|
|
2755
|
+
description: "Lists the currently open Hyperliquid orders for the connected account.",
|
|
2756
|
+
inputSchema: z5.object({}),
|
|
2757
|
+
async execute() {
|
|
2758
|
+
const result = openOrdersToolOutputSchema.parse(await service.getOpenOrders());
|
|
2759
|
+
return {
|
|
2760
|
+
content: [
|
|
2761
|
+
{
|
|
2762
|
+
type: "text",
|
|
2763
|
+
text: result.status === "connected" ? `Retrieved ${result.orders.length} open Helios orders.` : result.message
|
|
2764
|
+
}
|
|
2765
|
+
],
|
|
2766
|
+
structuredContent: result.status === "connected" ? result : {
|
|
2767
|
+
...result,
|
|
2768
|
+
...withManualLoginFields(result)
|
|
2769
|
+
}
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
},
|
|
2773
|
+
{
|
|
2774
|
+
name: "helios_orders_cancel",
|
|
2775
|
+
title: "Cancel Helios Order",
|
|
2776
|
+
description: "Cancels an open Hyperliquid order by symbol and order id.",
|
|
2777
|
+
inputSchema: cancelOrderInputSchema,
|
|
2778
|
+
async execute(input) {
|
|
2779
|
+
const result = cancelOrderToolOutputSchema.parse(await service.cancelOrder(input));
|
|
2780
|
+
return {
|
|
2781
|
+
content: [
|
|
2782
|
+
{
|
|
2783
|
+
type: "text",
|
|
2784
|
+
text: result.status === "connected" ? `Cancelled Helios order ${result.cancellation.oid} for ${result.cancellation.symbol}.` : result.message
|
|
2785
|
+
}
|
|
2786
|
+
],
|
|
2787
|
+
structuredContent: result.status === "connected" ? result : {
|
|
2788
|
+
...result,
|
|
2789
|
+
...withManualLoginFields(result)
|
|
2790
|
+
}
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
];
|
|
2795
|
+
}
|
|
2796
|
+
function withManualLoginFields(result) {
|
|
2797
|
+
return {
|
|
2798
|
+
actionRequired: true,
|
|
2799
|
+
loginUrl: result.verificationUriComplete,
|
|
2800
|
+
nextStep: "Open the login URL in your browser, approve Helios access, then ask me to continue."
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
function buildPortfolioText(result) {
|
|
2804
|
+
return `Helios Hyperliquid portfolio: ${result.positions.length} positions, available balance ${result.availableBalance}, total balance ${result.totalBalance}.`;
|
|
2805
|
+
}
|
|
2806
|
+
function buildManualLoginText(result) {
|
|
2807
|
+
return `Log in to Helios by opening this URL in your browser:
|
|
2808
|
+
|
|
2809
|
+
${result.verificationUriComplete}
|
|
2810
|
+
|
|
2811
|
+
User code: ${result.userCode}
|
|
2812
|
+
|
|
2813
|
+
After approving access, ask me to continue and I will finish connecting.`;
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
// src/server.ts
|
|
2817
|
+
async function createHeliosAgentMcpServer(options) {
|
|
2818
|
+
if (!options.identityServiceUrl) {
|
|
2819
|
+
throw new Error("identityServiceUrl is required");
|
|
2820
|
+
}
|
|
2821
|
+
if (!options.dataBridgeUrl) {
|
|
2822
|
+
throw new Error("dataBridgeUrl is required");
|
|
2823
|
+
}
|
|
2824
|
+
const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js");
|
|
2825
|
+
const server = new McpServer({
|
|
2826
|
+
name: "helios",
|
|
2827
|
+
version: "0.1.0"
|
|
2828
|
+
});
|
|
2829
|
+
const sessionStore = options.sessionStore ?? new FileAgentSessionStore(options.sessionPath ?? getDefaultHeliosAgentMcpSessionPath());
|
|
2830
|
+
const client = new HeliosAgentAuthClient({
|
|
2831
|
+
baseUrl: options.identityServiceUrl,
|
|
2832
|
+
fetch: options.fetch
|
|
2833
|
+
});
|
|
2834
|
+
const dataBridgeClient = new HeliosAgentDataBridgeClient({
|
|
2835
|
+
baseUrl: options.dataBridgeUrl,
|
|
2836
|
+
fetch: options.fetch
|
|
2837
|
+
});
|
|
2838
|
+
const authService = new HeliosAgentMcpAuthService({
|
|
2839
|
+
client,
|
|
2840
|
+
sessionStore,
|
|
2841
|
+
refreshWindowMs: options.refreshWindowMs,
|
|
2842
|
+
deviceLogin: !options.hosted
|
|
2843
|
+
});
|
|
2844
|
+
const providerService = new HeliosAgentMcpProviderService({
|
|
2845
|
+
sessionStore
|
|
2846
|
+
});
|
|
2847
|
+
const accountService = new HeliosAgentMcpAccountService({
|
|
2848
|
+
authService,
|
|
2849
|
+
dataBridgeClient,
|
|
2850
|
+
providerService
|
|
2851
|
+
});
|
|
2852
|
+
const marketService = new HeliosAgentMcpMarketService({
|
|
2853
|
+
authService,
|
|
2854
|
+
dataBridgeClient
|
|
2855
|
+
});
|
|
2856
|
+
const ledgerService = new HeliosAgentMcpLedgerService({
|
|
2857
|
+
authService,
|
|
2858
|
+
dataBridgeClient
|
|
2859
|
+
});
|
|
2860
|
+
const tradingService = new HeliosAgentMcpTradingService({
|
|
2861
|
+
authService,
|
|
2862
|
+
dataBridgeClient,
|
|
2863
|
+
providerService
|
|
2864
|
+
});
|
|
2865
|
+
const docsService = new HeliosAgentMcpDocsService({
|
|
2866
|
+
docsRoot: options.docsRoot ?? getDefaultHeliosAgentMcpDocsRoot()
|
|
2867
|
+
});
|
|
2868
|
+
const docsTools = createHeliosDocsToolDefinitions(docsService);
|
|
2869
|
+
const authTools = createHeliosAuthToolDefinitions(authService);
|
|
2870
|
+
const providerTools = createHeliosProviderToolDefinitions(providerService);
|
|
2871
|
+
const accountTools = createHeliosAccountToolDefinitions(accountService);
|
|
2872
|
+
const ledgerTools = createHeliosLedgerToolDefinitions(ledgerService);
|
|
2873
|
+
const marketTools = createHeliosMarketToolDefinitions(marketService);
|
|
2874
|
+
const tradingTools = createHeliosTradingToolDefinitions(tradingService);
|
|
2875
|
+
registerTool(server, docsTools[0]);
|
|
2876
|
+
registerTool(server, docsTools[1]);
|
|
2877
|
+
if (!options.hosted) {
|
|
2878
|
+
registerTool(server, authTools[0]);
|
|
2879
|
+
registerTool(server, authTools[1]);
|
|
2880
|
+
registerTool(server, authTools[2]);
|
|
2881
|
+
}
|
|
2882
|
+
registerTool(server, authTools[3]);
|
|
2883
|
+
registerTool(server, providerTools[0]);
|
|
2884
|
+
registerTool(server, providerTools[1]);
|
|
2885
|
+
registerTool(server, accountTools[0]);
|
|
2886
|
+
registerTool(server, accountTools[1]);
|
|
2887
|
+
registerTool(server, ledgerTools[0]);
|
|
2888
|
+
registerTool(server, ledgerTools[1]);
|
|
2889
|
+
registerTool(server, ledgerTools[2]);
|
|
2890
|
+
registerTool(server, marketTools[0]);
|
|
2891
|
+
registerTool(server, marketTools[1]);
|
|
2892
|
+
registerTool(server, marketTools[2]);
|
|
2893
|
+
registerTool(server, tradingTools[0]);
|
|
2894
|
+
registerTool(server, tradingTools[1]);
|
|
2895
|
+
registerTool(server, tradingTools[2]);
|
|
2896
|
+
registerTool(server, tradingTools[3]);
|
|
2897
|
+
registerTool(server, tradingTools[4]);
|
|
2898
|
+
registerTool(server, tradingTools[5]);
|
|
2899
|
+
registerTool(server, tradingTools[6]);
|
|
2900
|
+
registerTool(server, tradingTools[7]);
|
|
2901
|
+
return {
|
|
2902
|
+
server,
|
|
2903
|
+
authService,
|
|
2904
|
+
providerService,
|
|
2905
|
+
accountService,
|
|
2906
|
+
docsService,
|
|
2907
|
+
ledgerService,
|
|
2908
|
+
marketService,
|
|
2909
|
+
tradingService,
|
|
2910
|
+
sessionStore
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
function registerTool(server, tool) {
|
|
2914
|
+
server.registerTool(
|
|
2915
|
+
tool.name,
|
|
2916
|
+
{
|
|
2917
|
+
title: tool.title,
|
|
2918
|
+
description: tool.description,
|
|
2919
|
+
inputSchema: tool.inputSchema
|
|
2920
|
+
},
|
|
2921
|
+
async (input) => {
|
|
2922
|
+
const result = await tool.execute(tool.inputSchema.parse(input));
|
|
2923
|
+
return {
|
|
2924
|
+
...result,
|
|
2925
|
+
structuredContent: result.structuredContent
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
);
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
export {
|
|
2932
|
+
HOSTED_RECONNECT_MESSAGE,
|
|
2933
|
+
HeliosAgentMcpAuthService,
|
|
2934
|
+
retryAfterUnauthorizedRefresh,
|
|
2935
|
+
formatHeliosLoginPrompt,
|
|
2936
|
+
HeliosAgentMcpAccountService,
|
|
2937
|
+
HeliosAgentMcpDocsService,
|
|
2938
|
+
getDefaultHeliosAgentMcpDocsRoot,
|
|
2939
|
+
HeliosAgentMcpLedgerService,
|
|
2940
|
+
HeliosAgentMcpMarketService,
|
|
2941
|
+
HeliosAgentMcpProviderService,
|
|
2942
|
+
getDefaultHeliosAgentMcpSessionPath,
|
|
2943
|
+
pendingAgentLoginSchema,
|
|
2944
|
+
FileAgentSessionStore,
|
|
2945
|
+
MemoryAgentMcpSessionStore,
|
|
2946
|
+
HeliosAgentMcpTradingService,
|
|
2947
|
+
createHeliosDocsToolDefinitions,
|
|
2948
|
+
createHeliosAuthToolDefinitions,
|
|
2949
|
+
createHeliosProviderToolDefinitions,
|
|
2950
|
+
createHeliosAccountToolDefinitions,
|
|
2951
|
+
createHeliosLedgerToolDefinitions,
|
|
2952
|
+
createHeliosMarketToolDefinitions,
|
|
2953
|
+
createHeliosTradingToolDefinitions,
|
|
2954
|
+
createHeliosAgentMcpServer
|
|
2955
|
+
};
|