@pgnio/helios-mcp 0.2.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4274 @@
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
+ // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.31_tsx@4.20.6_typescript@5.9.3_yaml@2.9.1/node_modules/tsup/assets/esm_shims.js
6
+ import path from "path";
7
+ import { fileURLToPath } from "url";
8
+ var getFilename = () => fileURLToPath(import.meta.url);
9
+ var getDirname = () => path.dirname(getFilename());
10
+ var __dirname = /* @__PURE__ */ getDirname();
11
+
12
+ // src/endpoints.ts
13
+ var DEFAULT_HELIOS_BASE_URL = "https://helios.pgn.io";
14
+ var DEFAULT_HELIOS_SESSION_ISSUER = {
15
+ identityServiceUrl: DEFAULT_HELIOS_BASE_URL,
16
+ dataBridgeUrl: DEFAULT_HELIOS_BASE_URL
17
+ };
18
+ var LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
19
+ function assertHeliosBaseUrl(name, value) {
20
+ let url;
21
+ try {
22
+ url = new URL(value);
23
+ } catch {
24
+ throw new Error(`${name} must be a URL, got ${JSON.stringify(value)}`);
25
+ }
26
+ if (url.protocol === "https:") {
27
+ return value;
28
+ }
29
+ if (url.protocol === "http:" && LOOPBACK_HOSTNAMES.has(url.hostname)) {
30
+ return value;
31
+ }
32
+ throw new Error(`${name} must use https (plain http only for localhost, 127.0.0.1 or [::1]), got ${url.origin}`);
33
+ }
34
+ function isSameHeliosIssuer(stored, configured) {
35
+ const issuer = stored ?? DEFAULT_HELIOS_SESSION_ISSUER;
36
+ return normalizeBaseUrl(issuer.identityServiceUrl) === normalizeBaseUrl(configured.identityServiceUrl) && normalizeBaseUrl(issuer.dataBridgeUrl) === normalizeBaseUrl(configured.dataBridgeUrl);
37
+ }
38
+ function normalizeBaseUrl(value) {
39
+ try {
40
+ const url = new URL(value);
41
+ return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
42
+ } catch {
43
+ return value.replace(/\/+$/, "");
44
+ }
45
+ }
46
+
47
+ // ../agent/dist/index.mjs
48
+ import { z as z4 } from "zod";
49
+ import { z } from "zod";
50
+ import { z as z2 } from "zod";
51
+ import { z as z3 } from "zod";
52
+ var __defProp2 = Object.defineProperty;
53
+ var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
54
+ var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
55
+ var VERIFIED_AGENT_APPS = [
56
+ { name: "Claude", redirects: ["https://claude.ai/api/mcp/auth_callback", "https://claude.com/api/mcp/auth_callback"] },
57
+ { name: "ChatGPT", redirects: ["https://chatgpt.com/connector_platform_oauth_redirect"] },
58
+ { name: "Cursor", redirects: ["https://www.cursor.com/agents/mcp/oauth/callback"] }
59
+ ];
60
+ var RESERVED_AGENT_APP_NAMES = [
61
+ ...VERIFIED_AGENT_APPS.map((app) => app.name),
62
+ "VS Code",
63
+ "Helios",
64
+ "Wagmi",
65
+ "Ares",
66
+ "OpenAI",
67
+ "Anthropic",
68
+ "Grok"
69
+ ];
70
+ var heliosAgentErrorResponseSchema = z.object({
71
+ error: z.string().optional(),
72
+ errorDescription: z.string().optional(),
73
+ message: z.string().optional()
74
+ });
75
+ var HeliosAgentApiError = class extends Error {
76
+ constructor(options) {
77
+ super(options.message);
78
+ __publicField2(this, "status");
79
+ __publicField2(this, "code");
80
+ __publicField2(this, "errorDescription");
81
+ __publicField2(this, "payload");
82
+ this.name = "HeliosAgentApiError";
83
+ this.status = options.status;
84
+ this.code = options.code;
85
+ this.errorDescription = options.errorDescription;
86
+ this.payload = options.payload;
87
+ if (options.cause !== void 0) {
88
+ Object.defineProperty(this, "cause", {
89
+ configurable: true,
90
+ enumerable: false,
91
+ value: options.cause,
92
+ writable: true
93
+ });
94
+ }
95
+ }
96
+ };
97
+ function isHeliosAgentApiError(error) {
98
+ return error instanceof HeliosAgentApiError;
99
+ }
100
+ var AGENT_SCOPES = {
101
+ ACCOUNT_READ: "account:read",
102
+ MARKET_READ: "market:read",
103
+ ORDERS_WRITE: "orders:write",
104
+ ORDERS_CANCEL: "orders:cancel"
105
+ };
106
+ var AGENT_SCOPE_VALUES = [
107
+ AGENT_SCOPES.ACCOUNT_READ,
108
+ AGENT_SCOPES.MARKET_READ,
109
+ AGENT_SCOPES.ORDERS_WRITE,
110
+ AGENT_SCOPES.ORDERS_CANCEL
111
+ ];
112
+ var agentScopeSchema = z2.enum(AGENT_SCOPE_VALUES);
113
+ var AGENT_TRADING_SCOPES = [AGENT_SCOPES.ORDERS_WRITE, AGENT_SCOPES.ORDERS_CANCEL];
114
+ var agentGrantStatusSchema = z2.enum(["active", "revoked"]);
115
+ var agentDeviceRequestStatusSchema = z2.enum(["pending", "approved", "rejected", "exchanged", "expired"]);
116
+ var AGENT_CONSTRAINTS_MAX_KEYS = 16;
117
+ var agentConstraintsSchema = z2.record(z2.string().max(64), z2.unknown()).refine((value) => Object.keys(value).length <= AGENT_CONSTRAINTS_MAX_KEYS, {
118
+ message: `At most ${AGENT_CONSTRAINTS_MAX_KEYS} constraints`
119
+ }).default({});
120
+ var agentGrantMetadataSchema = z2.object({
121
+ id: z2.string(),
122
+ userId: z2.string(),
123
+ clientName: z2.string(),
124
+ status: agentGrantStatusSchema,
125
+ scope: z2.array(agentScopeSchema).min(1),
126
+ constraints: agentConstraintsSchema,
127
+ tokenVersion: z2.number().int().nonnegative(),
128
+ createdAt: z2.string(),
129
+ approvedAt: z2.string().optional(),
130
+ lastUsedAt: z2.string().optional(),
131
+ revokedAt: z2.string().optional()
132
+ });
133
+ var AGENT_START_SECRET_HEADER = "x-helios-agent-start-secret";
134
+ var AGENT_CLIENT_IP_HEADER = "x-helios-client-ip";
135
+ var agentDeviceStartInputSchema = z2.object({
136
+ clientName: z2.string().trim().min(1).max(64),
137
+ scope: z2.array(agentScopeSchema).min(1),
138
+ constraints: agentConstraintsSchema.optional(),
139
+ /** The app was matched to a known client by the hosted MCP. Ignored unless the start carries the start secret. */
140
+ verified: z2.boolean().optional(),
141
+ /** The approval answers a one-time proof the device must send with the token exchange (the hosted MCP's redirect). */
142
+ requireApprovalProof: z2.boolean().optional()
143
+ });
144
+ var agentDeviceStartOutputSchema = z2.object({
145
+ deviceCode: z2.string(),
146
+ userCode: z2.string(),
147
+ verificationUri: z2.string().url(),
148
+ verificationUriComplete: z2.string().url(),
149
+ expiresInSeconds: z2.number().int().positive(),
150
+ pollIntervalSeconds: z2.number().int().positive()
151
+ });
152
+ var agentDeviceTokenInputSchema = z2.object({
153
+ deviceCode: z2.string().min(1),
154
+ /** Required when the login was started with requireApprovalProof: the proof the approval answered with. */
155
+ approvalProof: z2.string().min(1).max(128).optional()
156
+ });
157
+ var agentDeviceRequestViewSchema = z2.object({
158
+ userCode: z2.string(),
159
+ clientName: z2.string(),
160
+ scope: z2.array(agentScopeSchema).min(1),
161
+ constraints: agentConstraintsSchema,
162
+ status: agentDeviceRequestStatusSchema,
163
+ expiresAt: z2.string(),
164
+ /** False when the app's name wasn't checked by Helios (anyone can start a login under any name). */
165
+ verified: z2.boolean().optional(),
166
+ /** The signed-in viewer already has an active connection under this name, which approving would replace. */
167
+ replacesExisting: z2.boolean().optional(),
168
+ /** Where the login was started, as identity saw the device/start call. Absent from older identity builds. */
169
+ startedFrom: z2.object({
170
+ ip: z2.string().nullable(),
171
+ userAgent: z2.string().nullable(),
172
+ startedAt: z2.string()
173
+ }).optional()
174
+ });
175
+ var agentDeviceApproveInputSchema = z2.object({
176
+ /** The person confirmed replacing their existing connection of the same name. */
177
+ replaceExisting: z2.boolean().optional(),
178
+ /**
179
+ * The scopes the person allowed: a non-empty subset of the request's scope (identity answers 400 invalid_scope
180
+ * otherwise). The grant and its tokens get exactly these. Absent: everything the request asked for.
181
+ */
182
+ scope: z2.array(agentScopeSchema).min(1).max(AGENT_SCOPE_VALUES.length).optional(),
183
+ /**
184
+ * The person confirmed they trust an unverified app to trade. Required (else 409 trading_acknowledgement_required)
185
+ * when the login is unverified and the approved scope includes a trading scope (AGENT_TRADING_SCOPES).
186
+ */
187
+ acknowledgeTrading: z2.boolean().optional()
188
+ });
189
+ var agentTokenResponseSchema = z2.object({
190
+ accessToken: z2.string(),
191
+ refreshToken: z2.string(),
192
+ tokenType: z2.literal("Bearer"),
193
+ expiresInSeconds: z2.number().int().positive(),
194
+ scope: z2.array(agentScopeSchema).min(1),
195
+ ownerUserId: z2.string(),
196
+ agentGrantId: z2.string()
197
+ });
198
+ var agentRefreshInputSchema = z2.object({
199
+ refreshToken: z2.string().min(1)
200
+ });
201
+ var agentApprovalResponseSchema = z2.object({
202
+ request: agentDeviceRequestViewSchema,
203
+ grant: agentGrantMetadataSchema,
204
+ /** Present when the login asked for one: hand it back to the device, which sends it with the token exchange. */
205
+ approvalProof: z2.string().optional()
206
+ });
207
+ var agentSessionStateSchema = z2.object({
208
+ status: z2.enum(["pending", "authorized", "rejected", "revoked"]),
209
+ request: agentDeviceRequestViewSchema.optional(),
210
+ grant: agentGrantMetadataSchema.optional()
211
+ });
212
+ var providerSchema = z2.enum(["hyperliquid"]);
213
+ var walletStateSchema = z2.object({
214
+ provider: z2.literal("hyperliquid"),
215
+ accountValue: z2.number(),
216
+ availableBalance: z2.number(),
217
+ withdrawable: z2.number(),
218
+ totalMarginUsed: z2.number(),
219
+ totalNotionalPosition: z2.number(),
220
+ crossMarginAccountValue: z2.number(),
221
+ isActivated: z2.boolean(),
222
+ fetchedAt: z2.string().datetime()
223
+ });
224
+ var openPositionSchema = z2.object({
225
+ symbol: z2.string(),
226
+ side: z2.enum(["LONG", "SHORT"]),
227
+ size: z2.number(),
228
+ entryPrice: z2.number(),
229
+ markPrice: z2.number(),
230
+ unrealizedPnl: z2.number(),
231
+ margin: z2.number(),
232
+ leverage: z2.number(),
233
+ liquidationPrice: z2.number().optional()
234
+ });
235
+ var openPositionsSnapshotSchema = z2.object({
236
+ provider: z2.literal("hyperliquid"),
237
+ fetchedAt: z2.string().datetime(),
238
+ totalUnrealizedPnl: z2.number(),
239
+ positions: z2.array(openPositionSchema)
240
+ });
241
+ var protectiveOrderKindSchema = z2.enum(["STOP_LOSS", "TAKE_PROFIT"]);
242
+ var protectiveOrderSideSchema = z2.enum(["BUY", "SELL"]);
243
+ var protectiveOrderPositionSideSchema = z2.enum(["BOTH", "LONG", "SHORT"]);
244
+ var protectiveOrderSchema = z2.object({
245
+ /** Helios's record id: what helios_positions_delete_tp_sl takes (the Hyperliquid order id works there too). */
246
+ id: z2.string(),
247
+ /** The Hyperliquid order id (named after the retired Binance field). */
248
+ binanceOrderId: z2.number().int().nonnegative().nullish(),
249
+ clientOrderId: z2.string().nullish(),
250
+ symbol: z2.string(),
251
+ side: protectiveOrderSideSchema,
252
+ positionSide: protectiveOrderPositionSideSchema.nullish().transform((value) => value ?? "BOTH"),
253
+ status: z2.string(),
254
+ kind: protectiveOrderKindSchema,
255
+ groupId: z2.string().nullish(),
256
+ stopPrice: z2.union([z2.string(), z2.number()]).nullish().transform((value) => value == null ? value : String(value)),
257
+ parentClientOrderId: z2.string().nullish(),
258
+ assetId: z2.number().int().nonnegative().nullish(),
259
+ createdAt: z2.string().nullish()
260
+ });
261
+ var portfolioSnapshotSchema = z2.object({
262
+ provider: z2.literal("hyperliquid"),
263
+ totalBalance: z2.number(),
264
+ availableBalance: z2.number(),
265
+ totalMargin: z2.number(),
266
+ unrealizedPnl: z2.number(),
267
+ positions: z2.array(openPositionSchema),
268
+ /** Unreadable entries are dropped (see readProtectiveOrders) rather than failing the read. */
269
+ protectiveOrders: z2.array(protectiveOrderSchema)
270
+ });
271
+ var portfolioInputSchema = z2.object({
272
+ provider: providerSchema
273
+ });
274
+ var ledgerWindowSchema = z2.enum(["24h", "7d", "30d"]);
275
+ var ledgerFillsInputSchema = z2.object({
276
+ window: ledgerWindowSchema.optional(),
277
+ limit: z2.number().int().positive().max(200).optional(),
278
+ before: z2.number().int().nonnegative().optional()
279
+ });
280
+ var ledgerFillSchema = z2.object({
281
+ coin: z2.string(),
282
+ px: z2.number(),
283
+ sz: z2.number(),
284
+ side: z2.enum(["B", "A"]),
285
+ time: z2.number().int().nonnegative(),
286
+ dir: z2.string(),
287
+ closedPnl: z2.number(),
288
+ fee: z2.number(),
289
+ crossed: z2.boolean(),
290
+ oid: z2.number().int().nonnegative(),
291
+ tid: z2.number().int().nonnegative()
292
+ });
293
+ var ledgerFillsPageSchema = z2.object({
294
+ window: ledgerWindowSchema,
295
+ limit: z2.number().int().positive(),
296
+ before: z2.number().int().nonnegative().optional(),
297
+ hasMore: z2.boolean(),
298
+ nextBefore: z2.number().int().nonnegative().optional(),
299
+ fetchedAt: z2.string().datetime(),
300
+ fills: z2.array(ledgerFillSchema)
301
+ });
302
+ var ledgerPeriodSummarySchema = z2.object({
303
+ realizedPnl: z2.number(),
304
+ totalFees: z2.number(),
305
+ fillCount: z2.number().int().nonnegative(),
306
+ closeCount: z2.number().int().nonnegative(),
307
+ winCount: z2.number().int().nonnegative(),
308
+ lossCount: z2.number().int().nonnegative(),
309
+ averageWin: z2.number(),
310
+ averageLoss: z2.number()
311
+ });
312
+ var ledgerSummaryInputSchema = z2.object({
313
+ window: ledgerWindowSchema.optional()
314
+ });
315
+ var ledgerSummarySchema = ledgerPeriodSummarySchema.extend({
316
+ window: ledgerWindowSchema,
317
+ generatedAt: z2.string().datetime(),
318
+ periods: z2.object({
319
+ "24h": ledgerPeriodSummarySchema,
320
+ "7d": ledgerPeriodSummarySchema
321
+ })
322
+ });
323
+ var marketSnapshotScopeSchema = z2.enum(["selected", "all"]);
324
+ var marketSnapshotResolvedScopeSchema = z2.enum(["selected", "all", "explicit"]);
325
+ var marketSnapshotValueSchema = z2.union([z2.string(), z2.number(), z2.boolean(), z2.null()]);
326
+ var marketSnapshotInputSchema = z2.object({
327
+ scope: marketSnapshotScopeSchema.optional(),
328
+ symbols: z2.array(z2.string().min(1)).min(1).optional(),
329
+ fields: z2.array(z2.string().min(1)).min(1).optional().describe("Table columns to return; sym and base always come back. Ids: rank, lp, bv, qv, ts, r1m, r3m, r5m, r15m, r30m, r1h, r2h, r4h, r6h, r8h, r12h, r24h, r3d, r7d, hmi, hmiv, reg, ob, de, hfi, ar, ms, vcc, vcp, obosd, obosp, vwap, vwz, poc, pocd, vap, macdh, macds, macdd, srs, srr, fh24, fl24, fh7, fl7, sr, az, hfiw, trv, tri, trh, hs."),
330
+ sort: z2.enum(["rank"]).optional().describe('"rank" orders rows by market cap rank, unranked last.')
331
+ });
332
+ var marketSnapshotChunkInputSchema = marketSnapshotInputSchema.extend({
333
+ offset: z2.number().int().nonnegative().optional(),
334
+ limit: z2.number().int().positive().max(100).optional()
335
+ });
336
+ var marketFieldBlockSchema = z2.object({
337
+ fieldIds: z2.array(z2.string()).min(1),
338
+ values: z2.array(marketSnapshotValueSchema),
339
+ symbolLists: z2.record(z2.string(), z2.array(z2.string())).optional()
340
+ });
341
+ var marketMatrixSchema = z2.object({
342
+ fieldIds: z2.array(z2.string()).min(1),
343
+ rows: z2.array(z2.array(marketSnapshotValueSchema))
344
+ });
345
+ var marketFieldSemanticsSchema = z2.object({
346
+ nullMeans: z2.literal("unavailable"),
347
+ percentScale: z2.literal("already_percent"),
348
+ percentFields: z2.array(z2.string()).min(1),
349
+ changeMapPercentKeys: z2.array(z2.string()).min(1)
350
+ });
351
+ var marketSnapshotSchema = z2.object({
352
+ schema: z2.literal("helios.market_snapshot.v1"),
353
+ view: z2.literal("market_overview"),
354
+ scope: marketSnapshotResolvedScopeSchema,
355
+ symbolCount: z2.number().int().nonnegative(),
356
+ generatedAt: z2.string().datetime(),
357
+ returnedSymbols: z2.array(z2.string()),
358
+ semantics: marketFieldSemanticsSchema,
359
+ market: marketFieldBlockSchema,
360
+ table: marketMatrixSchema,
361
+ legend: z2.record(z2.string(), z2.union([z2.string(), z2.array(z2.string())]))
362
+ });
363
+ var marketSnapshotChunkSchema = z2.object({
364
+ schema: z2.literal("helios.market_snapshot_chunk.v1"),
365
+ view: z2.literal("market_overview_chunk"),
366
+ scope: marketSnapshotResolvedScopeSchema,
367
+ totalSymbolCount: z2.number().int().nonnegative(),
368
+ generatedAt: z2.string().datetime(),
369
+ offset: z2.number().int().nonnegative(),
370
+ limit: z2.number().int().positive(),
371
+ chunkSymbolCount: z2.number().int().nonnegative(),
372
+ hasMore: z2.boolean(),
373
+ nextOffset: z2.number().int().nonnegative().optional(),
374
+ returnedSymbols: z2.array(z2.string()),
375
+ semantics: marketFieldSemanticsSchema,
376
+ market: marketFieldBlockSchema,
377
+ table: marketMatrixSchema,
378
+ legend: z2.record(z2.string(), z2.union([z2.string(), z2.array(z2.string())]))
379
+ });
380
+ var marketSymbolDetailInputSchema = z2.object({
381
+ symbol: z2.string().min(1),
382
+ full: z2.boolean().optional().describe("true adds every raw indicator and the price history (large, ~25 KB). Omit for the compact row and market context.")
383
+ });
384
+ var marketSymbolDetailSchema = z2.object({
385
+ schema: z2.literal("helios.market_symbol_detail.v1"),
386
+ generatedAt: z2.string().datetime(),
387
+ symbol: z2.string(),
388
+ semantics: marketFieldSemanticsSchema,
389
+ market: marketFieldBlockSchema,
390
+ row: z2.record(z2.string(), marketSnapshotValueSchema),
391
+ symbolCtx: z2.record(z2.string(), marketSnapshotValueSchema),
392
+ changeMap: z2.record(z2.string(), z2.number().nullable()),
393
+ metrics: z2.record(z2.string(), z2.unknown()).nullable(),
394
+ legend: z2.record(z2.string(), z2.union([z2.string(), z2.array(z2.string())]))
395
+ });
396
+ var tradingDirectionSchema = z2.enum(["LONG", "SHORT"]);
397
+ var tradingExecutionStyleSchema = z2.enum(["MARKET", "LIMIT_TAKER", "LIMIT_MAKER", "LIMIT_GTC"]);
398
+ var tradingSizeSchema = z2.object({
399
+ mode: z2.enum(["MARGIN_USD", "BASE_QUANTITY"]),
400
+ value: z2.number().positive()
401
+ });
402
+ var tradingProtectionLegSchema = z2.object({
403
+ kind: z2.enum(["LIMIT", "MARKET"]),
404
+ triggerPrice: z2.number().positive(),
405
+ limitPrice: z2.number().positive().optional()
406
+ });
407
+ var tradingProtectionSchema = z2.object({
408
+ takeProfit: tradingProtectionLegSchema.optional(),
409
+ stopLoss: tradingProtectionLegSchema.optional()
410
+ });
411
+ var tradingSymbolSchema = z2.string().regex(/^(?:[a-zA-Z0-9]{1,10}:)?[a-zA-Z0-9]+(?:USDT)?$/);
412
+ var tradeIntentTypeSchema = z2.enum(["OPEN", "DCA", "REVERSE", "CLOSE"]);
413
+ var submitTradeIntentInputSchema = z2.object({
414
+ provider: providerSchema.optional(),
415
+ symbol: tradingSymbolSchema,
416
+ intentType: tradeIntentTypeSchema,
417
+ direction: tradingDirectionSchema,
418
+ size: tradingSizeSchema,
419
+ execution: z2.object({
420
+ style: tradingExecutionStyleSchema,
421
+ timeInForce: z2.enum(["GTC", "IOC", "FOK", "GTX"]).optional(),
422
+ limitPrice: z2.number().positive().optional()
423
+ }),
424
+ maxSlippageBps: z2.number().int().nonnegative().max(1e3).optional(),
425
+ leverageOverride: z2.number().positive().optional(),
426
+ marginModeOverride: z2.enum(["ISOLATED", "CROSS"]).optional(),
427
+ reduceOnly: z2.boolean().optional(),
428
+ protection: tradingProtectionSchema.optional(),
429
+ /** One per user action, reused on retries: the data-bridge never places a second order for the same key. */
430
+ idempotencyKey: z2.string().regex(/^[A-Za-z0-9_-]{8,64}$/).optional(),
431
+ /** With it, idempotencyKey is the order's fingerprint: repeats within this many seconds of the first are one order. */
432
+ idempotencyWindowSeconds: z2.number().int().min(10).max(600).optional()
433
+ });
434
+ var submitTradeIntentResultSchema = z2.object({
435
+ status: z2.literal("submitted"),
436
+ /** `replayed`: an earlier attempt of this action already placed the order; nothing new was sent. */
437
+ outcome: z2.enum(["placed", "replayed"]).optional(),
438
+ clientOrderId: z2.string().optional(),
439
+ orderStatus: z2.string().optional(),
440
+ /** TP/SL legs Hyperliquid refused while the entry went through: the position is open without them. */
441
+ rejectedProtection: z2.array(z2.object({ kind: z2.string(), reason: z2.string() })).optional(),
442
+ provider: providerSchema.optional(),
443
+ symbol: z2.string(),
444
+ intentType: tradeIntentTypeSchema,
445
+ direction: tradingDirectionSchema,
446
+ size: tradingSizeSchema,
447
+ execution: z2.object({
448
+ style: tradingExecutionStyleSchema,
449
+ timeInForce: z2.enum(["GTC", "IOC", "FOK", "GTX"]).optional(),
450
+ limitPrice: z2.number().positive().optional()
451
+ }),
452
+ maxSlippageBps: z2.number().int().nonnegative().max(1e3).optional(),
453
+ leverageOverride: z2.number().positive().optional(),
454
+ marginModeOverride: z2.enum(["ISOLATED", "CROSS"]).optional(),
455
+ reduceOnly: z2.boolean(),
456
+ hasProtection: z2.boolean()
457
+ });
458
+ var closePositionInputSchema = z2.object({
459
+ provider: providerSchema.optional(),
460
+ symbol: tradingSymbolSchema,
461
+ maxSlippageBps: z2.number().int().nonnegative().max(1e3).optional(),
462
+ /** The share of the position to close (0 < fraction < 1 closes part of it); omitted or 1 closes it all. */
463
+ fraction: z2.number().positive().max(1).optional()
464
+ });
465
+ var closePositionResultSchema = z2.object({
466
+ status: z2.literal("submitted"),
467
+ /** `no_position`: nothing was open on this coin, so no order was sent. Absent from older data-bridges. */
468
+ outcome: z2.enum(["closed", "no_position"]).optional(),
469
+ provider: providerSchema.optional(),
470
+ symbol: z2.string(),
471
+ mode: z2.enum(["FULL", "PARTIAL"]),
472
+ fraction: z2.number().positive().max(1).optional(),
473
+ maxSlippageBps: z2.number().int().nonnegative().max(1e3).optional()
474
+ });
475
+ var setTpSlInputSchema = z2.object({
476
+ provider: providerSchema.optional(),
477
+ symbol: tradingSymbolSchema,
478
+ applyTo: z2.enum(["FULL_POSITION", "CUSTOM_QUANTITY"]),
479
+ quantity: z2.number().positive().optional(),
480
+ parentClientOrderId: z2.string(),
481
+ takeProfit: tradingProtectionLegSchema.optional(),
482
+ stopLoss: tradingProtectionLegSchema.optional()
483
+ });
484
+ var setTpSlResultSchema = z2.object({
485
+ status: z2.literal("submitted"),
486
+ provider: providerSchema.optional(),
487
+ symbol: z2.string(),
488
+ applyTo: z2.enum(["FULL_POSITION", "CUSTOM_QUANTITY"]),
489
+ quantity: z2.number().positive().optional(),
490
+ parentClientOrderId: z2.string(),
491
+ hasTakeProfit: z2.boolean(),
492
+ hasStopLoss: z2.boolean()
493
+ });
494
+ var deleteTpSlInputSchema = z2.object({
495
+ provider: providerSchema.optional(),
496
+ symbol: tradingSymbolSchema,
497
+ orderId: z2.string()
498
+ });
499
+ var deleteTpSlResultSchema = z2.object({
500
+ status: z2.literal("deleted"),
501
+ provider: providerSchema.optional(),
502
+ symbol: z2.string(),
503
+ orderId: z2.string()
504
+ });
505
+ var openOrderSchema = z2.object({
506
+ coin: z2.string(),
507
+ side: z2.enum(["B", "A"]),
508
+ limitPx: z2.string(),
509
+ sz: z2.string(),
510
+ oid: z2.number().int().nonnegative(),
511
+ timestamp: z2.number().int().nonnegative(),
512
+ origSz: z2.string(),
513
+ orderType: z2.string(),
514
+ tif: z2.string().nullable(),
515
+ reduceOnly: z2.boolean(),
516
+ isTrigger: z2.boolean(),
517
+ triggerPx: z2.string(),
518
+ cloid: z2.string().nullable(),
519
+ assetId: z2.number().int().nonnegative().optional()
520
+ });
521
+ var cancelOrderInputSchema = z2.object({
522
+ symbol: tradingSymbolSchema,
523
+ oid: z2.number().int().nonnegative()
524
+ });
525
+ var cancelOrderResultSchema = z2.object({
526
+ status: z2.literal("cancelled"),
527
+ symbol: z2.string(),
528
+ oid: z2.number().int().nonnegative()
529
+ });
530
+ var modifyOrderInputSchema = z2.object({
531
+ symbol: tradingSymbolSchema,
532
+ oid: z2.number().int().nonnegative(),
533
+ price: z2.number().positive().finite(),
534
+ /** Size in the coin (base quantity). */
535
+ size: z2.number().positive().finite()
536
+ });
537
+ var modifyOrderResultSchema = z2.object({
538
+ status: z2.literal("modified"),
539
+ symbol: z2.string(),
540
+ oid: z2.number().int().nonnegative(),
541
+ price: z2.number(),
542
+ size: z2.number()
543
+ });
544
+ var AGENT_PAPER_ACCOUNT = "agent";
545
+ var paperFillSchema = z2.object({
546
+ ts: z2.number(),
547
+ coin: z2.string(),
548
+ kind: z2.string(),
549
+ side: z2.enum(["BUY", "SELL"]).nullable(),
550
+ size: z2.number(),
551
+ price: z2.number(),
552
+ fee: z2.number(),
553
+ pnl: z2.number()
554
+ });
555
+ var paperPortfolioSchema = portfolioSnapshotSchema.extend({
556
+ cash: z2.number().optional(),
557
+ realizedPnl: z2.number().optional(),
558
+ feesPaid: z2.number().optional(),
559
+ fundingPaid: z2.number().optional(),
560
+ liquidations: z2.number().optional()
561
+ });
562
+ var agentSetupSchema = z2.enum(["turning up from breakdown", "falling, no turn yet", "stretched up", "stretched down", "neutral"]);
563
+ var smSideNameSchema = z2.enum(["long", "short"]);
564
+ var agentMarketInputSchema = z2.object({
565
+ coins: z2.array(z2.string().min(1)).min(1).max(50).optional(),
566
+ limit: z2.number().int().min(1).max(300).optional(),
567
+ offset: z2.number().int().nonnegative().optional(),
568
+ sort: z2.enum(["volume", "rank", "move1h", "move24h", "funding", "lean", "edge"]).optional(),
569
+ filter: z2.enum(["turning", "falling", "stretched_up", "stretched_down", "sm_long", "sm_short", "discount"]).optional()
570
+ });
571
+ var agentMarketRowSchema = z2.object({
572
+ coin: z2.string(),
573
+ rank: z2.number().nullable(),
574
+ px: z2.number().nullable(),
575
+ r15m: z2.number().nullable(),
576
+ r1h: z2.number().nullable(),
577
+ r24h: z2.number().nullable(),
578
+ volUsd: z2.number().nullable(),
579
+ fundAprPct: z2.number().nullable(),
580
+ oiUsd: z2.number().nullable(),
581
+ setup: agentSetupSchema.nullable()
582
+ });
583
+ var agentMarketSchema = z2.object({
584
+ asOf: z2.string().nullable(),
585
+ smAsOf: z2.number().nullable(),
586
+ market: z2.object({ os: z2.number().nullable(), mti: z2.number().nullable(), Mti: z2.number().nullable() }),
587
+ total: z2.number().int().nonnegative(),
588
+ offset: z2.number().int().nonnegative(),
589
+ rows: z2.array(
590
+ agentMarketRowSchema.extend({
591
+ sm: z2.object({
592
+ side: smSideNameSchema.nullable(),
593
+ share: z2.number(),
594
+ edgePct: z2.number().nullable(),
595
+ discount: z2.enum(["strict", "fill"]).nullable()
596
+ }).nullable()
597
+ })
598
+ ),
599
+ next: z2.number().int().nonnegative().nullable(),
600
+ unknown: z2.array(z2.string())
601
+ });
602
+ var smPileInSchema = z2.object({
603
+ coin: z2.string(),
604
+ side: smSideNameSchema,
605
+ dir: z2.enum(["in", "out"]),
606
+ traders: z2.number(),
607
+ totalUsd: z2.number(),
608
+ weight: z2.number(),
609
+ names: z2.array(z2.string()),
610
+ first: z2.number(),
611
+ last: z2.number()
612
+ });
613
+ var smartMoneyBoardInputSchema = z2.object({
614
+ view: z2.enum(["conviction", "discount", "flows", "pileins"]).optional(),
615
+ side: z2.enum(["long", "short", "any"]).optional(),
616
+ limit: z2.number().int().min(1).max(25).optional()
617
+ });
618
+ var smartMoneyBoardSchema = z2.object({
619
+ asOf: z2.number().nullable(),
620
+ view: z2.enum(["conviction", "discount", "flows", "pileins"]),
621
+ wallets: z2.number(),
622
+ known: z2.number(),
623
+ rows: z2.array(
624
+ z2.object({
625
+ coin: z2.string(),
626
+ side: smSideNameSchema.nullable(),
627
+ share: z2.number(),
628
+ lean: z2.number(),
629
+ sideUsd: z2.number(),
630
+ heads: z2.number(),
631
+ traders: z2.number(),
632
+ entry: z2.number().nullable(),
633
+ mark: z2.number().nullable(),
634
+ edgePct: z2.number().nullable(),
635
+ flow1hUsd: z2.number(),
636
+ discount: z2.enum(["strict", "fill"]).nullable(),
637
+ setup: agentSetupSchema.nullable(),
638
+ r24h: z2.number().nullable()
639
+ })
640
+ ),
641
+ pileIns: z2.array(smPileInSchema),
642
+ note: z2.string().nullable()
643
+ });
644
+ var smartMoneyMovesInputSchema = z2.object({
645
+ since: z2.string().regex(/^\d{1,16}-\d{1,16}$/).optional(),
646
+ coin: z2.string().min(1).optional(),
647
+ kind: z2.enum(["entries", "exits", "all"]).optional(),
648
+ minUsd: z2.number().nonnegative().optional(),
649
+ limit: z2.number().int().min(1).max(50).optional()
650
+ });
651
+ var smMoveSchema = z2.object({
652
+ id: z2.string(),
653
+ t: z2.number(),
654
+ coin: z2.string(),
655
+ kind: z2.enum(["open", "add", "reduce", "close", "flip"]),
656
+ side: smSideNameSchema,
657
+ usd: z2.number(),
658
+ px: z2.number(),
659
+ lev: z2.number().nullable(),
660
+ pnl: z2.number().nullable(),
661
+ pnlPct: z2.number().nullable(),
662
+ nick: z2.string(),
663
+ rank: z2.number(),
664
+ style: z2.string(),
665
+ win: z2.number().nullable(),
666
+ w: z2.number()
667
+ });
668
+ var smartMoneyMovesSchema = z2.object({
669
+ order: z2.enum(["newest", "oldest"]),
670
+ moves: z2.array(smMoveSchema),
671
+ next: z2.string().nullable(),
672
+ more: z2.boolean(),
673
+ gap: z2.boolean(),
674
+ note: z2.string().nullable()
675
+ });
676
+ var smBookSideSchema = z2.object({ usd: z2.number(), heads: z2.number(), traders: z2.number(), entry: z2.number().nullable() });
677
+ var smartMoneyCoinSchema = z2.object({
678
+ coin: z2.string(),
679
+ asOf: z2.number().nullable(),
680
+ sm: z2.object({
681
+ coin: z2.string(),
682
+ mark: z2.number().nullable(),
683
+ long: smBookSideSchema,
684
+ short: smBookSideSchema,
685
+ lean: z2.number(),
686
+ side: smSideNameSchema.nullable(),
687
+ share: z2.number(),
688
+ edgePct: z2.number().nullable(),
689
+ flow1hUsd: z2.number(),
690
+ conviction: z2.number().nullable(),
691
+ discount: z2.enum(["strict", "fill"]).nullable(),
692
+ pileIn: smPileInSchema.nullable()
693
+ }).nullable(),
694
+ market: agentMarketRowSchema.nullable(),
695
+ moves: z2.array(smMoveSchema),
696
+ note: z2.string().nullable()
697
+ });
698
+ var marketCandlesInputSchema = z2.object({
699
+ symbol: z2.string().regex(/^[A-Za-z0-9]{1,20}$/),
700
+ interval: z2.enum(["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "1w", "1M"]),
701
+ endTime: z2.number().int().positive().optional(),
702
+ limit: z2.number().int().min(1).max(10080).optional()
703
+ });
704
+ var marketCandlesSchema = z2.object({
705
+ candles: z2.array(z2.object({ t: z2.number(), o: z2.number(), h: z2.number(), l: z2.number(), c: z2.number(), v: z2.number() })),
706
+ complete: z2.boolean()
707
+ });
708
+ var DEFAULT_AGENT_REFRESH_WINDOW_MS = 60 * 1e3;
709
+ var agentConnectionStatusSchema = z3.enum([
710
+ "disconnected",
711
+ "pending",
712
+ "connected",
713
+ "rejected",
714
+ "expired",
715
+ "failed"
716
+ ]);
717
+ var agentSessionIssuerSchema = z3.object({
718
+ identityServiceUrl: z3.string(),
719
+ dataBridgeUrl: z3.string()
720
+ });
721
+ var storedAgentSessionSchema = z3.object({
722
+ accessToken: z3.string(),
723
+ refreshToken: z3.string(),
724
+ tokenType: agentTokenResponseSchema.shape.tokenType,
725
+ ownerUserId: z3.string(),
726
+ agentGrantId: z3.string(),
727
+ scope: z3.array(agentScopeSchema).min(1),
728
+ accessTokenExpiresAt: z3.string().datetime(),
729
+ createdAt: z3.string().datetime(),
730
+ lastRefreshedAt: z3.string().datetime(),
731
+ issuer: agentSessionIssuerSchema.nullish()
732
+ });
733
+ var agentSessionViewSchema = z3.object({
734
+ connected: z3.boolean(),
735
+ status: agentConnectionStatusSchema,
736
+ ownerUserId: z3.string().optional(),
737
+ agentGrantId: z3.string().optional(),
738
+ scope: z3.array(agentScopeSchema).optional(),
739
+ accessTokenExpiresAt: z3.string().datetime().optional(),
740
+ lastRefreshedAt: z3.string().datetime().optional()
741
+ });
742
+ function createStoredAgentSession(tokenResponse, now = /* @__PURE__ */ new Date()) {
743
+ const createdAt = now.toISOString();
744
+ return {
745
+ accessToken: tokenResponse.accessToken,
746
+ refreshToken: tokenResponse.refreshToken,
747
+ tokenType: tokenResponse.tokenType,
748
+ ownerUserId: tokenResponse.ownerUserId,
749
+ agentGrantId: tokenResponse.agentGrantId,
750
+ scope: tokenResponse.scope,
751
+ accessTokenExpiresAt: new Date(now.getTime() + tokenResponse.expiresInSeconds * 1e3).toISOString(),
752
+ createdAt,
753
+ lastRefreshedAt: createdAt
754
+ };
755
+ }
756
+ function sanitizeAgentSession(session, status = session ? "connected" : "disconnected") {
757
+ if (!session) {
758
+ return {
759
+ connected: false,
760
+ status: status === "connected" ? "disconnected" : status
761
+ };
762
+ }
763
+ return {
764
+ connected: true,
765
+ status,
766
+ ownerUserId: session.ownerUserId,
767
+ agentGrantId: session.agentGrantId,
768
+ scope: session.scope,
769
+ accessTokenExpiresAt: session.accessTokenExpiresAt,
770
+ lastRefreshedAt: session.lastRefreshedAt
771
+ };
772
+ }
773
+ function shouldRefreshAgentSession(session, now = /* @__PURE__ */ new Date(), refreshWindowMs = DEFAULT_AGENT_REFRESH_WINDOW_MS) {
774
+ return new Date(session.accessTokenExpiresAt).getTime() - now.getTime() <= refreshWindowMs;
775
+ }
776
+ var pendingAgentLoginSchema = z4.object({
777
+ status: z4.literal("pending"),
778
+ deviceCode: z4.string(),
779
+ userCode: z4.string(),
780
+ verificationUri: z4.string().url(),
781
+ verificationUriComplete: z4.string().url(),
782
+ expiresInSeconds: z4.number().int().positive(),
783
+ pollIntervalSeconds: z4.number().int().positive(),
784
+ requestedScope: z4.array(agentScopeSchema).min(1),
785
+ startedAt: z4.string().datetime()
786
+ });
787
+ var DEFAULT_SCOPE = [AGENT_SCOPES.ACCOUNT_READ, AGENT_SCOPES.MARKET_READ];
788
+ var AgentAuthService = class {
789
+ constructor(options) {
790
+ __publicField2(this, "client");
791
+ __publicField2(this, "sessionStore");
792
+ __publicField2(this, "messages");
793
+ __publicField2(this, "refreshWindowMs");
794
+ __publicField2(this, "now");
795
+ __publicField2(this, "defaultClientName");
796
+ __publicField2(this, "deviceLogin");
797
+ __publicField2(this, "reloginForMissingScope");
798
+ __publicField2(this, "refreshInFlight", null);
799
+ __publicField2(this, "exchangeInFlight", null);
800
+ this.client = options.client;
801
+ this.sessionStore = options.sessionStore;
802
+ this.messages = options.messages;
803
+ this.refreshWindowMs = options.refreshWindowMs ?? DEFAULT_AGENT_REFRESH_WINDOW_MS;
804
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
805
+ this.defaultClientName = options.defaultClientName;
806
+ this.deviceLogin = options.deviceLogin ?? true;
807
+ this.reloginForMissingScope = options.reloginForMissingScope ?? false;
808
+ }
809
+ async startLogin(input) {
810
+ if (!this.deviceLogin) {
811
+ throw new Error(this.messages.reconnect);
812
+ }
813
+ const response = await this.client.startLogin(input);
814
+ const result = {
815
+ status: "pending",
816
+ ...response,
817
+ requestedScope: input.scope,
818
+ startedAt: this.now().toISOString()
819
+ };
820
+ await this.sessionStore.setPendingLogin(result);
821
+ return result;
822
+ }
823
+ async resetSessionAndStartLogin(input) {
824
+ await this.clearSessionState();
825
+ return this.startLogin(input);
826
+ }
827
+ async startRelogin(options = {}) {
828
+ const started = await this.resetSessionAndStartLogin(this.loginInput(options));
829
+ return this.pendingResult(started);
830
+ }
831
+ async clearSessionState() {
832
+ await this.sessionStore.clearSession();
833
+ await this.sessionStore.clearPendingLogin();
834
+ }
835
+ /** After a 401 from Helios: drops the session and starts a fresh login. Null for any other error. */
836
+ async invalidateIfUnauthorized(error, options = {}) {
837
+ if (!isHeliosAgentApiError(error) || error.status !== 401) {
838
+ return null;
839
+ }
840
+ return this.startRelogin(options);
841
+ }
842
+ /** After a 401 from Helios: refreshes the session even if it isn't near expiry. Null for any other error. */
843
+ async refreshSessionAfterUnauthorized(error) {
844
+ if (!isHeliosAgentApiError(error) || error.status !== 401) {
845
+ return null;
846
+ }
847
+ const session = await this.sessionStore.getSession();
848
+ return session ? this.refreshStoredSession(session, true) : null;
849
+ }
850
+ async ensureLogin(options = {}) {
851
+ const requestedScope = options.scope ?? DEFAULT_SCOPE;
852
+ const requestedConstraints = options.constraints ?? {};
853
+ const session = await this.getValidSession();
854
+ if (session) {
855
+ if (!this.reloginForMissingScope || hasScopes(session, requestedScope)) {
856
+ return connectedResult(session);
857
+ }
858
+ await this.sessionStore.clearSession();
859
+ }
860
+ const pendingLogin = await this.getPendingLogin(requestedScope, requestedConstraints);
861
+ if (pendingLogin) {
862
+ const polled = await this.exchangePendingLogin(pendingLogin.deviceCode).catch(() => null);
863
+ if (polled?.status === "connected") return polled;
864
+ const stillPending = await this.getPendingLogin(requestedScope, requestedConstraints);
865
+ if (stillPending) return this.pendingResult(stillPending);
866
+ }
867
+ return this.pendingResult(await this.startLogin(this.loginInput(options)));
868
+ }
869
+ /**
870
+ * Polls the stored pending login. A code, when given, must be that login's device code or the user code shown to the
871
+ * person: a code from anywhere else (a prompt injection carrying a login someone else approved) is never exchanged, so
872
+ * it can't swap the stored session for another account's.
873
+ */
874
+ async pollLogin(code) {
875
+ const pending = await this.sessionStore.getPendingLogin();
876
+ if (!pending) {
877
+ const session = await this.getValidSession();
878
+ if (session) return connectedResult(session);
879
+ return { status: "failed", connected: false, message: this.messages.noLoginInProgress };
880
+ }
881
+ const given = code?.trim();
882
+ if (given && given !== pending.deviceCode && given.toUpperCase() !== pending.userCode.toUpperCase()) {
883
+ return { status: "failed", connected: false, message: this.messages.wrongCode };
884
+ }
885
+ return this.exchangePendingLogin(pending.deviceCode);
886
+ }
887
+ async whoami() {
888
+ return sanitizeAgentSession(await this.getValidSession());
889
+ }
890
+ async getAccessToken() {
891
+ return (await this.getValidSession())?.accessToken ?? null;
892
+ }
893
+ async getPendingLoginState() {
894
+ return this.sessionStore.getPendingLogin();
895
+ }
896
+ async requireSession() {
897
+ const session = await this.getValidSession();
898
+ if (!session) {
899
+ throw new Error(this.deviceLogin ? "No Helios delegated agent session is connected" : this.messages.reconnect);
900
+ }
901
+ return session;
902
+ }
903
+ loginInput(options) {
904
+ return {
905
+ clientName: options.clientName ?? this.defaultClientName,
906
+ scope: options.scope ?? DEFAULT_SCOPE,
907
+ constraints: options.constraints
908
+ };
909
+ }
910
+ pendingResult(started) {
911
+ return { ...started, connected: false, message: this.messages.loginPrompt(started) };
912
+ }
913
+ async getValidSession() {
914
+ const session = await this.sessionStore.getSession();
915
+ if (!session) {
916
+ return null;
917
+ }
918
+ if (!shouldRefreshAgentSession(session, this.now(), this.refreshWindowMs)) {
919
+ return session;
920
+ }
921
+ return this.refreshStoredSession(session, false);
922
+ }
923
+ /**
924
+ * Refreshes `seen` (the session the caller read), joining a refresh already in progress. `force` refreshes even a
925
+ * session that isn't near expiry (Helios just answered 401 to it).
926
+ */
927
+ refreshStoredSession(seen, force) {
928
+ this.refreshInFlight ?? (this.refreshInFlight = this.refreshOnce(seen, force).finally(() => {
929
+ this.refreshInFlight = null;
930
+ }));
931
+ return this.refreshInFlight;
932
+ }
933
+ async refreshOnce(seen, force) {
934
+ const latest = await this.sessionStore.getSession();
935
+ if (!latest) {
936
+ return null;
937
+ }
938
+ const rotatedElsewhere = latest.refreshToken !== seen.refreshToken;
939
+ if ((rotatedElsewhere || !force) && !shouldRefreshAgentSession(latest, this.now(), this.refreshWindowMs)) {
940
+ return latest;
941
+ }
942
+ if (!latest.refreshToken) {
943
+ return null;
944
+ }
945
+ try {
946
+ const refreshed = await this.client.refreshSession(latest.refreshToken);
947
+ const nextSession = createStoredAgentSession(refreshed, this.now());
948
+ await this.sessionStore.setSession(nextSession);
949
+ return nextSession;
950
+ } catch (error) {
951
+ const stored = await this.sessionStore.getSession().catch(() => null);
952
+ if (stored && stored.refreshToken !== latest.refreshToken) {
953
+ return stored;
954
+ }
955
+ if (isHeliosAgentApiError(error) && error.code === "invalid_grant") {
956
+ await this.clearSessionState();
957
+ return null;
958
+ }
959
+ throw error;
960
+ }
961
+ }
962
+ exchangePendingLogin(deviceCode) {
963
+ if (this.exchangeInFlight?.deviceCode !== deviceCode) {
964
+ const flight = {
965
+ deviceCode,
966
+ promise: this.exchangeOnce(deviceCode).finally(() => {
967
+ if (this.exchangeInFlight === flight) this.exchangeInFlight = null;
968
+ })
969
+ };
970
+ this.exchangeInFlight = flight;
971
+ }
972
+ return this.exchangeInFlight.promise;
973
+ }
974
+ async exchangeOnce(deviceCode) {
975
+ try {
976
+ const response = await this.client.exchangeDeviceCode(deviceCode);
977
+ const session = createStoredAgentSession(response, this.now());
978
+ await this.sessionStore.setSession(session);
979
+ await this.sessionStore.clearPendingLogin();
980
+ return connectedResult(session);
981
+ } catch (error) {
982
+ if (!isHeliosAgentApiError(error)) {
983
+ return {
984
+ status: "failed",
985
+ connected: false,
986
+ message: error instanceof Error ? error.message : "Unexpected Helios login error"
987
+ };
988
+ }
989
+ switch (error.code) {
990
+ case "authorization_pending":
991
+ return { status: "pending", connected: false, message: error.message };
992
+ case "access_denied":
993
+ await this.clearPendingLoginIfMatches(deviceCode);
994
+ return { status: "rejected", connected: false, message: error.message };
995
+ case "expired_token":
996
+ await this.clearPendingLoginIfMatches(deviceCode);
997
+ return { status: "expired", connected: false, message: error.message };
998
+ case "invalid_grant":
999
+ await this.clearPendingLoginIfMatches(deviceCode);
1000
+ return { status: "failed", connected: false, message: this.messages.staleLogin };
1001
+ default:
1002
+ return { status: "failed", connected: false, message: error.message };
1003
+ }
1004
+ }
1005
+ }
1006
+ async getPendingLogin(requestedScope, requestedConstraints) {
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
+ const pendingScope = new Set(pendingLogin.requestedScope);
1017
+ if (!requestedScope.every((scope) => pendingScope.has(scope))) {
1018
+ return null;
1019
+ }
1020
+ return Object.keys(requestedConstraints ?? {}).length === 0 ? pendingLogin : null;
1021
+ }
1022
+ async clearPendingLoginIfMatches(deviceCode) {
1023
+ const pendingLogin = await this.sessionStore.getPendingLogin();
1024
+ if (pendingLogin?.deviceCode === deviceCode) {
1025
+ await this.sessionStore.clearPendingLogin();
1026
+ }
1027
+ }
1028
+ };
1029
+ async function retryAfterUnauthorizedRefresh(options) {
1030
+ const refreshedSession = await options.authService.refreshSessionAfterUnauthorized(options.error);
1031
+ if (!refreshedSession) {
1032
+ return options.authService.invalidateIfUnauthorized(options.error, options.loginOptions);
1033
+ }
1034
+ try {
1035
+ return await options.retry(refreshedSession.accessToken);
1036
+ } catch (retryError) {
1037
+ const relogin = await options.authService.invalidateIfUnauthorized(retryError, options.loginOptions);
1038
+ if (relogin) {
1039
+ return relogin;
1040
+ }
1041
+ throw retryError;
1042
+ }
1043
+ }
1044
+ function connectedResult(session) {
1045
+ return { ...sanitizeAgentSession(session), connected: true, status: "connected" };
1046
+ }
1047
+ function hasScopes(session, requestedScope) {
1048
+ const granted = new Set(session.scope);
1049
+ return requestedScope.every((scope) => granted.has(scope));
1050
+ }
1051
+ var HeliosAgentAuthClient = class {
1052
+ constructor(options) {
1053
+ __publicField2(this, "baseUrl");
1054
+ __publicField2(this, "fetchImpl");
1055
+ __publicField2(this, "headers");
1056
+ __publicField2(this, "startSecret");
1057
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
1058
+ this.fetchImpl = options.fetch ?? fetch;
1059
+ this.headers = options.headers ?? {};
1060
+ this.startSecret = options.startSecret || void 0;
1061
+ }
1062
+ async startLogin(input, options = {}) {
1063
+ const parsed = agentDeviceStartInputSchema.parse(input);
1064
+ const headers = this.startSecret ? { [AGENT_START_SECRET_HEADER]: this.startSecret, ...options.clientIp ? { [AGENT_CLIENT_IP_HEADER]: options.clientIp } : {} } : void 0;
1065
+ return this.post("/api/agents/device/start", parsed, agentDeviceStartOutputSchema, headers);
1066
+ }
1067
+ async exchangeDeviceCode(deviceCode, options = {}) {
1068
+ const parsed = agentDeviceTokenInputSchema.parse({ deviceCode, approvalProof: options.approvalProof });
1069
+ return this.post("/api/agents/device/token", parsed, agentTokenResponseSchema);
1070
+ }
1071
+ async refreshSession(refreshToken) {
1072
+ const parsed = agentRefreshInputSchema.parse({ refreshToken });
1073
+ return this.post("/api/agents/auth/refresh", parsed, agentTokenResponseSchema);
1074
+ }
1075
+ async post(path4, body, schema, extraHeaders) {
1076
+ let response;
1077
+ try {
1078
+ response = await this.fetchImpl(`${this.baseUrl}${path4}`, {
1079
+ method: "POST",
1080
+ headers: {
1081
+ Accept: "application/json",
1082
+ "Content-Type": "application/json",
1083
+ ...this.headers,
1084
+ ...extraHeaders
1085
+ },
1086
+ body: JSON.stringify(body)
1087
+ });
1088
+ } catch (error) {
1089
+ throw new HeliosAgentApiError({
1090
+ status: 0,
1091
+ code: "network_error",
1092
+ message: error instanceof Error ? error.message : "Network error",
1093
+ cause: error
1094
+ });
1095
+ }
1096
+ const payload = await this.parseJson(response);
1097
+ if (!response.ok) {
1098
+ const parsed = heliosAgentErrorResponseSchema.safeParse(payload);
1099
+ throw new HeliosAgentApiError({
1100
+ status: response.status,
1101
+ code: parsed.success ? parsed.data.error ?? "request_failed" : "request_failed",
1102
+ message: parsed.success ? parsed.data.errorDescription ?? parsed.data.message ?? `Request failed with status ${response.status}` : `Request failed with status ${response.status}`,
1103
+ errorDescription: parsed.success ? parsed.data.errorDescription : void 0,
1104
+ payload
1105
+ });
1106
+ }
1107
+ return schema.parse(payload);
1108
+ }
1109
+ async parseJson(response) {
1110
+ const text2 = await response.text();
1111
+ if (!text2) {
1112
+ return null;
1113
+ }
1114
+ try {
1115
+ return JSON.parse(text2);
1116
+ } catch (error) {
1117
+ throw new HeliosAgentApiError({
1118
+ status: response.status,
1119
+ code: "invalid_json",
1120
+ message: "Received an invalid JSON response from Helios",
1121
+ cause: error
1122
+ });
1123
+ }
1124
+ }
1125
+ };
1126
+ var HeliosAgentDataBridgeClient = class {
1127
+ constructor(options) {
1128
+ __publicField2(this, "baseUrl");
1129
+ __publicField2(this, "fetchImpl");
1130
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
1131
+ this.fetchImpl = options.fetch ?? fetch;
1132
+ }
1133
+ async getWalletState(accessToken) {
1134
+ const payload = await this.request(accessToken, "account.getWalletState");
1135
+ return walletStateSchema.parse(this.unwrapResult(payload));
1136
+ }
1137
+ async getOpenPositions(accessToken) {
1138
+ const payload = await this.request(accessToken, "positions.getOpen");
1139
+ return openPositionsSnapshotSchema.parse(this.unwrapResult(payload));
1140
+ }
1141
+ async getPortfolio(accessToken, input) {
1142
+ const parsedInput = portfolioInputSchema.parse(input);
1143
+ const payload = await this.request(accessToken, "positions.getPortfolio", parsedInput);
1144
+ const result = this.unwrapResult(payload);
1145
+ if (!result || typeof result !== "object" || Array.isArray(result)) {
1146
+ throw new HeliosAgentApiError({
1147
+ status: 200,
1148
+ code: "invalid_response",
1149
+ message: "Helios data bridge returned an invalid portfolio payload",
1150
+ payload
1151
+ });
1152
+ }
1153
+ return portfolioSnapshotSchema.parse({
1154
+ ...result,
1155
+ provider: parsedInput.provider,
1156
+ protectiveOrders: readProtectiveOrders(result.protectiveOrders)
1157
+ });
1158
+ }
1159
+ async getLedgerFills(accessToken, input = {}) {
1160
+ const parsedInput = ledgerFillsInputSchema.parse(input);
1161
+ const payload = await this.request(accessToken, "ledger.getFills", parsedInput);
1162
+ return ledgerFillsPageSchema.parse(this.unwrapResult(payload));
1163
+ }
1164
+ async getLedgerSummary(accessToken, input = {}) {
1165
+ const parsedInput = ledgerSummaryInputSchema.parse(input);
1166
+ const payload = await this.request(accessToken, "ledger.getSummary", parsedInput);
1167
+ return ledgerSummarySchema.parse(this.unwrapResult(payload));
1168
+ }
1169
+ async getMarketSnapshot(accessToken, input = {}) {
1170
+ const parsedInput = marketSnapshotInputSchema.parse(input);
1171
+ const payload = await this.request(accessToken, "market.getSnapshot", parsedInput);
1172
+ return marketSnapshotSchema.parse(this.unwrapResult(payload));
1173
+ }
1174
+ async getMarketSnapshotChunk(accessToken, input = {}) {
1175
+ const parsedInput = marketSnapshotChunkInputSchema.parse(input);
1176
+ const payload = await this.request(accessToken, "market.getSnapshotChunk", parsedInput);
1177
+ return marketSnapshotChunkSchema.parse(this.unwrapResult(payload));
1178
+ }
1179
+ async getMarketSymbolDetail(accessToken, input) {
1180
+ const parsedInput = marketSymbolDetailInputSchema.parse(input);
1181
+ const payload = await this.request(accessToken, "market.getSymbolDetail", parsedInput);
1182
+ return marketSymbolDetailSchema.parse(this.unwrapResult(payload));
1183
+ }
1184
+ /** The lean market table: price, two returns, volume, funding, open interest, setup label and the smart money lean. */
1185
+ async getAgentMarket(accessToken, input = {}) {
1186
+ const payload = await this.request(accessToken, "market.getAgentTable", agentMarketInputSchema.parse(input));
1187
+ return agentMarketSchema.parse(this.unwrapResult(payload));
1188
+ }
1189
+ async getMarketCandles(accessToken, input) {
1190
+ const payload = await this.request(accessToken, "market.getCandles", marketCandlesInputSchema.parse(input));
1191
+ return marketCandlesSchema.parse(this.unwrapResult(payload));
1192
+ }
1193
+ /** The research wallets' smart money board (conviction, discount, flows or pile-ins). */
1194
+ async getSmartMoneyBoard(accessToken, input = {}) {
1195
+ const payload = await this.request(accessToken, "smartMoney.board", smartMoneyBoardInputSchema.parse(input));
1196
+ return smartMoneyBoardSchema.parse(this.unwrapResult(payload));
1197
+ }
1198
+ /** Their trades by nickname: the latest, or only what came after an earlier call's `next`. */
1199
+ async getSmartMoneyMoves(accessToken, input = {}) {
1200
+ const payload = await this.request(accessToken, "smartMoney.moves", smartMoneyMovesInputSchema.parse(input));
1201
+ return smartMoneyMovesSchema.parse(this.unwrapResult(payload));
1202
+ }
1203
+ /** One coin: their book, its market row and their latest trades on it. */
1204
+ async getSmartMoneyCoin(accessToken, coin2) {
1205
+ const payload = await this.request(accessToken, "smartMoney.coin", { coin: coin2 });
1206
+ return smartMoneyCoinSchema.parse(this.unwrapResult(payload));
1207
+ }
1208
+ async submitTradeIntent(accessToken, input) {
1209
+ const parsedInput = submitTradeIntentInputSchema.parse(input);
1210
+ const payload = await this.request(accessToken, "trading.submitIntent", parsedInput, "POST");
1211
+ const answer = this.unwrapResult(payload) ?? {};
1212
+ return submitTradeIntentResultSchema.parse({
1213
+ status: "submitted",
1214
+ ...answer.outcome === "placed" || answer.outcome === "replayed" ? { outcome: answer.outcome } : {},
1215
+ ...typeof answer.clientOrderId === "string" ? { clientOrderId: answer.clientOrderId } : {},
1216
+ ...typeof answer.orderStatus === "string" ? { orderStatus: answer.orderStatus } : {},
1217
+ ...Array.isArray(answer.rejectedProtection) ? { rejectedProtection: answer.rejectedProtection } : {},
1218
+ provider: parsedInput.provider,
1219
+ symbol: parsedInput.symbol,
1220
+ intentType: parsedInput.intentType,
1221
+ direction: parsedInput.direction,
1222
+ size: parsedInput.size,
1223
+ execution: parsedInput.execution,
1224
+ maxSlippageBps: parsedInput.maxSlippageBps,
1225
+ leverageOverride: parsedInput.leverageOverride,
1226
+ marginModeOverride: parsedInput.marginModeOverride,
1227
+ reduceOnly: Boolean(parsedInput.reduceOnly),
1228
+ hasProtection: Boolean(parsedInput.protection?.takeProfit || parsedInput.protection?.stopLoss)
1229
+ });
1230
+ }
1231
+ async closePosition(accessToken, input) {
1232
+ const parsedInput = closePositionInputSchema.parse(input);
1233
+ const partial = parsedInput.fraction !== void 0 && parsedInput.fraction < 1;
1234
+ const payload = await this.request(
1235
+ accessToken,
1236
+ "positions.close",
1237
+ {
1238
+ ...parsedInput.provider !== void 0 ? { provider: parsedInput.provider } : {},
1239
+ symbol: parsedInput.symbol,
1240
+ mode: partial ? "PARTIAL" : "FULL",
1241
+ ...partial ? { fraction: parsedInput.fraction } : {},
1242
+ ...parsedInput.maxSlippageBps !== void 0 ? { maxSlippageBps: parsedInput.maxSlippageBps } : {}
1243
+ },
1244
+ "POST"
1245
+ );
1246
+ const outcome = this.tryUnwrapResult(payload)?.outcome;
1247
+ return closePositionResultSchema.parse({
1248
+ status: "submitted",
1249
+ ...outcome === "closed" || outcome === "no_position" ? { outcome } : {},
1250
+ provider: parsedInput.provider,
1251
+ symbol: parsedInput.symbol,
1252
+ mode: partial ? "PARTIAL" : "FULL",
1253
+ ...partial ? { fraction: parsedInput.fraction } : {},
1254
+ maxSlippageBps: parsedInput.maxSlippageBps
1255
+ });
1256
+ }
1257
+ async setTpSl(accessToken, input) {
1258
+ const parsedInput = setTpSlInputSchema.parse(input);
1259
+ await this.request(accessToken, "positions.setTpSl", parsedInput, "POST");
1260
+ return setTpSlResultSchema.parse({
1261
+ status: "submitted",
1262
+ provider: parsedInput.provider,
1263
+ symbol: parsedInput.symbol,
1264
+ applyTo: parsedInput.applyTo,
1265
+ quantity: parsedInput.quantity,
1266
+ parentClientOrderId: parsedInput.parentClientOrderId,
1267
+ hasTakeProfit: Boolean(parsedInput.takeProfit),
1268
+ hasStopLoss: Boolean(parsedInput.stopLoss)
1269
+ });
1270
+ }
1271
+ async deleteTpSl(accessToken, input) {
1272
+ const parsedInput = deleteTpSlInputSchema.parse(input);
1273
+ await this.request(accessToken, "positions.deleteTpSl", parsedInput, "POST");
1274
+ return deleteTpSlResultSchema.parse({
1275
+ status: "deleted",
1276
+ provider: parsedInput.provider,
1277
+ symbol: parsedInput.symbol,
1278
+ orderId: parsedInput.orderId
1279
+ });
1280
+ }
1281
+ async getOpenOrders(accessToken) {
1282
+ const payload = await this.request(accessToken, "positions.getOpenOrders");
1283
+ return openOrderSchema.array().parse(this.unwrapResult(payload));
1284
+ }
1285
+ async cancelOrder(accessToken, input) {
1286
+ const parsedInput = cancelOrderInputSchema.parse(input);
1287
+ await this.request(accessToken, "positions.cancelOrder", parsedInput, "POST");
1288
+ return cancelOrderResultSchema.parse({
1289
+ status: "cancelled",
1290
+ symbol: parsedInput.symbol,
1291
+ oid: parsedInput.oid
1292
+ });
1293
+ }
1294
+ async modifyOrder(accessToken, input) {
1295
+ const parsedInput = modifyOrderInputSchema.parse(input);
1296
+ await this.request(accessToken, "positions.modifyOrder", parsedInput, "POST");
1297
+ return modifyOrderResultSchema.parse({ status: "modified", ...parsedInput });
1298
+ }
1299
+ // ---------------------------------------------------------------- paper (AGENT_PAPER_ACCOUNT unless told otherwise)
1300
+ async getPaperPortfolio(accessToken, accountId = AGENT_PAPER_ACCOUNT) {
1301
+ const payload = await this.request(accessToken, "paper.getPortfolio", { accountId, fills: 0 });
1302
+ return paperPortfolioSchema.parse(this.unwrapResult(payload));
1303
+ }
1304
+ /** A paper market order; answers with its fills (none when nothing filled). */
1305
+ async submitPaperIntent(accessToken, input, accountId = AGENT_PAPER_ACCOUNT) {
1306
+ const parsedInput = submitTradeIntentInputSchema.parse(input);
1307
+ const payload = await this.request(accessToken, "paper.submitIntent", { ...parsedInput, accountId }, "POST");
1308
+ return paperFillSchema.array().parse(this.tryUnwrapResult(payload) ?? []);
1309
+ }
1310
+ /** Closes all or part of a paper position; no fills when there was none. */
1311
+ async closePaperPosition(accessToken, input, accountId = AGENT_PAPER_ACCOUNT) {
1312
+ const parsedInput = closePositionInputSchema.parse(input);
1313
+ const partial = parsedInput.fraction !== void 0 && parsedInput.fraction < 1;
1314
+ const payload = await this.request(
1315
+ accessToken,
1316
+ "paper.close",
1317
+ { symbol: parsedInput.symbol, mode: partial ? "PARTIAL" : "FULL", ...partial ? { fraction: parsedInput.fraction } : {}, accountId },
1318
+ "POST"
1319
+ );
1320
+ return paperFillSchema.array().parse(this.tryUnwrapResult(payload) ?? []);
1321
+ }
1322
+ async setPaperTpSl(accessToken, input, accountId = AGENT_PAPER_ACCOUNT) {
1323
+ const parsedInput = setTpSlInputSchema.parse(input);
1324
+ await this.request(accessToken, "paper.setTpSl", { ...parsedInput, accountId }, "POST");
1325
+ return setTpSlResultSchema.parse({
1326
+ status: "submitted",
1327
+ provider: parsedInput.provider,
1328
+ symbol: parsedInput.symbol,
1329
+ applyTo: parsedInput.applyTo,
1330
+ quantity: parsedInput.quantity,
1331
+ parentClientOrderId: parsedInput.parentClientOrderId,
1332
+ hasTakeProfit: Boolean(parsedInput.takeProfit),
1333
+ hasStopLoss: Boolean(parsedInput.stopLoss)
1334
+ });
1335
+ }
1336
+ async deletePaperTpSl(accessToken, input, accountId = AGENT_PAPER_ACCOUNT) {
1337
+ const parsedInput = deleteTpSlInputSchema.parse(input);
1338
+ await this.request(accessToken, "paper.deleteTpSl", { ...parsedInput, accountId }, "POST");
1339
+ return deleteTpSlResultSchema.parse({ status: "deleted", provider: parsedInput.provider, symbol: parsedInput.symbol, orderId: parsedInput.orderId });
1340
+ }
1341
+ async getPaperLedgerFills(accessToken, input = {}, accountId = AGENT_PAPER_ACCOUNT) {
1342
+ const parsedInput = ledgerFillsInputSchema.parse(input);
1343
+ const payload = await this.request(accessToken, "paper.getLedgerFills", { ...parsedInput, accountId });
1344
+ return ledgerFillsPageSchema.parse(this.unwrapResult(payload));
1345
+ }
1346
+ /** Starts the paper account over with `startingBalance` USD (data-bridge's default when omitted). */
1347
+ async resetPaperAccount(accessToken, startingBalance, accountId = AGENT_PAPER_ACCOUNT) {
1348
+ await this.request(accessToken, "paper.reset", { accountId, ...startingBalance ? { startingBalance } : {} }, "POST");
1349
+ }
1350
+ async request(accessToken, procedure, input, method = "GET") {
1351
+ let response;
1352
+ try {
1353
+ response = await this.fetchImpl(this.buildProcedureUrl(procedure, method === "GET" ? input : void 0), {
1354
+ method,
1355
+ headers: {
1356
+ Accept: "application/json",
1357
+ ...method === "POST" ? { "Content-Type": "application/json" } : {},
1358
+ Authorization: `Bearer ${accessToken}`
1359
+ },
1360
+ ...method === "POST" && input !== void 0 ? { body: JSON.stringify({ json: input }) } : {}
1361
+ });
1362
+ } catch (error) {
1363
+ throw new HeliosAgentApiError({
1364
+ status: 0,
1365
+ code: "network_error",
1366
+ message: error instanceof Error ? error.message : "Network error",
1367
+ cause: error
1368
+ });
1369
+ }
1370
+ const payload = await this.parseJson(response);
1371
+ if (!response.ok) {
1372
+ throw new HeliosAgentApiError({
1373
+ status: response.status,
1374
+ code: this.extractErrorCode(payload),
1375
+ message: this.extractErrorMessage(payload, response.status),
1376
+ payload
1377
+ });
1378
+ }
1379
+ return payload;
1380
+ }
1381
+ buildProcedureUrl(procedure, input) {
1382
+ const url = new URL(`${this.baseUrl}/api/trpc/${procedure}`);
1383
+ if (input !== void 0) {
1384
+ url.searchParams.set("input", JSON.stringify({ json: input }));
1385
+ }
1386
+ return url.toString();
1387
+ }
1388
+ async parseJson(response) {
1389
+ const text2 = await response.text();
1390
+ if (!text2) {
1391
+ return null;
1392
+ }
1393
+ try {
1394
+ return JSON.parse(text2);
1395
+ } catch (error) {
1396
+ throw new HeliosAgentApiError({
1397
+ status: response.status,
1398
+ code: "invalid_json",
1399
+ message: "Received an invalid JSON response from Helios data bridge",
1400
+ cause: error
1401
+ });
1402
+ }
1403
+ }
1404
+ /** The result of a mutation older data-bridges answer with nothing; null when there is none. */
1405
+ tryUnwrapResult(payload) {
1406
+ try {
1407
+ return this.unwrapResult(payload) ?? null;
1408
+ } catch {
1409
+ return null;
1410
+ }
1411
+ }
1412
+ unwrapResult(payload) {
1413
+ if (!payload || typeof payload !== "object") {
1414
+ throw new HeliosAgentApiError({
1415
+ status: 200,
1416
+ code: "invalid_response",
1417
+ message: "Helios data bridge returned an invalid response payload",
1418
+ payload
1419
+ });
1420
+ }
1421
+ const result = payload.result;
1422
+ if (!result) {
1423
+ throw new HeliosAgentApiError({
1424
+ status: 200,
1425
+ code: "invalid_response",
1426
+ message: "Helios data bridge response is missing a result payload",
1427
+ payload
1428
+ });
1429
+ }
1430
+ const data = result.data;
1431
+ if (data && typeof data === "object" && "json" in data) {
1432
+ return data.json;
1433
+ }
1434
+ return data;
1435
+ }
1436
+ extractErrorCode(payload) {
1437
+ if (payload && typeof payload === "object") {
1438
+ const errorShape = this.unwrapTrpcError(payload);
1439
+ if (errorShape?.data?.code) {
1440
+ return errorShape.data.code;
1441
+ }
1442
+ }
1443
+ return "request_failed";
1444
+ }
1445
+ extractErrorMessage(payload, status) {
1446
+ if (payload && typeof payload === "object") {
1447
+ const errorShape = this.unwrapTrpcError(payload);
1448
+ if (errorShape?.message) {
1449
+ return errorShape.message;
1450
+ }
1451
+ }
1452
+ return `Request failed with status ${status}`;
1453
+ }
1454
+ /**
1455
+ * Unwrap a tRPC error envelope, handling the superjson `{ json: ... }` wrapper
1456
+ * that the data-bridge applies via its transformer configuration.
1457
+ */
1458
+ unwrapTrpcError(payload) {
1459
+ const envelope = payload.error;
1460
+ if (!envelope || typeof envelope !== "object") {
1461
+ return void 0;
1462
+ }
1463
+ if ("json" in envelope) {
1464
+ const inner = envelope.json;
1465
+ if (inner && typeof inner === "object") {
1466
+ return inner;
1467
+ }
1468
+ }
1469
+ return envelope;
1470
+ }
1471
+ };
1472
+ function readProtectiveOrders(value) {
1473
+ if (!Array.isArray(value)) return [];
1474
+ return value.flatMap((entry) => {
1475
+ const parsed = protectiveOrderSchema.safeParse(entry);
1476
+ return parsed.success ? [parsed.data] : [];
1477
+ });
1478
+ }
1479
+
1480
+ // src/auth-service.ts
1481
+ var HOSTED_RECONNECT_MESSAGE = "Helios no longer accepts this connection (it expired or was revoked). Reconnect Helios in your AI app to continue.";
1482
+ var HeliosAgentMcpAuthService = class extends AgentAuthService {
1483
+ constructor(options) {
1484
+ super({
1485
+ ...options,
1486
+ defaultClientName: "Helios MCP",
1487
+ messages: {
1488
+ loginPrompt: formatHeliosLoginPrompt,
1489
+ noLoginInProgress: "No Helios login is in progress. Call helios_auth_ensure_login to start one.",
1490
+ wrongCode: "That code is not the Helios login in progress. Call helios_auth_poll_login without a code to check it.",
1491
+ staleLogin: "Helios no longer knows this login request. Call helios_auth_ensure_login to start a fresh one.",
1492
+ reconnect: HOSTED_RECONNECT_MESSAGE
1493
+ }
1494
+ });
1495
+ }
1496
+ };
1497
+ function formatHeliosLoginPrompt(result) {
1498
+ return `Log in to Helios by opening this URL in your browser:
1499
+
1500
+ ${result.verificationUriComplete}
1501
+
1502
+ User code: ${result.userCode}
1503
+
1504
+ Requested scopes: ${result.requestedScope.join(", ")}
1505
+
1506
+ After approving access, call helios_auth_ensure_login (or helios_auth_poll_login) again to finish connecting.`;
1507
+ }
1508
+
1509
+ // src/account-service.ts
1510
+ var HeliosAgentMcpAccountService = class {
1511
+ constructor(options) {
1512
+ __publicField(this, "authService");
1513
+ __publicField(this, "dataBridgeClient");
1514
+ __publicField(this, "providerService");
1515
+ this.authService = options.authService;
1516
+ this.dataBridgeClient = options.dataBridgeClient;
1517
+ this.providerService = options.providerService;
1518
+ }
1519
+ async getWalletState() {
1520
+ const loginOptions = this.accountReadOptions();
1521
+ const login = await this.authService.ensureLogin(loginOptions);
1522
+ if (login.status !== "connected") {
1523
+ return {
1524
+ ...login,
1525
+ status: "pending",
1526
+ connected: false
1527
+ };
1528
+ }
1529
+ const session = await this.authService.requireSession();
1530
+ try {
1531
+ const walletState = await this.dataBridgeClient.getWalletState(session.accessToken);
1532
+ return {
1533
+ status: "connected",
1534
+ connected: true,
1535
+ walletState
1536
+ };
1537
+ } catch (error) {
1538
+ const recovery = await retryAfterUnauthorizedRefresh({
1539
+ authService: this.authService,
1540
+ error,
1541
+ loginOptions,
1542
+ retry: async (accessToken) => {
1543
+ const walletState = await this.dataBridgeClient.getWalletState(accessToken);
1544
+ return {
1545
+ status: "connected",
1546
+ connected: true,
1547
+ walletState
1548
+ };
1549
+ }
1550
+ });
1551
+ if (recovery) {
1552
+ return recovery;
1553
+ }
1554
+ throw error;
1555
+ }
1556
+ }
1557
+ async getPortfolio(input = {}) {
1558
+ const loginOptions = this.accountReadOptions();
1559
+ const login = await this.authService.ensureLogin(loginOptions);
1560
+ if (login.status !== "connected") {
1561
+ return {
1562
+ ...login,
1563
+ status: "pending",
1564
+ connected: false
1565
+ };
1566
+ }
1567
+ const session = await this.authService.requireSession();
1568
+ try {
1569
+ const provider = await this.providerService.resolveProvider(input.provider);
1570
+ const portfolio = await this.dataBridgeClient.getPortfolio(session.accessToken, { provider });
1571
+ return {
1572
+ status: "connected",
1573
+ connected: true,
1574
+ portfolio
1575
+ };
1576
+ } catch (error) {
1577
+ const recovery = await retryAfterUnauthorizedRefresh({
1578
+ authService: this.authService,
1579
+ error,
1580
+ loginOptions,
1581
+ retry: async (accessToken) => {
1582
+ const provider = await this.providerService.resolveProvider(input.provider);
1583
+ const portfolio = await this.dataBridgeClient.getPortfolio(accessToken, { provider });
1584
+ return {
1585
+ status: "connected",
1586
+ connected: true,
1587
+ portfolio
1588
+ };
1589
+ }
1590
+ });
1591
+ if (recovery) {
1592
+ return recovery;
1593
+ }
1594
+ throw error;
1595
+ }
1596
+ }
1597
+ accountReadOptions() {
1598
+ return {
1599
+ clientName: "Helios MCP",
1600
+ scope: [AGENT_SCOPES.ACCOUNT_READ]
1601
+ };
1602
+ }
1603
+ };
1604
+
1605
+ // src/docs-service.ts
1606
+ import { existsSync, readdirSync, readFileSync, statSync } from "fs";
1607
+ import path2 from "path";
1608
+ var HeliosAgentMcpDocsService = class {
1609
+ constructor(options) {
1610
+ __publicField(this, "docsRoot");
1611
+ this.docsRoot = options.docsRoot;
1612
+ }
1613
+ async getIndex() {
1614
+ const documents = listDocumentPaths(this.docsRoot).map((documentPath) => this.readDocumentMetadata(documentPath));
1615
+ return {
1616
+ root: "docs",
1617
+ documents
1618
+ };
1619
+ }
1620
+ async getDocument(input) {
1621
+ const documentPath = resolveDocumentPath(this.docsRoot, input.path);
1622
+ if (!existsSync(documentPath) || !statSync(documentPath).isFile()) {
1623
+ throw new Error(`No document "${input.path}". helios_docs_get_index lists the documents.`);
1624
+ }
1625
+ const body = readFileSync(documentPath, "utf8");
1626
+ const stats = statSync(documentPath);
1627
+ const relativePath = toPosixPath(path2.relative(this.docsRoot, documentPath));
1628
+ return {
1629
+ path: relativePath,
1630
+ section: getDocumentSection(relativePath),
1631
+ title: extractDocumentTitle(body, relativePath),
1632
+ body,
1633
+ bytes: stats.size
1634
+ };
1635
+ }
1636
+ readDocumentMetadata(documentPath) {
1637
+ const body = readFileSync(documentPath, "utf8");
1638
+ const stats = statSync(documentPath);
1639
+ const relativePath = toPosixPath(path2.relative(this.docsRoot, documentPath));
1640
+ return {
1641
+ path: relativePath,
1642
+ section: getDocumentSection(relativePath),
1643
+ title: extractDocumentTitle(body, relativePath),
1644
+ bytes: stats.size
1645
+ };
1646
+ }
1647
+ };
1648
+ function getDefaultHeliosAgentMcpDocsRoot() {
1649
+ return path2.resolve(__dirname, "..", "docs");
1650
+ }
1651
+ function listDocumentPaths(root) {
1652
+ return walk(root).filter((filePath) => filePath.endsWith(".txt")).sort((left, right) => left.localeCompare(right));
1653
+ }
1654
+ function walk(currentPath) {
1655
+ const entries = readdirSync(currentPath, { withFileTypes: true });
1656
+ const files = [];
1657
+ for (const entry of entries) {
1658
+ const nextPath = path2.join(currentPath, entry.name);
1659
+ if (entry.isDirectory()) {
1660
+ files.push(...walk(nextPath));
1661
+ continue;
1662
+ }
1663
+ if (entry.isFile()) {
1664
+ files.push(nextPath);
1665
+ }
1666
+ }
1667
+ return files;
1668
+ }
1669
+ function resolveDocumentPath(root, relativeDocumentPath) {
1670
+ const normalizedPath = relativeDocumentPath.replace(/\\/g, "/").replace(/^\/+/, "");
1671
+ const resolvedPath = path2.resolve(root, normalizedPath);
1672
+ const relativePath = path2.relative(root, resolvedPath);
1673
+ if (!normalizedPath) {
1674
+ throw new Error("Document path is required.");
1675
+ }
1676
+ if (path2.isAbsolute(normalizedPath) || relativePath.startsWith("..") || path2.isAbsolute(relativePath)) {
1677
+ throw new Error(`Document path "${relativeDocumentPath}" is outside the docs root.`);
1678
+ }
1679
+ if (!resolvedPath.endsWith(".txt")) {
1680
+ throw new Error(`Document path "${relativeDocumentPath}" must point to a .txt file.`);
1681
+ }
1682
+ return resolvedPath;
1683
+ }
1684
+ function extractDocumentTitle(body, relativePath) {
1685
+ const lines = body.split(/\r?\n/);
1686
+ for (const line of lines) {
1687
+ const trimmed = line.trim();
1688
+ if (!trimmed || /^=+$/.test(trimmed)) {
1689
+ continue;
1690
+ }
1691
+ if (trimmed.startsWith("TITLE:")) {
1692
+ return trimmed.slice("TITLE:".length).trim();
1693
+ }
1694
+ return trimmed;
1695
+ }
1696
+ return path2.basename(relativePath, ".txt");
1697
+ }
1698
+ function getDocumentSection(relativePath) {
1699
+ return relativePath.split("/")[0] ?? "docs";
1700
+ }
1701
+ function toPosixPath(value) {
1702
+ return value.split(path2.sep).join("/");
1703
+ }
1704
+
1705
+ // src/ledger-service.ts
1706
+ var HeliosAgentMcpLedgerService = class {
1707
+ constructor(options) {
1708
+ __publicField(this, "authService");
1709
+ __publicField(this, "dataBridgeClient");
1710
+ this.authService = options.authService;
1711
+ this.dataBridgeClient = options.dataBridgeClient;
1712
+ }
1713
+ async getOpenPositions() {
1714
+ const loginOptions = this.accountReadOptions();
1715
+ const login = await this.authService.ensureLogin(loginOptions);
1716
+ if (login.status !== "connected") {
1717
+ return asPending(login);
1718
+ }
1719
+ const session = await this.authService.requireSession();
1720
+ try {
1721
+ const openPositions = await this.dataBridgeClient.getOpenPositions(session.accessToken);
1722
+ return {
1723
+ status: "connected",
1724
+ connected: true,
1725
+ openPositions
1726
+ };
1727
+ } catch (error) {
1728
+ const recovery = await retryAfterUnauthorizedRefresh({
1729
+ authService: this.authService,
1730
+ error,
1731
+ loginOptions,
1732
+ retry: async (accessToken) => {
1733
+ const openPositions = await this.dataBridgeClient.getOpenPositions(accessToken);
1734
+ return {
1735
+ status: "connected",
1736
+ connected: true,
1737
+ openPositions
1738
+ };
1739
+ }
1740
+ });
1741
+ if (recovery) {
1742
+ return recovery;
1743
+ }
1744
+ throw error;
1745
+ }
1746
+ }
1747
+ async getFills(input = {}) {
1748
+ const loginOptions = this.accountReadOptions();
1749
+ const login = await this.authService.ensureLogin(loginOptions);
1750
+ if (login.status !== "connected") {
1751
+ return asPending(login);
1752
+ }
1753
+ const session = await this.authService.requireSession();
1754
+ try {
1755
+ const fills = await this.dataBridgeClient.getLedgerFills(session.accessToken, input);
1756
+ return {
1757
+ status: "connected",
1758
+ connected: true,
1759
+ fills
1760
+ };
1761
+ } catch (error) {
1762
+ const recovery = await retryAfterUnauthorizedRefresh({
1763
+ authService: this.authService,
1764
+ error,
1765
+ loginOptions,
1766
+ retry: async (accessToken) => {
1767
+ const fills = await this.dataBridgeClient.getLedgerFills(accessToken, input);
1768
+ return {
1769
+ status: "connected",
1770
+ connected: true,
1771
+ fills
1772
+ };
1773
+ }
1774
+ });
1775
+ if (recovery) {
1776
+ return recovery;
1777
+ }
1778
+ throw error;
1779
+ }
1780
+ }
1781
+ async getSummary(input = {}) {
1782
+ const loginOptions = this.accountReadOptions();
1783
+ const login = await this.authService.ensureLogin(loginOptions);
1784
+ if (login.status !== "connected") {
1785
+ return asPending(login);
1786
+ }
1787
+ const session = await this.authService.requireSession();
1788
+ try {
1789
+ const summary = await this.dataBridgeClient.getLedgerSummary(session.accessToken, input);
1790
+ return {
1791
+ status: "connected",
1792
+ connected: true,
1793
+ summary
1794
+ };
1795
+ } catch (error) {
1796
+ const recovery = await retryAfterUnauthorizedRefresh({
1797
+ authService: this.authService,
1798
+ error,
1799
+ loginOptions,
1800
+ retry: async (accessToken) => {
1801
+ const summary = await this.dataBridgeClient.getLedgerSummary(accessToken, input);
1802
+ return {
1803
+ status: "connected",
1804
+ connected: true,
1805
+ summary
1806
+ };
1807
+ }
1808
+ });
1809
+ if (recovery) {
1810
+ return recovery;
1811
+ }
1812
+ throw error;
1813
+ }
1814
+ }
1815
+ accountReadOptions() {
1816
+ return {
1817
+ clientName: "Helios MCP",
1818
+ scope: [AGENT_SCOPES.ACCOUNT_READ]
1819
+ };
1820
+ }
1821
+ };
1822
+ function asPending(login) {
1823
+ return {
1824
+ ...login,
1825
+ status: "pending",
1826
+ connected: false
1827
+ };
1828
+ }
1829
+
1830
+ // src/market-service.ts
1831
+ var DEFAULT_SNAPSHOT_TOP = 50;
1832
+ var DEFAULT_SNAPSHOT_FIELDS = [
1833
+ // Price, size and returns.
1834
+ "rank",
1835
+ "lp",
1836
+ "qv",
1837
+ "r15m",
1838
+ "r1h",
1839
+ "r4h",
1840
+ "r24h",
1841
+ "r7d",
1842
+ // Regime and momentum turn: the one edge that held out of sample is buying a sharp turn up in a BREAKDOWN/CRASH coin.
1843
+ "reg",
1844
+ "hmi",
1845
+ "hmiv",
1846
+ "hs",
1847
+ // Stretch and location: fades beat chases in the wallet research.
1848
+ "ob",
1849
+ "obosp",
1850
+ "de",
1851
+ "ms",
1852
+ "srs",
1853
+ "srr",
1854
+ "fh24",
1855
+ "fl24",
1856
+ "fh7",
1857
+ "fl7",
1858
+ "vwz",
1859
+ "tri"
1860
+ ];
1861
+ function withSnapshotDefaults(input) {
1862
+ return {
1863
+ ...input,
1864
+ fields: input.fields ?? [...DEFAULT_SNAPSHOT_FIELDS],
1865
+ sort: input.sort ?? "rank"
1866
+ };
1867
+ }
1868
+ function topOfSnapshot(snapshot, input, top = DEFAULT_SNAPSHOT_TOP) {
1869
+ if (input.symbols?.length || snapshot.table.rows.length <= top) return snapshot;
1870
+ const baseIndex = snapshot.table.fieldIds.indexOf("base");
1871
+ const rows = snapshot.table.rows.slice(0, top);
1872
+ const returnedSymbols = baseIndex === -1 ? snapshot.returnedSymbols.slice(0, top) : rows.map((row) => String(row[baseIndex]));
1873
+ return { ...snapshot, symbolCount: rows.length, returnedSymbols, table: { ...snapshot.table, rows } };
1874
+ }
1875
+ function compactSymbolDetail(detail, full) {
1876
+ if (full) return detail;
1877
+ return { ...detail, changeMap: {}, metrics: null };
1878
+ }
1879
+ var HeliosAgentMcpMarketService = class {
1880
+ constructor(options) {
1881
+ __publicField(this, "authService");
1882
+ __publicField(this, "dataBridgeClient");
1883
+ this.authService = options.authService;
1884
+ this.dataBridgeClient = options.dataBridgeClient;
1885
+ }
1886
+ async getSnapshot(input = {}) {
1887
+ const loginOptions = {
1888
+ clientName: "Helios MCP",
1889
+ scope: [AGENT_SCOPES.MARKET_READ]
1890
+ };
1891
+ const login = await this.authService.ensureLogin(loginOptions);
1892
+ if (login.status !== "connected") {
1893
+ return {
1894
+ ...login,
1895
+ status: "pending",
1896
+ connected: false
1897
+ };
1898
+ }
1899
+ const session = await this.authService.requireSession();
1900
+ try {
1901
+ const snapshot = topOfSnapshot(await this.dataBridgeClient.getMarketSnapshot(session.accessToken, withSnapshotDefaults(input)), input);
1902
+ return {
1903
+ status: "connected",
1904
+ connected: true,
1905
+ snapshot
1906
+ };
1907
+ } catch (error) {
1908
+ const recovery = await retryAfterUnauthorizedRefresh({
1909
+ authService: this.authService,
1910
+ error,
1911
+ loginOptions,
1912
+ retry: async (accessToken) => {
1913
+ const snapshot = topOfSnapshot(await this.dataBridgeClient.getMarketSnapshot(accessToken, withSnapshotDefaults(input)), input);
1914
+ return {
1915
+ status: "connected",
1916
+ connected: true,
1917
+ snapshot
1918
+ };
1919
+ }
1920
+ });
1921
+ if (recovery) {
1922
+ return recovery;
1923
+ }
1924
+ throw error;
1925
+ }
1926
+ }
1927
+ async getSnapshotChunk(input = {}) {
1928
+ const loginOptions = {
1929
+ clientName: "Helios MCP",
1930
+ scope: [AGENT_SCOPES.MARKET_READ]
1931
+ };
1932
+ const login = await this.authService.ensureLogin(loginOptions);
1933
+ if (login.status !== "connected") {
1934
+ return {
1935
+ ...login,
1936
+ status: "pending",
1937
+ connected: false
1938
+ };
1939
+ }
1940
+ const session = await this.authService.requireSession();
1941
+ try {
1942
+ const snapshotChunk = await this.dataBridgeClient.getMarketSnapshotChunk(session.accessToken, withSnapshotDefaults(input));
1943
+ return {
1944
+ status: "connected",
1945
+ connected: true,
1946
+ snapshotChunk
1947
+ };
1948
+ } catch (error) {
1949
+ const recovery = await retryAfterUnauthorizedRefresh({
1950
+ authService: this.authService,
1951
+ error,
1952
+ loginOptions,
1953
+ retry: async (accessToken) => {
1954
+ const snapshotChunk = await this.dataBridgeClient.getMarketSnapshotChunk(accessToken, withSnapshotDefaults(input));
1955
+ return {
1956
+ status: "connected",
1957
+ connected: true,
1958
+ snapshotChunk
1959
+ };
1960
+ }
1961
+ });
1962
+ if (recovery) {
1963
+ return recovery;
1964
+ }
1965
+ throw error;
1966
+ }
1967
+ }
1968
+ async getSymbolDetail(input) {
1969
+ const loginOptions = {
1970
+ clientName: "Helios MCP",
1971
+ scope: [AGENT_SCOPES.MARKET_READ]
1972
+ };
1973
+ const login = await this.authService.ensureLogin(loginOptions);
1974
+ if (login.status !== "connected") {
1975
+ return {
1976
+ ...login,
1977
+ status: "pending",
1978
+ connected: false
1979
+ };
1980
+ }
1981
+ const session = await this.authService.requireSession();
1982
+ try {
1983
+ const detail = compactSymbolDetail(await this.dataBridgeClient.getMarketSymbolDetail(session.accessToken, { symbol: input.symbol }), input.full);
1984
+ return {
1985
+ status: "connected",
1986
+ connected: true,
1987
+ detail
1988
+ };
1989
+ } catch (error) {
1990
+ const recovery = await retryAfterUnauthorizedRefresh({
1991
+ authService: this.authService,
1992
+ error,
1993
+ loginOptions,
1994
+ retry: async (accessToken) => {
1995
+ const detail = compactSymbolDetail(await this.dataBridgeClient.getMarketSymbolDetail(accessToken, { symbol: input.symbol }), input.full);
1996
+ return {
1997
+ status: "connected",
1998
+ connected: true,
1999
+ detail
2000
+ };
2001
+ }
2002
+ });
2003
+ if (recovery) {
2004
+ return recovery;
2005
+ }
2006
+ throw error;
2007
+ }
2008
+ }
2009
+ };
2010
+
2011
+ // src/provider-service.ts
2012
+ var HeliosAgentMcpProviderService = class {
2013
+ constructor(options) {
2014
+ __publicField(this, "sessionStore");
2015
+ this.sessionStore = options.sessionStore;
2016
+ }
2017
+ async getActiveProvider() {
2018
+ const provider = await this.sessionStore.getActiveProvider();
2019
+ if (!provider) {
2020
+ return {
2021
+ configured: false,
2022
+ provider: null
2023
+ };
2024
+ }
2025
+ return {
2026
+ configured: true,
2027
+ provider
2028
+ };
2029
+ }
2030
+ async setActiveProvider(provider) {
2031
+ await this.sessionStore.setActiveProvider(provider);
2032
+ return {
2033
+ configured: true,
2034
+ provider
2035
+ };
2036
+ }
2037
+ async resolveProvider(explicitProvider) {
2038
+ if (explicitProvider) {
2039
+ return explicitProvider;
2040
+ }
2041
+ const activeProvider = await this.sessionStore.getActiveProvider();
2042
+ if (activeProvider) {
2043
+ return activeProvider;
2044
+ }
2045
+ throw new Error("No active provider configured. Call helios_provider_set_active or pass provider explicitly.");
2046
+ }
2047
+ };
2048
+
2049
+ // src/session-store.ts
2050
+ import { randomBytes } from "crypto";
2051
+ import { constants as fsConstants } from "fs";
2052
+ import { chmod, lstat, mkdir, open, rename, rm } from "fs/promises";
2053
+ import { homedir } from "os";
2054
+ import path3 from "path";
2055
+ import { z as z5 } from "zod";
2056
+ function getDefaultHeliosAgentMcpSessionPath() {
2057
+ return path3.join(homedir(), ".helios-agent-mcp", "session.json");
2058
+ }
2059
+ var pendingAgentLoginSchema2 = pendingAgentLoginSchema.extend({
2060
+ /** The hosts the login was started with (FileAgentSessionStoreOptions.issuer); its device code is only polled there. */
2061
+ issuer: agentSessionIssuerSchema.nullish()
2062
+ });
2063
+ var mcpSessionStateSchema = z5.object({
2064
+ session: storedAgentSessionSchema.nullish(),
2065
+ pendingLogin: pendingAgentLoginSchema2.nullish(),
2066
+ // A provider pinned before Binance was retired ('binance') reads as unpinned, so the agent must pick again, rather than
2067
+ // failing the whole file (which would drop the session) or silently switching it to Hyperliquid.
2068
+ activeProvider: providerSchema.nullish().catch(null)
2069
+ });
2070
+ var EMPTY_STATE = { session: null, pendingLogin: null, activeProvider: null };
2071
+ var FileAgentSessionStore = class {
2072
+ constructor(filePath = getDefaultHeliosAgentMcpSessionPath(), options = {}) {
2073
+ __publicField(this, "filePath", filePath);
2074
+ __publicField(this, "issuer");
2075
+ this.issuer = options.issuer ?? null;
2076
+ }
2077
+ async getSession() {
2078
+ const { session } = await this.readState();
2079
+ if (!session || !this.isFromIssuer(session.issuer)) return null;
2080
+ const { issuer: _issuer, ...rest } = session;
2081
+ return rest;
2082
+ }
2083
+ async getPendingLogin() {
2084
+ const { pendingLogin } = await this.readState();
2085
+ if (!pendingLogin || !this.isFromIssuer(pendingLogin.issuer)) return null;
2086
+ const { issuer: _issuer, ...rest } = pendingLogin;
2087
+ return rest;
2088
+ }
2089
+ async getActiveProvider() {
2090
+ return (await this.readState()).activeProvider;
2091
+ }
2092
+ async setSession(session) {
2093
+ await this.update((state) => ({
2094
+ ...state,
2095
+ session: this.issuer ? { ...session, issuer: this.issuer } : session
2096
+ }));
2097
+ }
2098
+ async setPendingLogin(pendingLogin) {
2099
+ await this.update((state) => ({
2100
+ ...state,
2101
+ pendingLogin: this.issuer ? { ...pendingLogin, issuer: this.issuer } : pendingLogin
2102
+ }));
2103
+ }
2104
+ async setActiveProvider(activeProvider) {
2105
+ await this.update((state) => ({
2106
+ ...state,
2107
+ activeProvider
2108
+ }));
2109
+ }
2110
+ async clearSession() {
2111
+ await this.update((state) => ({ ...state, session: null }), { deleteWhenEmpty: true });
2112
+ }
2113
+ async clearPendingLogin() {
2114
+ await this.update((state) => ({ ...state, pendingLogin: null }), { deleteWhenEmpty: true });
2115
+ }
2116
+ async clearActiveProvider() {
2117
+ await this.update((state) => ({ ...state, activeProvider: null }), { deleteWhenEmpty: true });
2118
+ }
2119
+ isFromIssuer(issuer) {
2120
+ return !this.issuer || isSameHeliosIssuer(issuer, this.issuer);
2121
+ }
2122
+ /**
2123
+ * Read-modify-write under the file's lock: tool calls run concurrently and several MCP processes can share the file,
2124
+ * so without it one write could drop another's (a refreshed session, a pending login).
2125
+ */
2126
+ async update(change, options = {}) {
2127
+ await withSessionFileLock(this.filePath, async () => {
2128
+ const state = change(await this.readState());
2129
+ if (options.deleteWhenEmpty && !state.session && !state.pendingLogin && !state.activeProvider) {
2130
+ await this.deleteFile();
2131
+ return;
2132
+ }
2133
+ await this.writeState(state);
2134
+ });
2135
+ }
2136
+ async readState() {
2137
+ let parsed;
2138
+ try {
2139
+ parsed = JSON.parse(await readFileNoFollow(this.filePath));
2140
+ } catch (error) {
2141
+ if (isErrnoException(error, "ENOENT") || isErrnoException(error, "ELOOP") || error instanceof SyntaxError) {
2142
+ return { ...EMPTY_STATE };
2143
+ }
2144
+ throw error;
2145
+ }
2146
+ if (parsed && typeof parsed === "object" && ("session" in parsed || "pendingLogin" in parsed || "activeProvider" in parsed)) {
2147
+ const envelope = mcpSessionStateSchema.safeParse(parsed);
2148
+ if (envelope.success) {
2149
+ return {
2150
+ session: envelope.data.session ?? null,
2151
+ pendingLogin: envelope.data.pendingLogin ?? null,
2152
+ activeProvider: envelope.data.activeProvider ?? null
2153
+ };
2154
+ }
2155
+ }
2156
+ const legacySession = storedAgentSessionSchema.safeParse(parsed);
2157
+ if (legacySession.success) {
2158
+ return {
2159
+ session: legacySession.data,
2160
+ pendingLogin: null,
2161
+ activeProvider: null
2162
+ };
2163
+ }
2164
+ return { ...EMPTY_STATE };
2165
+ }
2166
+ async writeState(state) {
2167
+ const directory = path3.dirname(this.filePath);
2168
+ await mkdir(directory, { recursive: true, mode: 448 });
2169
+ if (path3.resolve(directory) === path3.dirname(getDefaultHeliosAgentMcpSessionPath())) {
2170
+ await chmod(directory, 448).catch(() => void 0);
2171
+ }
2172
+ const tempPath = `${this.filePath}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
2173
+ const handle = await open(tempPath, "wx", 384);
2174
+ try {
2175
+ try {
2176
+ await handle.writeFile(`${JSON.stringify(state, null, 2)}
2177
+ `, "utf8");
2178
+ await handle.chmod(384);
2179
+ await handle.sync();
2180
+ } finally {
2181
+ await handle.close();
2182
+ }
2183
+ await rename(tempPath, this.filePath);
2184
+ } catch (error) {
2185
+ await rm(tempPath, { force: true }).catch(() => void 0);
2186
+ throw error;
2187
+ }
2188
+ }
2189
+ async deleteFile() {
2190
+ try {
2191
+ await rm(this.filePath, { force: true });
2192
+ } catch (error) {
2193
+ if (!isErrnoException(error, "ENOENT")) {
2194
+ throw error;
2195
+ }
2196
+ }
2197
+ }
2198
+ };
2199
+ var MemoryAgentMcpSessionStore = class {
2200
+ constructor(session = null, activeProvider = "hyperliquid") {
2201
+ __publicField(this, "session", session);
2202
+ __publicField(this, "activeProvider", activeProvider);
2203
+ __publicField(this, "pendingLogin", null);
2204
+ }
2205
+ async getSession() {
2206
+ return this.session;
2207
+ }
2208
+ async setSession(session) {
2209
+ this.session = session;
2210
+ }
2211
+ async clearSession() {
2212
+ this.session = null;
2213
+ }
2214
+ async getActiveProvider() {
2215
+ return this.activeProvider;
2216
+ }
2217
+ async setActiveProvider(provider) {
2218
+ this.activeProvider = provider;
2219
+ }
2220
+ async clearActiveProvider() {
2221
+ this.activeProvider = null;
2222
+ }
2223
+ async getPendingLogin() {
2224
+ return this.pendingLogin;
2225
+ }
2226
+ async setPendingLogin(pendingLogin) {
2227
+ this.pendingLogin = pendingLogin;
2228
+ }
2229
+ async clearPendingLogin() {
2230
+ this.pendingLogin = null;
2231
+ }
2232
+ };
2233
+ function isErrnoException(error, code) {
2234
+ return error instanceof Error && "code" in error && error.code === code;
2235
+ }
2236
+ async function readFileNoFollow(filePath) {
2237
+ const flags = process.platform === "win32" || fsConstants.O_NOFOLLOW === void 0 ? "r" : fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW;
2238
+ const handle = await open(filePath, flags);
2239
+ try {
2240
+ return await handle.readFile("utf8");
2241
+ } finally {
2242
+ await handle.close();
2243
+ }
2244
+ }
2245
+ var sessionFileQueues = /* @__PURE__ */ new Map();
2246
+ var LOCK_FILE_STALE_MS = 1e4;
2247
+ var LOCK_FILE_WAIT_MS = 2e3;
2248
+ async function withSessionFileLock(filePath, task) {
2249
+ const key = path3.resolve(filePath);
2250
+ const previous = sessionFileQueues.get(key) ?? Promise.resolve();
2251
+ let release;
2252
+ const done = new Promise((resolve) => release = resolve);
2253
+ const queued = previous.then(() => done);
2254
+ sessionFileQueues.set(key, queued);
2255
+ await previous;
2256
+ try {
2257
+ const locked = await acquireLockFile(`${key}.lock`);
2258
+ try {
2259
+ return await task();
2260
+ } finally {
2261
+ if (locked) await rm(`${key}.lock`, { force: true }).catch(() => void 0);
2262
+ }
2263
+ } finally {
2264
+ release();
2265
+ if (sessionFileQueues.get(key) === queued) sessionFileQueues.delete(key);
2266
+ }
2267
+ }
2268
+ async function acquireLockFile(lockPath) {
2269
+ const deadline = Date.now() + LOCK_FILE_WAIT_MS;
2270
+ for (; ; ) {
2271
+ try {
2272
+ await (await open(lockPath, "wx", 384)).close();
2273
+ return true;
2274
+ } catch (error) {
2275
+ if (!isErrnoException(error, "EEXIST")) return false;
2276
+ }
2277
+ const stats = await lstat(lockPath).catch(() => null);
2278
+ if (stats && Date.now() - stats.mtimeMs > LOCK_FILE_STALE_MS) {
2279
+ await rm(lockPath, { force: true }).catch(() => void 0);
2280
+ continue;
2281
+ }
2282
+ if (Date.now() >= deadline) return false;
2283
+ await new Promise((resolve) => setTimeout(resolve, 20 + Math.floor(Math.random() * 30)));
2284
+ }
2285
+ }
2286
+
2287
+ // src/trading-service.ts
2288
+ import { randomUUID } from "crypto";
2289
+ var HeliosAgentMcpTradingService = class {
2290
+ constructor(options) {
2291
+ __publicField(this, "authService");
2292
+ __publicField(this, "dataBridgeClient");
2293
+ __publicField(this, "providerService");
2294
+ this.authService = options.authService;
2295
+ this.dataBridgeClient = options.dataBridgeClient;
2296
+ this.providerService = options.providerService;
2297
+ }
2298
+ async open(input) {
2299
+ return this.submitIntent("OPEN", input);
2300
+ }
2301
+ async dca(input) {
2302
+ return this.submitIntent("DCA", input);
2303
+ }
2304
+ async reverse(input) {
2305
+ return this.submitIntent("REVERSE", input);
2306
+ }
2307
+ async close(input) {
2308
+ const loginOptions = {
2309
+ clientName: "Helios MCP",
2310
+ scope: [AGENT_SCOPES.ORDERS_WRITE]
2311
+ };
2312
+ const login = await this.authService.ensureLogin(loginOptions);
2313
+ if (login.status !== "connected") {
2314
+ return asPending2(login);
2315
+ }
2316
+ const session = await this.authService.requireSession();
2317
+ try {
2318
+ const provider = await this.providerService.resolveProvider(input.provider);
2319
+ const closure = await this.dataBridgeClient.closePosition(session.accessToken, {
2320
+ ...input,
2321
+ provider
2322
+ });
2323
+ return {
2324
+ status: "connected",
2325
+ connected: true,
2326
+ closure
2327
+ };
2328
+ } catch (error) {
2329
+ const recovery = await retryAfterUnauthorizedRefresh({
2330
+ authService: this.authService,
2331
+ error,
2332
+ loginOptions,
2333
+ retry: async (accessToken) => {
2334
+ const provider = await this.providerService.resolveProvider(input.provider);
2335
+ const closure = await this.dataBridgeClient.closePosition(accessToken, {
2336
+ ...input,
2337
+ provider
2338
+ });
2339
+ return {
2340
+ status: "connected",
2341
+ connected: true,
2342
+ closure
2343
+ };
2344
+ }
2345
+ });
2346
+ if (recovery) {
2347
+ return recovery;
2348
+ }
2349
+ throw error;
2350
+ }
2351
+ }
2352
+ async setTpSl(input) {
2353
+ const loginOptions = {
2354
+ clientName: "Helios MCP",
2355
+ scope: [AGENT_SCOPES.ORDERS_WRITE]
2356
+ };
2357
+ const login = await this.authService.ensureLogin(loginOptions);
2358
+ if (login.status !== "connected") {
2359
+ return asPending2(login);
2360
+ }
2361
+ const session = await this.authService.requireSession();
2362
+ try {
2363
+ const provider = await this.providerService.resolveProvider(input.provider);
2364
+ const protection = await this.dataBridgeClient.setTpSl(session.accessToken, {
2365
+ ...input,
2366
+ provider
2367
+ });
2368
+ return {
2369
+ status: "connected",
2370
+ connected: true,
2371
+ protection
2372
+ };
2373
+ } catch (error) {
2374
+ const recovery = await retryAfterUnauthorizedRefresh({
2375
+ authService: this.authService,
2376
+ error,
2377
+ loginOptions,
2378
+ retry: async (accessToken) => {
2379
+ const provider = await this.providerService.resolveProvider(input.provider);
2380
+ const protection = await this.dataBridgeClient.setTpSl(accessToken, {
2381
+ ...input,
2382
+ provider
2383
+ });
2384
+ return {
2385
+ status: "connected",
2386
+ connected: true,
2387
+ protection
2388
+ };
2389
+ }
2390
+ });
2391
+ if (recovery) {
2392
+ return recovery;
2393
+ }
2394
+ throw error;
2395
+ }
2396
+ }
2397
+ async deleteTpSl(input) {
2398
+ const loginOptions = {
2399
+ clientName: "Helios MCP",
2400
+ scope: [AGENT_SCOPES.ORDERS_CANCEL]
2401
+ };
2402
+ const login = await this.authService.ensureLogin(loginOptions);
2403
+ if (login.status !== "connected") {
2404
+ return asPending2(login);
2405
+ }
2406
+ const session = await this.authService.requireSession();
2407
+ try {
2408
+ const provider = await this.providerService.resolveProvider(input.provider);
2409
+ const deletion = await this.dataBridgeClient.deleteTpSl(session.accessToken, {
2410
+ ...input,
2411
+ provider
2412
+ });
2413
+ return {
2414
+ status: "connected",
2415
+ connected: true,
2416
+ deletion
2417
+ };
2418
+ } catch (error) {
2419
+ const recovery = await retryAfterUnauthorizedRefresh({
2420
+ authService: this.authService,
2421
+ error,
2422
+ loginOptions,
2423
+ retry: async (accessToken) => {
2424
+ const provider = await this.providerService.resolveProvider(input.provider);
2425
+ const deletion = await this.dataBridgeClient.deleteTpSl(accessToken, {
2426
+ ...input,
2427
+ provider
2428
+ });
2429
+ return {
2430
+ status: "connected",
2431
+ connected: true,
2432
+ deletion
2433
+ };
2434
+ }
2435
+ });
2436
+ if (recovery) {
2437
+ return recovery;
2438
+ }
2439
+ throw error;
2440
+ }
2441
+ }
2442
+ async getOpenOrders() {
2443
+ const loginOptions = {
2444
+ clientName: "Helios MCP",
2445
+ scope: [AGENT_SCOPES.ACCOUNT_READ]
2446
+ };
2447
+ const login = await this.authService.ensureLogin(loginOptions);
2448
+ if (login.status !== "connected") {
2449
+ return asPending2(login);
2450
+ }
2451
+ const session = await this.authService.requireSession();
2452
+ try {
2453
+ const orders = await this.dataBridgeClient.getOpenOrders(session.accessToken);
2454
+ return {
2455
+ status: "connected",
2456
+ connected: true,
2457
+ orders
2458
+ };
2459
+ } catch (error) {
2460
+ const recovery = await retryAfterUnauthorizedRefresh({
2461
+ authService: this.authService,
2462
+ error,
2463
+ loginOptions,
2464
+ retry: async (accessToken) => {
2465
+ const orders = await this.dataBridgeClient.getOpenOrders(accessToken);
2466
+ return {
2467
+ status: "connected",
2468
+ connected: true,
2469
+ orders
2470
+ };
2471
+ }
2472
+ });
2473
+ if (recovery) {
2474
+ return recovery;
2475
+ }
2476
+ throw error;
2477
+ }
2478
+ }
2479
+ async cancelOrder(input) {
2480
+ const loginOptions = {
2481
+ clientName: "Helios MCP",
2482
+ scope: [AGENT_SCOPES.ORDERS_CANCEL]
2483
+ };
2484
+ const login = await this.authService.ensureLogin(loginOptions);
2485
+ if (login.status !== "connected") {
2486
+ return asPending2(login);
2487
+ }
2488
+ const session = await this.authService.requireSession();
2489
+ try {
2490
+ const cancellation = await this.dataBridgeClient.cancelOrder(session.accessToken, input);
2491
+ return {
2492
+ status: "connected",
2493
+ connected: true,
2494
+ cancellation
2495
+ };
2496
+ } catch (error) {
2497
+ const recovery = await retryAfterUnauthorizedRefresh({
2498
+ authService: this.authService,
2499
+ error,
2500
+ loginOptions,
2501
+ retry: async (accessToken) => {
2502
+ const cancellation = await this.dataBridgeClient.cancelOrder(accessToken, input);
2503
+ return {
2504
+ status: "connected",
2505
+ connected: true,
2506
+ cancellation
2507
+ };
2508
+ }
2509
+ });
2510
+ if (recovery) {
2511
+ return recovery;
2512
+ }
2513
+ throw error;
2514
+ }
2515
+ }
2516
+ async submitIntent(intentType, input) {
2517
+ const loginOptions = {
2518
+ clientName: "Helios MCP",
2519
+ scope: [AGENT_SCOPES.ORDERS_WRITE]
2520
+ };
2521
+ const login = await this.authService.ensureLogin(loginOptions);
2522
+ if (login.status !== "connected") {
2523
+ return asPending2(login);
2524
+ }
2525
+ const session = await this.authService.requireSession();
2526
+ const idempotencyKey = input.idempotencyKey ?? randomUUID().replace(/-/g, "");
2527
+ try {
2528
+ const provider = await this.providerService.resolveProvider(input.provider);
2529
+ const submission = await this.dataBridgeClient.submitTradeIntent(session.accessToken, {
2530
+ ...input,
2531
+ provider,
2532
+ intentType,
2533
+ idempotencyKey
2534
+ });
2535
+ return {
2536
+ status: "connected",
2537
+ connected: true,
2538
+ submission
2539
+ };
2540
+ } catch (error) {
2541
+ const recovery = await retryAfterUnauthorizedRefresh({
2542
+ authService: this.authService,
2543
+ error,
2544
+ loginOptions,
2545
+ retry: async (accessToken) => {
2546
+ const provider = await this.providerService.resolveProvider(input.provider);
2547
+ const submission = await this.dataBridgeClient.submitTradeIntent(accessToken, {
2548
+ ...input,
2549
+ provider,
2550
+ intentType,
2551
+ idempotencyKey
2552
+ });
2553
+ return {
2554
+ status: "connected",
2555
+ connected: true,
2556
+ submission
2557
+ };
2558
+ }
2559
+ });
2560
+ if (recovery) {
2561
+ return recovery;
2562
+ }
2563
+ throw error;
2564
+ }
2565
+ }
2566
+ };
2567
+ function asPending2(login) {
2568
+ return {
2569
+ ...login,
2570
+ status: "pending",
2571
+ connected: false
2572
+ };
2573
+ }
2574
+
2575
+ // src/lean/format.ts
2576
+ var usd = (v, signed = false) => {
2577
+ if (v === null || v === void 0 || !Number.isFinite(v)) return "n/a";
2578
+ const sign = v < 0 ? "-" : signed && v > 0 ? "+" : "";
2579
+ const a = Math.abs(v);
2580
+ const body = a >= 1e9 ? `${trim(a / 1e9, 2)}B` : a >= 1e6 ? `${trim(a / 1e6, 2)}M` : a >= 1e4 ? `${Math.round(a / 1e3)}k` : a >= 1e3 ? `${trim(a / 1e3, 1)}k` : `${Math.round(a)}`;
2581
+ return `${sign}$${body}`;
2582
+ };
2583
+ var trim = (v, places) => String(Number(v.toFixed(places)));
2584
+ var pct = (v, places = 1) => v === null || v === void 0 || !Number.isFinite(v) ? "n/a" : `${v > 0 ? "+" : ""}${v.toFixed(places)}%`;
2585
+ var price = (v) => {
2586
+ if (v === null || v === void 0 || !Number.isFinite(v)) return "n/a";
2587
+ const a = Math.abs(v);
2588
+ if (a >= 1e3) return v.toLocaleString("en-US", { maximumFractionDigits: a >= 1e4 ? 0 : 2 });
2589
+ if (a >= 1) return v.toFixed(a >= 100 ? 2 : a >= 10 ? 2 : 3);
2590
+ return v.toPrecision(4);
2591
+ };
2592
+ var ago = (t, now) => {
2593
+ if (t === null || t === void 0) return "n/a";
2594
+ const s = Math.max(0, Math.round((now - t) / 1e3));
2595
+ if (s < 60) return `${s}s`;
2596
+ if (s < 3600) return `${Math.round(s / 60)}m`;
2597
+ if (s < 172800) return `${Math.round(s / 3600)}h`;
2598
+ return `${Math.round(s / 86400)}d`;
2599
+ };
2600
+ var STYLE = {
2601
+ scalper: "scalper",
2602
+ "intraday trader": "intraday",
2603
+ "swing trader": "swing",
2604
+ "position trader": "position"
2605
+ };
2606
+ var trader = (m) => {
2607
+ const tags = [STYLE[m.style], m.win === null ? null : `${Math.round(m.win)}% win`].filter(Boolean);
2608
+ return `${m.nick} #${m.rank}${tags.length ? ` (${tags.join(", ")})` : ""}`;
2609
+ };
2610
+ var VERB = { open: "opened", add: "added", reduce: "trimmed", close: "closed", flip: "flipped to" };
2611
+ var moveText = (m) => {
2612
+ const pnl = m.pnl === null ? "" : ` ${usd(m.pnl, true)}${m.pnlPct === null ? "" : ` (${pct(m.pnlPct)})`}`;
2613
+ const lev = m.lev && (m.kind === "open" || m.kind === "add" || m.kind === "flip") ? ` ${trim(m.lev, 1)}x` : "";
2614
+ return `${VERB[m.kind]} ${m.side} ${m.coin} ${usd(m.usd)} @ ${price(m.px)}${lev}${pnl}`;
2615
+ };
2616
+ var entryText = (side, edgePct) => {
2617
+ if (!side || edgePct === null) return null;
2618
+ const below = side === "long" ? edgePct >= 0 : edgePct < 0;
2619
+ return `${Math.abs(edgePct).toFixed(1)}% ${below ? "under" : "over"} their entry`;
2620
+ };
2621
+ function renderMarket(m, now = Date.now()) {
2622
+ const head = [`market \xB7 ${m.rows.length} of ${m.total}${m.offset ? ` from #${m.offset + 1}` : ""}`];
2623
+ if (m.market.os !== null) head.push(`${m.market.os} of 23 large coins down >7% in 24h`);
2624
+ if (m.market.Mti !== null) head.push(`majors trend ${pct(m.market.Mti, 2)}`);
2625
+ if (m.asOf) head.push(`as of ${ago(Date.parse(m.asOf), now)} ago`);
2626
+ const lines = [head.join(" \xB7 ")];
2627
+ if (m.rows.length) {
2628
+ lines.push("coin | price | 1h | 24h | vol 24h | funding APR | open interest | setup | top traders");
2629
+ for (const r of m.rows) {
2630
+ const sm = r.sm?.side ? [`${r.sm.side} ${Math.round(r.sm.share * 100)}%`, entryText(r.sm.side, r.sm.edgePct), r.sm.discount === "strict" ? "discount" : null].filter(Boolean).join(", ") : "none";
2631
+ lines.push(
2632
+ [r.coin, price(r.px), pct(r.r1h), pct(r.r24h), usd(r.volUsd), pct(r.fundAprPct, 0), usd(r.oiUsd), r.setup ?? "n/a", sm].join(
2633
+ " | "
2634
+ )
2635
+ );
2636
+ }
2637
+ } else lines.push("No coins match.");
2638
+ if (m.unknown.length) lines.push(`Not listed on Hyperliquid: ${m.unknown.join(", ")}`);
2639
+ if (m.next !== null) lines.push(`More: call again with offset=${m.next}.`);
2640
+ return lines.join("\n");
2641
+ }
2642
+ var pileText = (p, now) => `${p.coin} | ${p.traders} traders ${p.dir === "in" ? "into" : "out of"} ${p.side} | ${usd(p.totalUsd)} | ${ago(p.first, now)} to ${ago(p.last, now)} ago | ${p.names.join(", ")}`;
2643
+ function renderBoard(b, now = Date.now()) {
2644
+ const title = {
2645
+ conviction: "where the top traders agree (weighted by track record)",
2646
+ discount: "coins below the top traders\u2019 entry (longs) or above it (shorts)",
2647
+ flows: "biggest net flows of the last hour",
2648
+ pileins: "pile-ins: 3+ top traders moving the same way within 45 min, last 2h"
2649
+ };
2650
+ const lines = [`smart money \xB7 ${title[b.view]}${b.asOf ? ` \xB7 as of ${ago(b.asOf, now)} ago` : ""}`];
2651
+ if (b.view === "pileins") {
2652
+ if (b.pileIns.length) lines.push("coin | who | size | when | biggest movers", ...b.pileIns.map((p) => pileText(p, now)));
2653
+ else lines.push("No pile-ins in the last 2 hours.");
2654
+ } else if (b.rows.length) {
2655
+ lines.push("coin | side | share | size | traders | their entry | price | vs entry | 1h flow | setup | 24h");
2656
+ for (const r of b.rows)
2657
+ lines.push(
2658
+ [
2659
+ r.coin,
2660
+ r.side ?? "n/a",
2661
+ `${Math.round(r.share * 100)}%`,
2662
+ usd(r.sideUsd),
2663
+ `${r.traders} (${trim(r.heads, 1)} weighted)`,
2664
+ price(r.entry),
2665
+ price(r.mark),
2666
+ entryText(r.side, r.edgePct) ?? "n/a",
2667
+ usd(r.flow1hUsd, true),
2668
+ r.setup ?? "n/a",
2669
+ pct(r.r24h)
2670
+ ].join(" | ")
2671
+ );
2672
+ } else lines.push("No coins on this board right now.");
2673
+ if (b.note) lines.push(b.note);
2674
+ return lines.join("\n");
2675
+ }
2676
+ function renderMoves(m, now = Date.now()) {
2677
+ const lines = [];
2678
+ if (m.order === "oldest") lines.push(m.moves.length ? `${m.moves.length} new move${m.moves.length === 1 ? "" : "s"}, oldest first` : "No new moves.");
2679
+ else lines.push(m.moves.length ? `latest ${m.moves.length} moves, newest first` : "No moves match.");
2680
+ for (const mv of m.moves) lines.push(`${ago(mv.t, now)} ago | ${trader(mv)} | ${moveText(mv)}`);
2681
+ if (m.gap) lines.push("Some moves before these were missed: the cursor was older than the moves kept.");
2682
+ if (m.more) lines.push(`More waiting: call again with since=${m.next}.`);
2683
+ else if (m.next) lines.push(`next=${m.next} (pass as since to get only newer moves)`);
2684
+ if (m.note) lines.push(m.note);
2685
+ return lines.join("\n");
2686
+ }
2687
+ function renderCoin(c, you, now = Date.now()) {
2688
+ const lines = [];
2689
+ const sm = c.sm;
2690
+ const mk = c.market;
2691
+ if (sm && sm.side) {
2692
+ const ours = sm.side === "long" ? sm.long : sm.short;
2693
+ const theirs = sm.side === "long" ? sm.short : sm.long;
2694
+ const tags = [sm.discount === "strict" ? "on the discount board" : null, sm.conviction !== null ? "on the conviction board" : null].filter(Boolean);
2695
+ lines.push(
2696
+ `${c.coin}: top traders lean ${sm.side.toUpperCase()} ${Math.round(sm.share * 100)}% (${usd(ours.usd)} vs ${usd(theirs.usd)})${tags.length ? ` \xB7 ${tags.join(", ")}` : ""}`
2697
+ );
2698
+ lines.push(
2699
+ `book | long ${sm.long.traders} traders (${trim(sm.long.heads, 1)} weighted), avg entry ${price(sm.long.entry)} | short ${sm.short.traders} traders (${trim(sm.short.heads, 1)} weighted), avg entry ${price(sm.short.entry)}`
2700
+ );
2701
+ } else lines.push(`${c.coin}: no top-trader positions right now`);
2702
+ if (mk) {
2703
+ const vs = sm?.side ? entryText(sm.side, sm.edgePct) : null;
2704
+ lines.push(`price | ${price(mk.px)}${vs ? ` (${vs})` : ""} \xB7 15m ${pct(mk.r15m)} \xB7 1h ${pct(mk.r1h)} \xB7 24h ${pct(mk.r24h)}`);
2705
+ lines.push(`setup | ${mk.setup ?? "n/a"} \xB7 funding ${pct(mk.fundAprPct, 0)} APR \xB7 open interest ${usd(mk.oiUsd)} \xB7 vol ${usd(mk.volUsd)}`);
2706
+ } else lines.push("price | no market data for this coin");
2707
+ if (sm && sm.flow1hUsd !== 0) lines.push(`flow | ${usd(Math.abs(sm.flow1hUsd))} net toward ${sm.flow1hUsd > 0 ? "long" : "short"} in the last hour`);
2708
+ if (sm?.pileIn) lines.push(`pile-in | ${pileText(sm.pileIn, now)}`);
2709
+ if (c.moves.length) lines.push(...c.moves.map((m) => `move | ${ago(m.t, now)} ago | ${trader(m)} | ${moveText(m)}`));
2710
+ else lines.push("move | none of $10k+ recently");
2711
+ if (you) {
2712
+ const p = you.position;
2713
+ lines.push(
2714
+ p ? `you | ${p.side.toLowerCase()} ${trim(Math.abs(p.size), 6)} @ ${price(p.entryPrice)}, open PnL ${usd(p.unrealizedPnl, true)}, ${trim(p.leverage, 1)}x \xB7 ${usd(you.availableBalance)} free` : `you | no ${c.coin} position \xB7 ${usd(you.availableBalance)} free`
2715
+ );
2716
+ }
2717
+ if (c.note) lines.push(c.note);
2718
+ return lines.join("\n");
2719
+ }
2720
+ function renderAccount(p, orders, summary) {
2721
+ const lines = [
2722
+ `account \xB7 value ${usd(p.totalBalance)} \xB7 free ${usd(p.availableBalance)} \xB7 margin used ${usd(p.totalMargin)} \xB7 open PnL ${usd(p.unrealizedPnl, true)}`
2723
+ ];
2724
+ if (p.positions.length) {
2725
+ lines.push("positions: coin | side | size @ entry | now | open PnL | leverage | liquidation | TP/SL (id)");
2726
+ for (const pos of p.positions) {
2727
+ const prot = p.protectiveOrders.filter((o) => o.symbol.replace(/USDT$/, "").toUpperCase() === pos.symbol.replace(/USDT$/, "").toUpperCase()).map((o) => `${o.kind === "TAKE_PROFIT" ? "TP" : "SL"} ${o.stopPrice ?? "n/a"} (${o.id})`);
2728
+ lines.push(
2729
+ [
2730
+ pos.symbol,
2731
+ pos.side.toLowerCase(),
2732
+ `${trim(Math.abs(pos.size), 6)} @ ${price(pos.entryPrice)}`,
2733
+ price(pos.markPrice),
2734
+ usd(pos.unrealizedPnl, true),
2735
+ `${trim(pos.leverage, 1)}x`,
2736
+ price(pos.liquidationPrice ?? null),
2737
+ prot.length ? prot.join(", ") : "none"
2738
+ ].join(" | ")
2739
+ );
2740
+ }
2741
+ } else lines.push("positions: none");
2742
+ if (orders) {
2743
+ const working = orders.filter((o) => !o.isTrigger);
2744
+ if (working.length) {
2745
+ lines.push("open orders: coin | side | size @ price | type | oid");
2746
+ for (const o of working) lines.push([o.coin, o.side === "B" ? "buy" : "sell", `${o.sz} @ ${o.limitPx}`, `${o.orderType}${o.tif ? ` ${o.tif}` : ""}${o.reduceOnly ? " reduce-only" : ""}`, o.oid].join(" | "));
2747
+ } else lines.push("open orders: none");
2748
+ }
2749
+ if (summary)
2750
+ lines.push(
2751
+ `${summary.window}: realized ${usd(summary.realizedPnl, true)} \xB7 fees ${usd(summary.totalFees)} \xB7 ${summary.winCount} wins / ${summary.lossCount} losses`
2752
+ );
2753
+ return lines.join("\n");
2754
+ }
2755
+ function positionLine(pos) {
2756
+ return [
2757
+ `${pos.side.toLowerCase()} ${trim(Math.abs(pos.size), 6)} ${pos.symbol.replace(/USDT$/, "")} @ ${price(pos.entryPrice)}`,
2758
+ `mark ${price(pos.markPrice)}`,
2759
+ `open PnL ${usd(pos.unrealizedPnl, true)}`,
2760
+ `${trim(pos.leverage, 1)}x`,
2761
+ `liquidation ${price(pos.liquidationPrice ?? null)}`
2762
+ ].join(" \xB7 ");
2763
+ }
2764
+ var cents = (v, signed = false) => Math.abs(v) >= 1e3 ? usd(v, signed) : `${v < 0 ? "-" : signed && v > 0 ? "+" : ""}$${Math.abs(v).toFixed(2)}`;
2765
+ function renderTrades(page, paper2, coin2, now = Date.now()) {
2766
+ const f = page.fills;
2767
+ const closes = f.filter((x) => x.closedPnl !== 0);
2768
+ const pnl = f.reduce((sum, x) => sum + x.closedPnl, 0);
2769
+ const fees = f.reduce((sum, x) => sum + x.fee, 0);
2770
+ const wins = closes.filter((x) => x.closedPnl > 0).length;
2771
+ const head = `${paper2 ? "PRACTICE " : ""}trades ${page.window}${coin2 ? ` ${coin2}` : ""} \xB7 ${f.length} fills \xB7 realized ${cents(pnl, true)} \xB7 fees ${cents(fees)} \xB7 net ${cents(pnl - fees, true)} \xB7 ${wins} wins / ${closes.length - wins} losses`;
2772
+ if (!f.length) return `${head}
2773
+ no fills in this window`;
2774
+ const lines = [head, "ago | coin | what | size @ price | realized PnL | fee"];
2775
+ for (const x of f)
2776
+ lines.push([ago(x.time, now), x.coin, x.dir, `${x.sz} @ ${price(x.px)}`, x.closedPnl === 0 ? "-" : cents(x.closedPnl, true), cents(x.fee)].join(" | "));
2777
+ if (page.hasMore) lines.push("more fills are older: widen window or raise limit");
2778
+ return lines.join("\n");
2779
+ }
2780
+ function renderCandles(coin2, interval, c) {
2781
+ const iso = (t) => new Date(t).toISOString().slice(0, 16).replace("T", " ");
2782
+ const lines = [`${coin2} ${interval} candles \xB7 ${c.candles.length}, oldest first${c.complete ? "" : " \xB7 some missing minutes left gaps"}`, "time UTC,open,high,low,close,volume"];
2783
+ for (const k of c.candles) lines.push([iso(k.t), k.o, k.h, k.l, k.c, k.v].join(","));
2784
+ return lines.join("\n");
2785
+ }
2786
+
2787
+ // src/lean/tools.ts
2788
+ import { createHash } from "crypto";
2789
+ import { z as z6 } from "zod";
2790
+ var text = (body, data) => ({
2791
+ content: [{ type: "text", text: body }],
2792
+ structuredContent: data
2793
+ });
2794
+ var loginText = (login) => `Helios is not connected yet. Open this link to approve access, then call this tool again:
2795
+ ${login.verificationUriComplete}
2796
+ User code: ${login.userCode}`;
2797
+ var pendingResult = (login) => text(loginText(login), {
2798
+ status: "pending",
2799
+ connected: false,
2800
+ actionRequired: true,
2801
+ loginUrl: login.verificationUriComplete,
2802
+ userCode: login.userCode
2803
+ });
2804
+ async function withSession(auth, scope, call) {
2805
+ const loginOptions = { clientName: "Helios MCP", scope };
2806
+ const login = await auth.ensureLogin(loginOptions);
2807
+ if (login.status !== "connected") return { ok: false, pending: login };
2808
+ const session = await auth.requireSession();
2809
+ try {
2810
+ return { ok: true, value: await call(session.accessToken) };
2811
+ } catch (error) {
2812
+ const recovery = await retryAfterUnauthorizedRefresh({ authService: auth, error, loginOptions, retry: call });
2813
+ if (recovery === null) throw error;
2814
+ if (isPending(recovery)) return { ok: false, pending: recovery };
2815
+ return { ok: true, value: recovery };
2816
+ }
2817
+ }
2818
+ function isPending(value) {
2819
+ return Boolean(value && typeof value === "object" && value.connected === false && "verificationUriComplete" in value);
2820
+ }
2821
+ async function sessionScopes(auth) {
2822
+ const session = await auth.requireSession().catch(() => null);
2823
+ return new Set(session?.scope ?? []);
2824
+ }
2825
+ var coin = z6.string().trim().min(1).max(36).describe("e.g. BTC, kPEPE, xyz:TSLA");
2826
+ var smBoardInputSchema = z6.object({
2827
+ view: z6.enum(["conviction", "discount", "flows", "pileins"]).optional().describe("conviction (default): where they agree; discount: price better than their entry; flows: last hour; pileins: 3+ moving together"),
2828
+ side: z6.enum(["long", "short", "any"]).optional(),
2829
+ limit: z6.number().int().min(1).max(25).optional().describe("Default 8")
2830
+ });
2831
+ var smMovesInputSchema = z6.object({
2832
+ since: z6.string().regex(/^\d{1,16}-\d{1,16}$/).optional().describe("next from your last call: only newer moves"),
2833
+ coin: coin.optional(),
2834
+ kind: z6.enum(["entries", "exits", "all"]).optional().describe("entries = opens, adds; exits = trims, closes"),
2835
+ min_usd: z6.number().nonnegative().optional().describe("Default 25000"),
2836
+ limit: z6.number().int().min(1).max(50).optional().describe("Default 15")
2837
+ });
2838
+ var smCoinInputSchema = z6.object({ coin });
2839
+ var marketInputSchema = z6.object({
2840
+ coins: z6.string().max(400).optional().describe('Comma list, e.g. "BTC,HYPE"; omit for all'),
2841
+ limit: z6.number().int().min(1).max(300).optional().describe("Default 20"),
2842
+ offset: z6.number().int().nonnegative().optional(),
2843
+ sort: z6.enum(["volume", "rank", "move1h", "move24h", "funding", "lean", "edge"]).optional(),
2844
+ filter: z6.enum(["turning", "falling", "stretched_up", "stretched_down", "sm_long", "sm_short", "discount"]).optional().describe("turning = turning up from breakdown (held up in backtests); discount = better than their entry")
2845
+ });
2846
+ var paper = z6.boolean().optional().describe("true = your practice account: live prices, simulated fills, no real money (market orders only)");
2847
+ var accountInputSchema = z6.object({ paper });
2848
+ var tradeInputSchema = z6.object({
2849
+ coin,
2850
+ action: z6.enum(["open", "add", "close", "reverse", "reset"]).describe("reset = start the practice account over (paper only; margin_usd = starting balance, default $10k)"),
2851
+ side: z6.enum(["long", "short"]).optional().describe("For open, add, reverse"),
2852
+ // Only obviously absurd values are cut here; Hyperliquid's margin check and the person's approval do the rest.
2853
+ margin_usd: z6.number().positive().max(1e6).optional().describe("Size = margin x leverage"),
2854
+ leverage: z6.number().positive().max(100).optional(),
2855
+ sl_pct: z6.number().positive().max(95).optional().describe("Stop, % from entry"),
2856
+ tp_pct: z6.number().positive().max(1e3).optional().describe("Take profit, % from entry"),
2857
+ limit_px: z6.number().positive().optional().describe("Omit for market"),
2858
+ slippage_bps: z6.number().int().min(1).max(1e3).optional().describe("Market only: worst fill vs price, bps. Default 300 (3%)"),
2859
+ fraction: z6.number().positive().max(1).optional().describe("close only: share to close, e.g. 0.5 = half. Default 1 = all"),
2860
+ paper,
2861
+ key: z6.string().regex(/^[A-Za-z0-9_-]{8,64}$/).optional().describe("Same key on retry = never placed twice. Omitted: repeats of the same call within 60s of the first are placed once")
2862
+ });
2863
+ var TRADE_KEY_WINDOW_SECONDS = 60;
2864
+ function tradeKeyFor(input, account) {
2865
+ const order = [
2866
+ account,
2867
+ input.coin.toUpperCase(),
2868
+ input.action,
2869
+ input.side ?? "",
2870
+ input.margin_usd ?? "",
2871
+ input.leverage ?? "",
2872
+ input.sl_pct ?? "",
2873
+ input.tp_pct ?? "",
2874
+ input.limit_px ?? "",
2875
+ input.slippage_bps ?? "",
2876
+ input.fraction ?? ""
2877
+ ];
2878
+ return `mcpw-${createHash("sha256").update(`trade:${order.join("|")}`).digest("base64url")}`;
2879
+ }
2880
+ var protectInputSchema = z6.object({
2881
+ coin,
2882
+ op: z6.enum(["set", "remove", "cancel", "move"]).describe("set TP/SL; remove a TP/SL; cancel an order; move an open order to px"),
2883
+ tp_px: z6.number().positive().optional(),
2884
+ sl_px: z6.number().positive().optional(),
2885
+ order_id: z6.string().max(64).optional().describe("From account: TP/SL id or order oid"),
2886
+ px: z6.number().positive().optional().describe("move only: the new limit (or trigger) price"),
2887
+ paper
2888
+ });
2889
+ var tradesInputSchema = z6.object({
2890
+ window: z6.enum(["24h", "7d", "30d"]).optional().describe("Default 7d"),
2891
+ coin: coin.optional(),
2892
+ limit: z6.number().int().min(1).max(100).optional().describe("Default 20"),
2893
+ paper
2894
+ });
2895
+ var candlesInputSchema = z6.object({
2896
+ coin: z6.string().regex(/^[A-Za-z0-9]{1,20}$/).describe("e.g. BTC, kPEPE"),
2897
+ interval: z6.enum(["1m", "5m", "15m", "1h", "4h", "1d"]),
2898
+ limit: z6.number().int().min(1).max(1e3).optional().describe("Default 100")
2899
+ });
2900
+ var READ = [AGENT_SCOPES.MARKET_READ];
2901
+ var sameCoinName = (a, b) => a.replace(/USDT$/, "").toUpperCase() === b.replace(/USDT$/, "").toUpperCase();
2902
+ var sameCoin = (pos, coin2) => sameCoinName(pos.symbol, coin2);
2903
+ var paperTotals = (p) => p.realizedPnl === void 0 ? "" : `
2904
+ practice totals: realized ${p.realizedPnl >= 0 ? "+" : "-"}$${Math.abs(p.realizedPnl).toFixed(2)} \xB7 fees $${(p.feesPaid ?? 0).toFixed(2)} \xB7 funding $${(p.fundingPaid ?? 0).toFixed(2)} \xB7 liquidations ${p.liquidations ?? 0}`;
2905
+ function wrongSideTpSl(side, mark, tp, sl) {
2906
+ const long = side === "LONG";
2907
+ const at = `${side.toLowerCase()} (price ${price(mark)})`;
2908
+ if (sl !== void 0 && (long ? sl >= mark : sl <= mark)) return `Stop ${price(sl)} must be ${long ? "under" : "over"} the price for a ${at}`;
2909
+ if (tp !== void 0 && (long ? tp <= mark : tp >= mark)) return `Take profit ${price(tp)} must be ${long ? "over" : "under"} the price for a ${at}`;
2910
+ return null;
2911
+ }
2912
+ function createLeanToolDefinitions(services) {
2913
+ const { auth, dataBridge } = services;
2914
+ const smBoard = {
2915
+ name: "sm_board",
2916
+ title: "Smart Money Board",
2917
+ description: "Where the top Hyperliquid traders (ranked on a year of results) are positioned: side, size, entry vs price.",
2918
+ inputSchema: smBoardInputSchema,
2919
+ async execute(input) {
2920
+ const r = await withSession(auth, READ, (token) => dataBridge.getSmartMoneyBoard(token, input));
2921
+ return r.ok ? text(renderBoard(r.value), r.value) : pendingResult(r.pending);
2922
+ }
2923
+ };
2924
+ const smMoves = {
2925
+ name: "sm_moves",
2926
+ title: "Smart Money Moves",
2927
+ description: "Top traders\u2019 latest trades. Poll with since=next for new ones only.",
2928
+ inputSchema: smMovesInputSchema,
2929
+ async execute(input) {
2930
+ const r = await withSession(
2931
+ auth,
2932
+ READ,
2933
+ (token) => dataBridge.getSmartMoneyMoves(token, { since: input.since, coin: input.coin, kind: input.kind, minUsd: input.min_usd, limit: input.limit })
2934
+ );
2935
+ return r.ok ? text(renderMoves(r.value), r.value) : pendingResult(r.pending);
2936
+ }
2937
+ };
2938
+ const smCoin = {
2939
+ name: "sm_coin",
2940
+ title: "Smart Money Coin Brief",
2941
+ description: "One coin in one call: top traders\u2019 book, entries and trades, price, setup, funding, OI, your position.",
2942
+ inputSchema: smCoinInputSchema,
2943
+ async execute(input) {
2944
+ const r = await withSession(auth, READ, (token) => dataBridge.getSmartMoneyCoin(token, input.coin));
2945
+ if (!r.ok) return pendingResult(r.pending);
2946
+ let you = null;
2947
+ if ((await sessionScopes(auth)).has(AGENT_SCOPES.ACCOUNT_READ)) {
2948
+ const p = await withSession(auth, [AGENT_SCOPES.ACCOUNT_READ], (token) => dataBridge.getPortfolio(token, { provider: "hyperliquid" })).catch(
2949
+ () => null
2950
+ );
2951
+ if (p?.ok) {
2952
+ you = {
2953
+ position: p.value.positions.find((pos) => sameCoin(pos, r.value.coin)) ?? null,
2954
+ availableBalance: p.value.availableBalance
2955
+ };
2956
+ }
2957
+ }
2958
+ return text(renderCoin(r.value, you), { ...r.value, you });
2959
+ }
2960
+ };
2961
+ const market = {
2962
+ name: "market",
2963
+ title: "Market Scan",
2964
+ description: "All coins: price, moves, volume, funding, OI, setup label, top traders\u2019 lean. Filter, sort, page.",
2965
+ inputSchema: marketInputSchema,
2966
+ async execute(input) {
2967
+ const coins = input.coins?.split(",").map((c) => c.trim()).filter(Boolean).slice(0, 50);
2968
+ const r = await withSession(
2969
+ auth,
2970
+ READ,
2971
+ (token) => dataBridge.getAgentMarket(token, {
2972
+ ...coins?.length ? { coins } : {},
2973
+ limit: input.limit,
2974
+ offset: input.offset,
2975
+ sort: input.sort,
2976
+ filter: input.filter
2977
+ })
2978
+ );
2979
+ return r.ok ? text(renderMarket(r.value), r.value) : pendingResult(r.pending);
2980
+ }
2981
+ };
2982
+ const account = {
2983
+ name: "account",
2984
+ title: "Account",
2985
+ description: "Balance, positions with TP/SL ids, open orders, 30-day result. paper=true for the practice account.",
2986
+ inputSchema: accountInputSchema,
2987
+ async execute(input) {
2988
+ if (input.paper) {
2989
+ const p = await withSession(auth, [AGENT_SCOPES.ACCOUNT_READ], (token) => dataBridge.getPaperPortfolio(token));
2990
+ if (!p.ok) return pendingResult(p.pending);
2991
+ return text(`PRACTICE ${renderAccount(p.value, null, null)}${paperTotals(p.value)}`, { paper: true, portfolio: p.value });
2992
+ }
2993
+ const r = await withSession(
2994
+ auth,
2995
+ [AGENT_SCOPES.ACCOUNT_READ],
2996
+ (token) => Promise.all([
2997
+ dataBridge.getPortfolio(token, { provider: "hyperliquid" }),
2998
+ dataBridge.getOpenOrders(token).catch(() => null),
2999
+ dataBridge.getLedgerSummary(token, { window: "30d" }).catch(() => null)
3000
+ ])
3001
+ );
3002
+ if (!r.ok) return pendingResult(r.pending);
3003
+ const [portfolio, orders, summary] = r.value;
3004
+ return text(renderAccount(portfolio, orders, summary), { portfolio, orders, summary });
3005
+ }
3006
+ };
3007
+ const trade = {
3008
+ name: "trade",
3009
+ title: "Trade",
3010
+ description: "Open, add to, close (all or part) or reverse a position, sized by margin in USD. Answers with the position after the order. paper=true to practice.",
3011
+ inputSchema: tradeInputSchema,
3012
+ acts: true,
3013
+ async execute(input) {
3014
+ if (input.fraction !== void 0 && input.action !== "close") throw new Error("fraction is for close only");
3015
+ if (input.action === "reset") {
3016
+ if (!input.paper) throw new Error("reset is for the practice account only: pass paper=true");
3017
+ const r2 = await withSession(auth, [AGENT_SCOPES.ORDERS_WRITE], (token) => dataBridge.resetPaperAccount(token, input.margin_usd));
3018
+ if (!r2.ok) return pendingResult(r2.pending);
3019
+ return text(`Practice account started over with ${input.margin_usd ? `$${input.margin_usd}` : "the default balance"}.`, { paper: true, reset: true });
3020
+ }
3021
+ if (input.paper) return paperTrade(input);
3022
+ if (input.action === "close") {
3023
+ const r2 = await withSession(
3024
+ auth,
3025
+ [AGENT_SCOPES.ORDERS_WRITE],
3026
+ (token) => dataBridge.closePosition(token, {
3027
+ provider: "hyperliquid",
3028
+ symbol: input.coin,
3029
+ ...input.fraction !== void 0 && input.fraction < 1 ? { fraction: input.fraction } : {},
3030
+ // A close is a market order: the same cap applies (unset, data-bridge uses its default).
3031
+ ...input.slippage_bps ? { maxSlippageBps: input.slippage_bps } : {}
3032
+ })
3033
+ );
3034
+ if (!r2.ok) return pendingResult(r2.pending);
3035
+ if (r2.value.outcome === "no_position") return text(`No open ${input.coin} position: nothing was sent.`, r2.value);
3036
+ const after2 = await positionAfter(input.coin, false);
3037
+ const what = r2.value.mode === "PARTIAL" ? `${Math.round((input.fraction ?? 1) * 100)}% of the ${input.coin} position` : `the whole ${input.coin} position`;
3038
+ return text(`Sent a market order closing ${what}. ${after2.text}`, { ...r2.value, after: after2.position });
3039
+ }
3040
+ if (!input.side || !input.margin_usd) throw new Error(`${input.action} needs side and margin_usd`);
3041
+ if (input.limit_px && input.slippage_bps) throw new Error("slippage_bps is for market orders: a limit order fills at limit_px or better");
3042
+ const prot = await protectionFor(input);
3043
+ if ("pending" in prot) return pendingResult(prot.pending);
3044
+ const protection = prot.value;
3045
+ const intentType = input.action === "open" ? "OPEN" : input.action === "add" ? "DCA" : "REVERSE";
3046
+ let idempotencyKey = input.key;
3047
+ const r = await withSession(auth, [AGENT_SCOPES.ORDERS_WRITE], async (token) => {
3048
+ if (!idempotencyKey) {
3049
+ const session = await auth.requireSession();
3050
+ idempotencyKey = tradeKeyFor(input, `${session.ownerUserId}|${session.agentGrantId}`);
3051
+ }
3052
+ return dataBridge.submitTradeIntent(token, {
3053
+ provider: "hyperliquid",
3054
+ symbol: input.coin,
3055
+ intentType,
3056
+ direction: input.side === "long" ? "LONG" : "SHORT",
3057
+ size: { mode: "MARGIN_USD", value: input.margin_usd },
3058
+ execution: input.limit_px ? { style: "LIMIT_GTC", limitPrice: input.limit_px } : { style: "MARKET" },
3059
+ // Unset, data-bridge caps a market order at its own default (300 bps).
3060
+ ...input.slippage_bps ? { maxSlippageBps: input.slippage_bps } : {},
3061
+ ...input.leverage ? { leverageOverride: input.leverage } : {},
3062
+ ...protection ? { protection } : {},
3063
+ idempotencyKey,
3064
+ ...input.key ? {} : { idempotencyWindowSeconds: TRADE_KEY_WINDOW_SECONDS }
3065
+ });
3066
+ }).catch((error) => {
3067
+ if (!isHeliosAgentApiError(error) || error.code !== "CONFLICT" || !/being placed/.test(error.message)) throw error;
3068
+ const why = input.key ? `key ${idempotencyKey} was already used` : `an identical trade call within ${TRADE_KEY_WINDOW_SECONDS}s of the first is the same order`;
3069
+ throw new Error(
3070
+ `${error.message} (${why}, so this may be that earlier order.) Call account to see whether it went through. To place another one on purpose, call trade again with a new key.`
3071
+ );
3072
+ });
3073
+ if (!r.ok) return pendingResult(r.pending);
3074
+ const s = r.value;
3075
+ const parts = [
3076
+ s.outcome === "replayed" ? `Already sent earlier with key ${idempotencyKey}${input.key ? "" : ` (an identical call within ${TRADE_KEY_WINDOW_SECONDS}s of the first is the same order)`}: nothing new was placed. To place another on purpose, pass a new key.` : `Sent: ${input.action} ${input.side} ${input.coin}, $${input.margin_usd} margin${input.leverage ? ` at ${input.leverage}x` : ""}, ${input.limit_px ? `limit ${price(input.limit_px)}` : "market"}.`
3077
+ ];
3078
+ if (protection?.stopLoss) parts.push(`Stop ${price(protection.stopLoss.triggerPrice)}.`);
3079
+ if (protection?.takeProfit) parts.push(`Take profit ${price(protection.takeProfit.triggerPrice)}.`);
3080
+ if (s.orderStatus) parts.push(`Order status: ${s.orderStatus}.`);
3081
+ if (s.rejectedProtection?.length) parts.push(`Hyperliquid refused part of the TP/SL; check account and set it with protect.`);
3082
+ parts.push(input.key ? `key=${idempotencyKey}` : `key=${idempotencyKey} (an identical call within ${TRADE_KEY_WINDOW_SECONDS}s of the first is the same order)`);
3083
+ let after;
3084
+ if (!input.limit_px && s.outcome !== "replayed") {
3085
+ const a = await positionAfter(input.coin, false);
3086
+ parts.push(a.text);
3087
+ after = a.position;
3088
+ } else if (input.limit_px) parts.push("A limit order rests until filled: see it in account; move it with protect op=move.");
3089
+ return text(parts.join(" "), { ...s, idempotencyKey, ...after !== void 0 ? { after } : {} });
3090
+ }
3091
+ };
3092
+ const protect = {
3093
+ name: "protect",
3094
+ title: "Protect",
3095
+ description: "Set or remove TP/SL on a position, or cancel an order.",
3096
+ inputSchema: protectInputSchema,
3097
+ acts: true,
3098
+ async execute(input) {
3099
+ if (input.op === "set") {
3100
+ if (!input.tp_px && !input.sl_px) throw new Error("set needs tp_px, sl_px or both");
3101
+ const p = await withSession(
3102
+ auth,
3103
+ [AGENT_SCOPES.ACCOUNT_READ],
3104
+ (token) => input.paper ? dataBridge.getPaperPortfolio(token) : dataBridge.getPortfolio(token, { provider: "hyperliquid" })
3105
+ ).catch(
3106
+ (error) => {
3107
+ throw new Error(
3108
+ `Could not read your ${input.coin} position to check the TP/SL side (${error instanceof Error ? error.message : String(error)}); nothing was set.`
3109
+ );
3110
+ }
3111
+ );
3112
+ if (!p.ok) return pendingResult(p.pending);
3113
+ const position = p.value.positions.find((pos) => sameCoin(pos, input.coin));
3114
+ if (!position) throw new Error(`No open ${input.coin} position: nothing was set.`);
3115
+ if (!(position.markPrice > 0)) throw new Error(`No price for ${input.coin} to check the TP/SL against: nothing was set. Try again shortly.`);
3116
+ const wrong = wrongSideTpSl(position.side, position.markPrice, input.tp_px, input.sl_px);
3117
+ if (wrong) throw new Error(`${wrong}: it would trigger at once and close the position. Nothing was set.`);
3118
+ const tpSl = {
3119
+ provider: "hyperliquid",
3120
+ symbol: input.coin,
3121
+ applyTo: "FULL_POSITION",
3122
+ // TP/SL attach to the position on Hyperliquid; data-bridge links them to the order that opened it.
3123
+ parentClientOrderId: `hl-position:${input.coin}`,
3124
+ ...input.tp_px ? { takeProfit: { kind: "MARKET", triggerPrice: input.tp_px } } : {},
3125
+ ...input.sl_px ? { stopLoss: { kind: "MARKET", triggerPrice: input.sl_px } } : {}
3126
+ };
3127
+ const r2 = await withSession(
3128
+ auth,
3129
+ [AGENT_SCOPES.ORDERS_WRITE],
3130
+ (token) => input.paper ? dataBridge.setPaperTpSl(token, tpSl) : dataBridge.setTpSl(token, tpSl)
3131
+ );
3132
+ if (!r2.ok) return pendingResult(r2.pending);
3133
+ const legs = [input.tp_px ? `take profit ${price(input.tp_px)}` : null, input.sl_px ? `stop ${price(input.sl_px)}` : null].filter(Boolean);
3134
+ return text(`${input.paper ? "Practice: s" : "S"}et on the whole ${input.coin} position: ${legs.join(", ")}.`, r2.value);
3135
+ }
3136
+ if (!input.order_id) throw new Error(`${input.op} needs order_id (from account)`);
3137
+ if (input.op === "remove") {
3138
+ const del = { provider: "hyperliquid", symbol: input.coin, orderId: input.order_id };
3139
+ const r2 = await withSession(
3140
+ auth,
3141
+ [AGENT_SCOPES.ORDERS_CANCEL],
3142
+ (token) => input.paper ? dataBridge.deletePaperTpSl(token, del) : dataBridge.deleteTpSl(token, del)
3143
+ );
3144
+ return r2.ok ? text(`Removed TP/SL ${input.order_id} on ${input.coin}.`, r2.value) : pendingResult(r2.pending);
3145
+ }
3146
+ if (input.paper) throw new Error("The practice account has no resting orders (every order fills at market): nothing to cancel or move");
3147
+ const oid = Number(input.order_id);
3148
+ if (!Number.isInteger(oid) || oid < 0) throw new Error(`${input.op} needs the numeric order oid from account`);
3149
+ if (input.op === "move") {
3150
+ if (!input.px) throw new Error("move needs px, the new price");
3151
+ const o = await withSession(auth, [AGENT_SCOPES.ACCOUNT_READ], (token) => dataBridge.getOpenOrders(token));
3152
+ if (!o.ok) return pendingResult(o.pending);
3153
+ const order = o.value.find((x) => x.oid === oid);
3154
+ if (!order) throw new Error(`No open order ${oid}: it may have filled or been cancelled. Call account.`);
3155
+ const size = Number(order.sz);
3156
+ const r2 = await withSession(auth, [AGENT_SCOPES.ORDERS_WRITE], (token) => dataBridge.modifyOrder(token, { symbol: order.coin, oid, price: input.px, size }));
3157
+ return r2.ok ? text(`Moved order ${oid} on ${order.coin} to ${price(input.px)} (size ${order.sz}).`, r2.value) : pendingResult(r2.pending);
3158
+ }
3159
+ const r = await withSession(auth, [AGENT_SCOPES.ORDERS_CANCEL], (token) => dataBridge.cancelOrder(token, { symbol: input.coin, oid }));
3160
+ return r.ok ? text(`Cancelled order ${oid} on ${input.coin}.`, r.value) : pendingResult(r.pending);
3161
+ }
3162
+ };
3163
+ const trades = {
3164
+ name: "trades",
3165
+ title: "Your Trades",
3166
+ description: "Your recent fills, newest first: price, size, realized PnL and fees, with totals. Learn from results. paper=true for practice.",
3167
+ inputSchema: tradesInputSchema,
3168
+ async execute(input) {
3169
+ const window = input.window ?? "7d";
3170
+ const want = input.limit ?? 20;
3171
+ const ask = { window, limit: input.coin ? 200 : want };
3172
+ const r = await withSession(
3173
+ auth,
3174
+ [AGENT_SCOPES.ACCOUNT_READ],
3175
+ (token) => input.paper ? dataBridge.getPaperLedgerFills(token, ask) : dataBridge.getLedgerFills(token, ask)
3176
+ );
3177
+ if (!r.ok) return pendingResult(r.pending);
3178
+ const fills = r.value.fills.filter((f) => !input.coin || sameCoinName(f.coin, input.coin)).slice(0, want);
3179
+ const page = { ...r.value, fills };
3180
+ return text(renderTrades(page, Boolean(input.paper), input.coin ?? null), { paper: Boolean(input.paper), ...page });
3181
+ }
3182
+ };
3183
+ const candles = {
3184
+ name: "candles",
3185
+ title: "Candles",
3186
+ description: "OHLCV candles for one coin (up to 7 days), oldest first, as CSV.",
3187
+ inputSchema: candlesInputSchema,
3188
+ async execute(input) {
3189
+ const r = await withSession(
3190
+ auth,
3191
+ READ,
3192
+ (token) => dataBridge.getMarketCandles(token, { symbol: input.coin, interval: input.interval, limit: input.limit ?? 100 })
3193
+ );
3194
+ return r.ok ? text(renderCandles(input.coin, input.interval, r.value), r.value) : pendingResult(r.pending);
3195
+ }
3196
+ };
3197
+ async function positionAfter(coinName, isPaper) {
3198
+ const p = await withSession(
3199
+ auth,
3200
+ [AGENT_SCOPES.ACCOUNT_READ],
3201
+ (token) => isPaper ? dataBridge.getPaperPortfolio(token) : dataBridge.getPortfolio(token, { provider: "hyperliquid" })
3202
+ ).catch(() => null);
3203
+ if (!p?.ok || !Array.isArray(p.value?.positions)) return { text: "Call account to see the position.", position: null };
3204
+ const position = p.value.positions.find((pos) => sameCoin(pos, coinName)) ?? null;
3205
+ return { text: position ? `Now: ${positionLine(position)}.` : `Now: no ${coinName} position.`, position };
3206
+ }
3207
+ async function paperTrade(input) {
3208
+ if (input.limit_px) throw new Error("The practice account fills market orders only: leave out limit_px");
3209
+ if (input.action === "close") {
3210
+ const r2 = await withSession(
3211
+ auth,
3212
+ [AGENT_SCOPES.ORDERS_WRITE],
3213
+ (token) => dataBridge.closePaperPosition(token, { symbol: input.coin, ...input.fraction !== void 0 && input.fraction < 1 ? { fraction: input.fraction } : {} })
3214
+ );
3215
+ if (!r2.ok) return pendingResult(r2.pending);
3216
+ if (!r2.value.length) return text(`Practice: no open ${input.coin} position, nothing closed.`, { paper: true, fills: [] });
3217
+ const after2 = await positionAfter(input.coin, true);
3218
+ const pnl = r2.value.reduce((sum, f) => sum + f.pnl - f.fee, 0);
3219
+ return text(`Practice: closed ${input.fraction && input.fraction < 1 ? `${Math.round(input.fraction * 100)}% of ` : ""}${input.coin} @ ${price(r2.value[r2.value.length - 1].price)}, ${pnl >= 0 ? "+" : "-"}$${Math.abs(pnl).toFixed(2)} after fees. ${after2.text}`, {
3220
+ paper: true,
3221
+ fills: r2.value,
3222
+ after: after2.position
3223
+ });
3224
+ }
3225
+ if (!input.side || !input.margin_usd) throw new Error(`${input.action} needs side and margin_usd`);
3226
+ const protection = await protectionFor(input);
3227
+ if ("pending" in protection) return pendingResult(protection.pending);
3228
+ const r = await withSession(
3229
+ auth,
3230
+ [AGENT_SCOPES.ORDERS_WRITE],
3231
+ (token) => dataBridge.submitPaperIntent(token, {
3232
+ provider: "hyperliquid",
3233
+ symbol: input.coin,
3234
+ intentType: input.action === "open" ? "OPEN" : input.action === "add" ? "DCA" : "REVERSE",
3235
+ direction: input.side === "long" ? "LONG" : "SHORT",
3236
+ size: { mode: "MARGIN_USD", value: input.margin_usd },
3237
+ execution: { style: "MARKET" },
3238
+ ...input.leverage ? { leverageOverride: input.leverage } : {},
3239
+ ...protection.value ? { protection: protection.value } : {}
3240
+ })
3241
+ );
3242
+ if (!r.ok) return pendingResult(r.pending);
3243
+ const after = await positionAfter(input.coin, true);
3244
+ const filled = r.value.filter((f) => f.side !== null);
3245
+ const parts = [
3246
+ filled.length ? `Practice: ${input.action} ${input.side} ${input.coin} filled @ ${price(filled[filled.length - 1].price)}, $${input.margin_usd} margin${input.leverage ? ` at ${input.leverage}x` : ""}.` : `Practice: ${input.action} ${input.side} ${input.coin} sent, no fill reported.`
3247
+ ];
3248
+ if (protection.value?.stopLoss) parts.push(`Stop ${price(protection.value.stopLoss.triggerPrice)}.`);
3249
+ if (protection.value?.takeProfit) parts.push(`Take profit ${price(protection.value.takeProfit.triggerPrice)}.`);
3250
+ parts.push(after.text);
3251
+ return text(parts.join(" "), { paper: true, fills: r.value, after: after.position });
3252
+ }
3253
+ async function protectionFor(input) {
3254
+ if (input.side === "short" && (input.tp_pct ?? 0) >= 100) throw new Error("A short can gain at most 100%: tp_pct must be under 100");
3255
+ if (!input.sl_pct && !input.tp_pct) return { value: void 0 };
3256
+ let ref = input.limit_px ?? null;
3257
+ if (ref === null) {
3258
+ const m = await withSession(auth, READ, (token) => dataBridge.getAgentMarket(token, { coins: [input.coin], limit: 1 }));
3259
+ if (!m.ok) return { pending: m.pending };
3260
+ ref = m.value.rows[0]?.px ?? null;
3261
+ }
3262
+ if (ref === null) throw new Error(`No price for ${input.coin} to place the stop from; trade without sl_pct/tp_pct, then use protect`);
3263
+ const long = input.side === "long";
3264
+ const at = (p, away) => Number((p * (1 + away / 100)).toPrecision(6));
3265
+ return {
3266
+ value: {
3267
+ ...input.sl_pct ? { stopLoss: { kind: "MARKET", triggerPrice: at(ref, long ? -input.sl_pct : input.sl_pct) } } : {},
3268
+ ...input.tp_pct ? { takeProfit: { kind: "MARKET", triggerPrice: at(ref, long ? input.tp_pct : -input.tp_pct) } } : {}
3269
+ }
3270
+ };
3271
+ }
3272
+ return { smBoard, smMoves, smCoin, market, account, trade, protect, trades, candles };
3273
+ }
3274
+ function leanToolsFor(tools, profile) {
3275
+ const reads = [tools.smBoard, tools.smMoves, tools.smCoin, tools.market];
3276
+ const list = profile === "signals" ? [...reads, tools.account, tools.trades, tools.candles] : [...reads, tools.account, tools.trades, tools.trade, tools.protect, tools.candles];
3277
+ return list;
3278
+ }
3279
+
3280
+ // src/tools.ts
3281
+ import { z as z7 } from "zod";
3282
+ var pollLoginInputSchema = z7.object({
3283
+ /**
3284
+ * Optional, kept for older agents: the login in progress is always the one polled, and any other code (one that isn't
3285
+ * its user code) is refused.
3286
+ */
3287
+ deviceCode: z7.string().min(1).optional()
3288
+ });
3289
+ var startLoginOutputSchema = z7.object({
3290
+ status: z7.literal("pending"),
3291
+ userCode: z7.string(),
3292
+ verificationUri: z7.string().url(),
3293
+ verificationUriComplete: z7.string().url(),
3294
+ expiresInSeconds: z7.number().int().positive(),
3295
+ pollIntervalSeconds: z7.number().int().positive(),
3296
+ requestedScope: z7.array(agentScopeSchema).min(1),
3297
+ startedAt: z7.string().datetime()
3298
+ });
3299
+ var ensureLoginInputSchema = z7.object({
3300
+ clientName: z7.string().min(1).max(64).optional(),
3301
+ scope: z7.array(agentScopeSchema).min(1).optional(),
3302
+ constraints: agentConstraintsSchema.optional()
3303
+ });
3304
+ var providerSelectionInputSchema = z7.object({
3305
+ provider: providerSchema.optional()
3306
+ });
3307
+ var providerSetInputSchema = z7.object({
3308
+ provider: providerSchema
3309
+ });
3310
+ var activeProviderSchema = z7.object({
3311
+ configured: z7.boolean(),
3312
+ provider: providerSchema.nullable()
3313
+ });
3314
+ var ensureLoginOutputSchema = z7.discriminatedUnion("status", [
3315
+ agentSessionViewSchema.extend({
3316
+ status: z7.literal("connected"),
3317
+ connected: z7.literal(true)
3318
+ }),
3319
+ startLoginOutputSchema.extend({
3320
+ status: z7.literal("pending"),
3321
+ connected: z7.literal(false),
3322
+ message: z7.string()
3323
+ })
3324
+ ]);
3325
+ var pollLoginOutputSchema = z7.discriminatedUnion("status", [
3326
+ agentSessionViewSchema.extend({
3327
+ status: z7.literal("connected"),
3328
+ connected: z7.literal(true)
3329
+ }),
3330
+ z7.object({
3331
+ status: z7.enum(["pending", "rejected", "expired", "failed"]),
3332
+ connected: z7.literal(false),
3333
+ message: z7.string()
3334
+ })
3335
+ ]);
3336
+ var walletStateToolOutputSchema = z7.discriminatedUnion("status", [
3337
+ z7.object({
3338
+ status: z7.literal("connected"),
3339
+ connected: z7.literal(true),
3340
+ walletState: walletStateSchema
3341
+ }),
3342
+ startLoginOutputSchema.extend({
3343
+ status: z7.literal("pending"),
3344
+ connected: z7.literal(false),
3345
+ message: z7.string()
3346
+ })
3347
+ ]);
3348
+ var portfolioToolOutputSchema = z7.discriminatedUnion("status", [
3349
+ z7.object({
3350
+ status: z7.literal("connected"),
3351
+ connected: z7.literal(true),
3352
+ portfolio: portfolioSnapshotSchema
3353
+ }),
3354
+ startLoginOutputSchema.extend({
3355
+ status: z7.literal("pending"),
3356
+ connected: z7.literal(false),
3357
+ message: z7.string()
3358
+ })
3359
+ ]);
3360
+ var openPositionsToolOutputSchema = z7.discriminatedUnion("status", [
3361
+ z7.object({
3362
+ status: z7.literal("connected"),
3363
+ connected: z7.literal(true),
3364
+ openPositions: openPositionsSnapshotSchema
3365
+ }),
3366
+ startLoginOutputSchema.extend({
3367
+ status: z7.literal("pending"),
3368
+ connected: z7.literal(false),
3369
+ message: z7.string()
3370
+ })
3371
+ ]);
3372
+ var ledgerFillsToolOutputSchema = z7.discriminatedUnion("status", [
3373
+ z7.object({
3374
+ status: z7.literal("connected"),
3375
+ connected: z7.literal(true),
3376
+ fills: ledgerFillsPageSchema
3377
+ }),
3378
+ startLoginOutputSchema.extend({
3379
+ status: z7.literal("pending"),
3380
+ connected: z7.literal(false),
3381
+ message: z7.string()
3382
+ })
3383
+ ]);
3384
+ var ledgerSummaryToolOutputSchema = z7.discriminatedUnion("status", [
3385
+ z7.object({
3386
+ status: z7.literal("connected"),
3387
+ connected: z7.literal(true),
3388
+ summary: ledgerSummarySchema
3389
+ }),
3390
+ startLoginOutputSchema.extend({
3391
+ status: z7.literal("pending"),
3392
+ connected: z7.literal(false),
3393
+ message: z7.string()
3394
+ })
3395
+ ]);
3396
+ var marketSnapshotToolOutputSchema = z7.discriminatedUnion("status", [
3397
+ z7.object({
3398
+ status: z7.literal("connected"),
3399
+ connected: z7.literal(true),
3400
+ snapshot: marketSnapshotSchema
3401
+ }),
3402
+ startLoginOutputSchema.extend({
3403
+ status: z7.literal("pending"),
3404
+ connected: z7.literal(false),
3405
+ message: z7.string()
3406
+ })
3407
+ ]);
3408
+ var marketSnapshotChunkToolOutputSchema = z7.discriminatedUnion("status", [
3409
+ z7.object({
3410
+ status: z7.literal("connected"),
3411
+ connected: z7.literal(true),
3412
+ snapshotChunk: marketSnapshotChunkSchema
3413
+ }),
3414
+ startLoginOutputSchema.extend({
3415
+ status: z7.literal("pending"),
3416
+ connected: z7.literal(false),
3417
+ message: z7.string()
3418
+ })
3419
+ ]);
3420
+ var marketSymbolDetailToolOutputSchema = z7.discriminatedUnion("status", [
3421
+ z7.object({
3422
+ status: z7.literal("connected"),
3423
+ connected: z7.literal(true),
3424
+ detail: marketSymbolDetailSchema
3425
+ }),
3426
+ startLoginOutputSchema.extend({
3427
+ status: z7.literal("pending"),
3428
+ connected: z7.literal(false),
3429
+ message: z7.string()
3430
+ })
3431
+ ]);
3432
+ var tradeActionInputSchema = submitTradeIntentInputSchema.omit({
3433
+ intentType: true
3434
+ });
3435
+ var tradeActionToolOutputSchema = z7.discriminatedUnion("status", [
3436
+ z7.object({
3437
+ status: z7.literal("connected"),
3438
+ connected: z7.literal(true),
3439
+ submission: submitTradeIntentResultSchema
3440
+ }),
3441
+ startLoginOutputSchema.extend({
3442
+ status: z7.literal("pending"),
3443
+ connected: z7.literal(false),
3444
+ message: z7.string()
3445
+ })
3446
+ ]);
3447
+ var closeTradeToolOutputSchema = z7.discriminatedUnion("status", [
3448
+ z7.object({
3449
+ status: z7.literal("connected"),
3450
+ connected: z7.literal(true),
3451
+ closure: closePositionResultSchema
3452
+ }),
3453
+ startLoginOutputSchema.extend({
3454
+ status: z7.literal("pending"),
3455
+ connected: z7.literal(false),
3456
+ message: z7.string()
3457
+ })
3458
+ ]);
3459
+ var setTpSlToolOutputSchema = z7.discriminatedUnion("status", [
3460
+ z7.object({
3461
+ status: z7.literal("connected"),
3462
+ connected: z7.literal(true),
3463
+ protection: setTpSlResultSchema
3464
+ }),
3465
+ startLoginOutputSchema.extend({
3466
+ status: z7.literal("pending"),
3467
+ connected: z7.literal(false),
3468
+ message: z7.string()
3469
+ })
3470
+ ]);
3471
+ var deleteTpSlToolOutputSchema = z7.discriminatedUnion("status", [
3472
+ z7.object({
3473
+ status: z7.literal("connected"),
3474
+ connected: z7.literal(true),
3475
+ deletion: deleteTpSlResultSchema
3476
+ }),
3477
+ startLoginOutputSchema.extend({
3478
+ status: z7.literal("pending"),
3479
+ connected: z7.literal(false),
3480
+ message: z7.string()
3481
+ })
3482
+ ]);
3483
+ var openOrdersToolOutputSchema = z7.discriminatedUnion("status", [
3484
+ z7.object({
3485
+ status: z7.literal("connected"),
3486
+ connected: z7.literal(true),
3487
+ orders: z7.array(openOrderSchema)
3488
+ }),
3489
+ startLoginOutputSchema.extend({
3490
+ status: z7.literal("pending"),
3491
+ connected: z7.literal(false),
3492
+ message: z7.string()
3493
+ })
3494
+ ]);
3495
+ var cancelOrderToolOutputSchema = z7.discriminatedUnion("status", [
3496
+ z7.object({
3497
+ status: z7.literal("connected"),
3498
+ connected: z7.literal(true),
3499
+ cancellation: cancelOrderResultSchema
3500
+ }),
3501
+ startLoginOutputSchema.extend({
3502
+ status: z7.literal("pending"),
3503
+ connected: z7.literal(false),
3504
+ message: z7.string()
3505
+ })
3506
+ ]);
3507
+ var documentPathInputSchema = z7.object({
3508
+ path: z7.string().min(1)
3509
+ });
3510
+ var docsIndexSchema = z7.object({
3511
+ root: z7.literal("docs"),
3512
+ documents: z7.array(
3513
+ z7.object({
3514
+ path: z7.string(),
3515
+ section: z7.string(),
3516
+ title: z7.string(),
3517
+ bytes: z7.number().int().nonnegative()
3518
+ })
3519
+ )
3520
+ });
3521
+ var documentSchema = z7.object({
3522
+ path: z7.string(),
3523
+ section: z7.string(),
3524
+ title: z7.string(),
3525
+ body: z7.string(),
3526
+ bytes: z7.number().int().nonnegative()
3527
+ });
3528
+ function createHeliosDocsToolDefinitions(service) {
3529
+ return [
3530
+ {
3531
+ name: "helios_docs_get_index",
3532
+ title: "Get Helios Docs Index",
3533
+ description: "Lists the package-local Helios MCP and indicator .txt documentation available to the agent.",
3534
+ inputSchema: z7.object({}),
3535
+ async execute() {
3536
+ const result = docsIndexSchema.parse(await service.getIndex());
3537
+ return {
3538
+ content: [
3539
+ {
3540
+ type: "text",
3541
+ text: `Helios docs index: ${result.documents.length} documents across ${new Set(result.documents.map((document) => document.section)).size} sections.`
3542
+ }
3543
+ ],
3544
+ structuredContent: result
3545
+ };
3546
+ }
3547
+ },
3548
+ {
3549
+ name: "helios_docs_get_document",
3550
+ title: "Get Helios Doc",
3551
+ description: "Returns one package-local Helios .txt document by relative docs path.",
3552
+ inputSchema: documentPathInputSchema,
3553
+ async execute(input) {
3554
+ const result = documentSchema.parse(await service.getDocument(input));
3555
+ return {
3556
+ content: [
3557
+ {
3558
+ type: "text",
3559
+ text: result.body
3560
+ }
3561
+ ],
3562
+ structuredContent: result
3563
+ };
3564
+ }
3565
+ }
3566
+ ];
3567
+ }
3568
+ function createHeliosAuthToolDefinitions(service) {
3569
+ return [
3570
+ {
3571
+ name: "helios_auth_start_login",
3572
+ title: "Start Helios Login",
3573
+ description: "Starts the delegated Helios login flow and returns the approval URL for the user.",
3574
+ inputSchema: agentDeviceStartInputSchema,
3575
+ async execute(input) {
3576
+ const result = startLoginOutputSchema.parse(await service.startLogin(input));
3577
+ const manualResult = {
3578
+ ...result,
3579
+ ...withManualLoginFields(result)
3580
+ };
3581
+ return {
3582
+ content: [
3583
+ {
3584
+ type: "text",
3585
+ text: buildManualLoginText(result)
3586
+ }
3587
+ ],
3588
+ structuredContent: manualResult
3589
+ };
3590
+ }
3591
+ },
3592
+ {
3593
+ name: "helios_auth_ensure_login",
3594
+ title: "Ensure Helios Login",
3595
+ description: "Ensures a delegated Helios session exists, or starts a login flow and returns a ready-to-display approval prompt. Call it again after the person approves: it finishes connecting.",
3596
+ inputSchema: ensureLoginInputSchema,
3597
+ async execute(input) {
3598
+ const result = ensureLoginOutputSchema.parse(await service.ensureLogin(input));
3599
+ const structuredContent = result.status === "connected" ? result : {
3600
+ ...result,
3601
+ ...withManualLoginFields(result)
3602
+ };
3603
+ return {
3604
+ content: [
3605
+ {
3606
+ type: "text",
3607
+ text: result.status === "connected" ? `Connected to Helios as user ${result.ownerUserId}.` : result.message
3608
+ }
3609
+ ],
3610
+ structuredContent
3611
+ };
3612
+ }
3613
+ },
3614
+ {
3615
+ name: "helios_auth_poll_login",
3616
+ title: "Poll Helios Login",
3617
+ description: "Checks whether the Helios login in progress was approved and finishes connecting. No code is needed.",
3618
+ inputSchema: pollLoginInputSchema,
3619
+ async execute(input) {
3620
+ const result = pollLoginOutputSchema.parse(await service.pollLogin(input.deviceCode));
3621
+ return {
3622
+ content: [
3623
+ {
3624
+ type: "text",
3625
+ text: result.status === "connected" ? `Connected to Helios as user ${result.ownerUserId}.` : `Helios login is ${result.status}: ${result.message}`
3626
+ }
3627
+ ],
3628
+ structuredContent: result
3629
+ };
3630
+ }
3631
+ },
3632
+ {
3633
+ name: "helios_auth_whoami",
3634
+ title: "Helios Login Status",
3635
+ description: "Returns the delegated Helios session state currently stored by the MCP server.",
3636
+ inputSchema: z7.object({}),
3637
+ async execute() {
3638
+ const result = agentSessionViewSchema.parse(await service.whoami());
3639
+ return {
3640
+ content: [
3641
+ {
3642
+ type: "text",
3643
+ text: result.connected ? `Connected to Helios as user ${result.ownerUserId}.` : "No delegated Helios session is connected."
3644
+ }
3645
+ ],
3646
+ structuredContent: result
3647
+ };
3648
+ }
3649
+ }
3650
+ ];
3651
+ }
3652
+ function createHeliosProviderToolDefinitions(service) {
3653
+ return [
3654
+ {
3655
+ name: "helios_provider_get_active",
3656
+ title: "Get Helios Active Provider",
3657
+ description: "Returns the provider currently pinned for this MCP session, if any.",
3658
+ inputSchema: z7.object({}),
3659
+ async execute() {
3660
+ const result = activeProviderSchema.parse(await service.getActiveProvider());
3661
+ return {
3662
+ content: [
3663
+ {
3664
+ type: "text",
3665
+ text: result.configured ? `Helios active provider is ${result.provider}.` : "No Helios active provider is configured for this session."
3666
+ }
3667
+ ],
3668
+ structuredContent: result
3669
+ };
3670
+ }
3671
+ },
3672
+ {
3673
+ name: "helios_provider_set_active",
3674
+ title: "Set Helios Active Provider",
3675
+ description: "Pins the active provider for this MCP session so portfolio and trading tools can omit provider.",
3676
+ inputSchema: providerSetInputSchema,
3677
+ async execute(input) {
3678
+ const result = activeProviderSchema.parse(await service.setActiveProvider(input.provider));
3679
+ return {
3680
+ content: [
3681
+ {
3682
+ type: "text",
3683
+ text: `Helios active provider set to ${result.provider}.`
3684
+ }
3685
+ ],
3686
+ structuredContent: result
3687
+ };
3688
+ }
3689
+ }
3690
+ ];
3691
+ }
3692
+ function createHeliosAccountToolDefinitions(service) {
3693
+ return [
3694
+ {
3695
+ name: "helios_account_get_wallet_state",
3696
+ title: "Get Helios Wallet State",
3697
+ description: "Returns the current Hyperliquid wallet snapshot for the connected Helios account.",
3698
+ inputSchema: z7.object({}),
3699
+ async execute() {
3700
+ const result = walletStateToolOutputSchema.parse(await service.getWalletState());
3701
+ const structuredContent = result.status === "connected" ? result : {
3702
+ ...result,
3703
+ ...withManualLoginFields(result)
3704
+ };
3705
+ return {
3706
+ content: [
3707
+ {
3708
+ type: "text",
3709
+ text: result.status === "connected" ? `Helios wallet state: available balance ${result.walletState.availableBalance}, account value ${result.walletState.accountValue}.` : result.message
3710
+ }
3711
+ ],
3712
+ structuredContent
3713
+ };
3714
+ }
3715
+ },
3716
+ {
3717
+ name: "helios_portfolio_get",
3718
+ title: "Get Helios Portfolio",
3719
+ description: "Returns balances, open positions and their TP/SL (protectiveOrders, each with the id helios_positions_delete_tp_sl takes). Provider may be omitted if one is already pinned with helios_provider_set_active.",
3720
+ inputSchema: providerSelectionInputSchema,
3721
+ async execute(input) {
3722
+ const result = portfolioToolOutputSchema.parse(await service.getPortfolio(input));
3723
+ const structuredContent = result.status === "connected" ? result : {
3724
+ ...result,
3725
+ ...withManualLoginFields(result)
3726
+ };
3727
+ return {
3728
+ content: [
3729
+ {
3730
+ type: "text",
3731
+ text: result.status === "connected" ? buildPortfolioText(result.portfolio) : result.message
3732
+ }
3733
+ ],
3734
+ structuredContent
3735
+ };
3736
+ }
3737
+ }
3738
+ ];
3739
+ }
3740
+ function createHeliosLedgerToolDefinitions(service) {
3741
+ return [
3742
+ {
3743
+ name: "helios_positions_get_open",
3744
+ title: "Get Helios Open Positions",
3745
+ description: "Returns the current open Hyperliquid positions for the connected Helios account.",
3746
+ inputSchema: z7.object({}),
3747
+ async execute() {
3748
+ const result = openPositionsToolOutputSchema.parse(await service.getOpenPositions());
3749
+ const structuredContent = result.status === "connected" ? result : {
3750
+ ...result,
3751
+ ...withManualLoginFields(result)
3752
+ };
3753
+ return {
3754
+ content: [
3755
+ {
3756
+ type: "text",
3757
+ text: result.status === "connected" ? `Helios open positions: ${result.openPositions.positions.length} positions, total unrealized PnL ${result.openPositions.totalUnrealizedPnl}.` : result.message
3758
+ }
3759
+ ],
3760
+ structuredContent
3761
+ };
3762
+ }
3763
+ },
3764
+ {
3765
+ name: "helios_ledger_get_fills",
3766
+ title: "Get Helios Ledger Fills",
3767
+ description: "Returns recent Hyperliquid fills/trade history for the connected Helios account.",
3768
+ inputSchema: ledgerFillsInputSchema,
3769
+ async execute(input) {
3770
+ const result = ledgerFillsToolOutputSchema.parse(await service.getFills(input));
3771
+ const structuredContent = result.status === "connected" ? result : {
3772
+ ...result,
3773
+ ...withManualLoginFields(result)
3774
+ };
3775
+ return {
3776
+ content: [
3777
+ {
3778
+ type: "text",
3779
+ text: result.status === "connected" ? `Helios ledger fills: ${result.fills.fills.length} fills from ${result.fills.window}${result.fills.hasMore ? ", more available" : ""}.` : result.message
3780
+ }
3781
+ ],
3782
+ structuredContent
3783
+ };
3784
+ }
3785
+ },
3786
+ {
3787
+ name: "helios_ledger_get_summary",
3788
+ title: "Get Helios Ledger Summary",
3789
+ description: "Returns realized PnL, fees, and win/loss metrics over a recent Hyperliquid fill window.",
3790
+ inputSchema: ledgerSummaryInputSchema,
3791
+ async execute(input) {
3792
+ const result = ledgerSummaryToolOutputSchema.parse(await service.getSummary(input));
3793
+ const structuredContent = result.status === "connected" ? result : {
3794
+ ...result,
3795
+ ...withManualLoginFields(result)
3796
+ };
3797
+ return {
3798
+ content: [
3799
+ {
3800
+ type: "text",
3801
+ 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
3802
+ }
3803
+ ],
3804
+ structuredContent
3805
+ };
3806
+ }
3807
+ }
3808
+ ];
3809
+ }
3810
+ function createHeliosMarketToolDefinitions(service) {
3811
+ return [
3812
+ {
3813
+ name: "helios_market_get_snapshot",
3814
+ title: "Get Helios Market Snapshot",
3815
+ description: "Returns a compact market snapshot for the selected, all, or explicitly requested symbols. By default: the top 50 coins by market cap with the main columns; name symbols to get other coins, pass fields for other columns. What each column means: helios_docs_get_document with path ai-agent-indicators/00_columns.txt (read it once).",
3816
+ inputSchema: marketSnapshotInputSchema,
3817
+ async execute(input) {
3818
+ const result = marketSnapshotToolOutputSchema.parse(await service.getSnapshot(input));
3819
+ const structuredContent = result.status === "connected" ? result : {
3820
+ ...result,
3821
+ ...withManualLoginFields(result)
3822
+ };
3823
+ return {
3824
+ content: [
3825
+ {
3826
+ type: "text",
3827
+ 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
3828
+ }
3829
+ ],
3830
+ structuredContent
3831
+ };
3832
+ }
3833
+ },
3834
+ {
3835
+ name: "helios_market_get_snapshot_chunk",
3836
+ title: "Get Helios Market Snapshot Chunk",
3837
+ description: "Returns one chunk of the Helios market snapshot for batch analysis when the full snapshot is too large.",
3838
+ inputSchema: marketSnapshotChunkInputSchema,
3839
+ async execute(input) {
3840
+ const result = marketSnapshotChunkToolOutputSchema.parse(await service.getSnapshotChunk(input));
3841
+ const structuredContent = result.status === "connected" ? result : {
3842
+ ...result,
3843
+ ...withManualLoginFields(result)
3844
+ };
3845
+ return {
3846
+ content: [
3847
+ {
3848
+ type: "text",
3849
+ 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
3850
+ }
3851
+ ],
3852
+ structuredContent
3853
+ };
3854
+ }
3855
+ },
3856
+ {
3857
+ name: "helios_market_get_symbol_detail",
3858
+ title: "Get Helios Symbol Detail",
3859
+ description: "Returns one coin: every snapshot column plus Hyperliquid context (funding, open interest, max leverage, mark price). For several coins, use helios_market_get_snapshot with symbols instead; pass full only when you need raw indicators.",
3860
+ inputSchema: marketSymbolDetailInputSchema,
3861
+ async execute(input) {
3862
+ const result = marketSymbolDetailToolOutputSchema.parse(await service.getSymbolDetail(input));
3863
+ const structuredContent = result.status === "connected" ? result : {
3864
+ ...result,
3865
+ ...withManualLoginFields(result)
3866
+ };
3867
+ return {
3868
+ content: [
3869
+ {
3870
+ type: "text",
3871
+ 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
3872
+ }
3873
+ ],
3874
+ structuredContent
3875
+ };
3876
+ }
3877
+ }
3878
+ ];
3879
+ }
3880
+ function createHeliosTradingToolDefinitions(service) {
3881
+ return [
3882
+ {
3883
+ name: "helios_trading_open",
3884
+ title: "Open Helios Position",
3885
+ description: "Submits a Helios OPEN trading intent. Provider may be omitted if one is already pinned with helios_provider_set_active.",
3886
+ inputSchema: tradeActionInputSchema,
3887
+ async execute(input) {
3888
+ const result = tradeActionToolOutputSchema.parse(await service.open(input));
3889
+ return {
3890
+ content: [
3891
+ {
3892
+ type: "text",
3893
+ 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
3894
+ }
3895
+ ],
3896
+ structuredContent: result.status === "connected" ? result : {
3897
+ ...result,
3898
+ ...withManualLoginFields(result)
3899
+ }
3900
+ };
3901
+ }
3902
+ },
3903
+ {
3904
+ name: "helios_trading_dca",
3905
+ title: "DCA Helios Position",
3906
+ description: "Submits a Helios DCA trading intent. Provider may be omitted if one is already pinned with helios_provider_set_active.",
3907
+ inputSchema: tradeActionInputSchema,
3908
+ async execute(input) {
3909
+ const result = tradeActionToolOutputSchema.parse(await service.dca(input));
3910
+ return {
3911
+ content: [
3912
+ {
3913
+ type: "text",
3914
+ 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
3915
+ }
3916
+ ],
3917
+ structuredContent: result.status === "connected" ? result : {
3918
+ ...result,
3919
+ ...withManualLoginFields(result)
3920
+ }
3921
+ };
3922
+ }
3923
+ },
3924
+ {
3925
+ name: "helios_trading_reverse",
3926
+ title: "Reverse Helios Position",
3927
+ description: "Submits a Helios REVERSE trading intent. Provider may be omitted if one is already pinned with helios_provider_set_active.",
3928
+ inputSchema: tradeActionInputSchema,
3929
+ async execute(input) {
3930
+ const result = tradeActionToolOutputSchema.parse(await service.reverse(input));
3931
+ return {
3932
+ content: [
3933
+ {
3934
+ type: "text",
3935
+ 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
3936
+ }
3937
+ ],
3938
+ structuredContent: result.status === "connected" ? result : {
3939
+ ...result,
3940
+ ...withManualLoginFields(result)
3941
+ }
3942
+ };
3943
+ }
3944
+ },
3945
+ {
3946
+ name: "helios_trading_close",
3947
+ title: "Close Helios Position",
3948
+ 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.",
3949
+ inputSchema: closePositionInputSchema,
3950
+ async execute(input) {
3951
+ const result = closeTradeToolOutputSchema.parse(await service.close(input));
3952
+ return {
3953
+ content: [
3954
+ {
3955
+ type: "text",
3956
+ text: result.status !== "connected" ? result.message : result.closure.outcome === "no_position" ? `No open ${result.closure.symbol} position: nothing was closed and no order was sent.` : `Sent a market order closing the full ${result.closure.provider ?? input.provider ?? "hyperliquid"} ${result.closure.symbol} position. Check helios_positions_get_open to confirm.`
3957
+ }
3958
+ ],
3959
+ structuredContent: result.status === "connected" ? result : {
3960
+ ...result,
3961
+ ...withManualLoginFields(result)
3962
+ }
3963
+ };
3964
+ }
3965
+ },
3966
+ {
3967
+ name: "helios_positions_set_tp_sl",
3968
+ title: "Set Helios TP/SL",
3969
+ 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.",
3970
+ inputSchema: setTpSlInputSchema,
3971
+ async execute(input) {
3972
+ const result = setTpSlToolOutputSchema.parse(await service.setTpSl(input));
3973
+ return {
3974
+ content: [
3975
+ {
3976
+ type: "text",
3977
+ text: result.status === "connected" ? `Submitted ${result.protection.provider ?? input.provider} TP/SL protection update for ${result.protection.symbol}.` : result.message
3978
+ }
3979
+ ],
3980
+ structuredContent: result.status === "connected" ? result : {
3981
+ ...result,
3982
+ ...withManualLoginFields(result)
3983
+ }
3984
+ };
3985
+ }
3986
+ },
3987
+ {
3988
+ name: "helios_positions_delete_tp_sl",
3989
+ title: "Delete Helios TP/SL",
3990
+ description: "Deletes a take-profit or stop-loss. orderId is the id from helios_portfolio_get protectiveOrders, or the Hyperliquid order id of the TP/SL. Provider may be omitted if one is already pinned with helios_provider_set_active.",
3991
+ inputSchema: deleteTpSlInputSchema,
3992
+ async execute(input) {
3993
+ const result = deleteTpSlToolOutputSchema.parse(await service.deleteTpSl(input));
3994
+ return {
3995
+ content: [
3996
+ {
3997
+ type: "text",
3998
+ text: result.status === "connected" ? `Deleted ${result.deletion.provider ?? input.provider} TP/SL order ${result.deletion.orderId} for ${result.deletion.symbol}.` : result.message
3999
+ }
4000
+ ],
4001
+ structuredContent: result.status === "connected" ? result : {
4002
+ ...result,
4003
+ ...withManualLoginFields(result)
4004
+ }
4005
+ };
4006
+ }
4007
+ },
4008
+ {
4009
+ name: "helios_orders_get_open",
4010
+ title: "Get Helios Open Orders",
4011
+ description: "Lists the currently open Hyperliquid orders for the connected account.",
4012
+ inputSchema: z7.object({}),
4013
+ async execute() {
4014
+ const result = openOrdersToolOutputSchema.parse(await service.getOpenOrders());
4015
+ return {
4016
+ content: [
4017
+ {
4018
+ type: "text",
4019
+ text: result.status === "connected" ? `Retrieved ${result.orders.length} open Helios orders.` : result.message
4020
+ }
4021
+ ],
4022
+ structuredContent: result.status === "connected" ? result : {
4023
+ ...result,
4024
+ ...withManualLoginFields(result)
4025
+ }
4026
+ };
4027
+ }
4028
+ },
4029
+ {
4030
+ name: "helios_orders_cancel",
4031
+ title: "Cancel Helios Order",
4032
+ description: "Cancels an open Hyperliquid order by symbol and order id.",
4033
+ inputSchema: cancelOrderInputSchema,
4034
+ async execute(input) {
4035
+ const result = cancelOrderToolOutputSchema.parse(await service.cancelOrder(input));
4036
+ return {
4037
+ content: [
4038
+ {
4039
+ type: "text",
4040
+ text: result.status === "connected" ? `Cancelled Helios order ${result.cancellation.oid} for ${result.cancellation.symbol}.` : result.message
4041
+ }
4042
+ ],
4043
+ structuredContent: result.status === "connected" ? result : {
4044
+ ...result,
4045
+ ...withManualLoginFields(result)
4046
+ }
4047
+ };
4048
+ }
4049
+ }
4050
+ ];
4051
+ }
4052
+ function withManualLoginFields(result) {
4053
+ return {
4054
+ actionRequired: true,
4055
+ loginUrl: result.verificationUriComplete,
4056
+ nextStep: "Open the login URL in your browser, approve Helios access, then call helios_auth_ensure_login again to finish connecting."
4057
+ };
4058
+ }
4059
+ function buildPortfolioText(result) {
4060
+ return `Helios Hyperliquid portfolio: ${result.positions.length} positions, available balance ${result.availableBalance}, total balance ${result.totalBalance}.`;
4061
+ }
4062
+ function buildManualLoginText(result) {
4063
+ return `Log in to Helios by opening this URL in your browser:
4064
+
4065
+ ${result.verificationUriComplete}
4066
+
4067
+ User code: ${result.userCode}
4068
+
4069
+ After approving access, call helios_auth_ensure_login (or helios_auth_poll_login) again to finish connecting.`;
4070
+ }
4071
+
4072
+ // src/server.ts
4073
+ var MCP_PROFILES = ["lean", "signals", "full"];
4074
+ var MAX_TOOL_INPUT_ELEMENTS = 1e4;
4075
+ async function createHeliosAgentMcpServer(options) {
4076
+ if (!options.identityServiceUrl) {
4077
+ throw new Error("identityServiceUrl is required");
4078
+ }
4079
+ if (!options.dataBridgeUrl) {
4080
+ throw new Error("dataBridgeUrl is required");
4081
+ }
4082
+ const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js");
4083
+ const server = new McpServer(
4084
+ {
4085
+ name: "helios",
4086
+ version: "0.4.0"
4087
+ },
4088
+ // Caps the array elements plus object keys one tools/call may carry (GHSA-46jr-4wx8-v453: without it, one request
4089
+ // under the body size limit could run the hosted server out of memory while validating). The largest real input is
4090
+ // a snapshot over every symbol and field, a few hundred entries, so this is far above normal use.
4091
+ { maxToolInputElements: MAX_TOOL_INPUT_ELEMENTS }
4092
+ );
4093
+ if (!options.hosted) {
4094
+ assertHeliosBaseUrl("identityServiceUrl (HELIOS_IDENTITY_SERVICE_URL)", options.identityServiceUrl);
4095
+ assertHeliosBaseUrl("dataBridgeUrl (HELIOS_DATA_BRIDGE_URL)", options.dataBridgeUrl);
4096
+ }
4097
+ const sessionStore = options.sessionStore ?? new FileAgentSessionStore(options.sessionPath ?? getDefaultHeliosAgentMcpSessionPath(), {
4098
+ issuer: { identityServiceUrl: options.identityServiceUrl, dataBridgeUrl: options.dataBridgeUrl }
4099
+ });
4100
+ const client = new HeliosAgentAuthClient({
4101
+ baseUrl: options.identityServiceUrl,
4102
+ fetch: options.fetch
4103
+ });
4104
+ const dataBridgeClient = new HeliosAgentDataBridgeClient({
4105
+ baseUrl: options.dataBridgeUrl,
4106
+ fetch: options.fetch
4107
+ });
4108
+ const authService = new HeliosAgentMcpAuthService({
4109
+ client,
4110
+ sessionStore,
4111
+ refreshWindowMs: options.refreshWindowMs,
4112
+ deviceLogin: !options.hosted
4113
+ });
4114
+ const providerService = new HeliosAgentMcpProviderService({
4115
+ sessionStore
4116
+ });
4117
+ const accountService = new HeliosAgentMcpAccountService({
4118
+ authService,
4119
+ dataBridgeClient,
4120
+ providerService
4121
+ });
4122
+ const marketService = new HeliosAgentMcpMarketService({
4123
+ authService,
4124
+ dataBridgeClient
4125
+ });
4126
+ const ledgerService = new HeliosAgentMcpLedgerService({
4127
+ authService,
4128
+ dataBridgeClient
4129
+ });
4130
+ const tradingService = new HeliosAgentMcpTradingService({
4131
+ authService,
4132
+ dataBridgeClient,
4133
+ providerService
4134
+ });
4135
+ const docsService = new HeliosAgentMcpDocsService({
4136
+ docsRoot: options.docsRoot ?? getDefaultHeliosAgentMcpDocsRoot()
4137
+ });
4138
+ const profile = options.profile ?? "full";
4139
+ const lean = createLeanToolDefinitions({ auth: authService, dataBridge: dataBridgeClient });
4140
+ for (const tool of leanToolsFor(lean, profile)) {
4141
+ registerTool(server, { ...tool, textIsResult: true });
4142
+ }
4143
+ if (profile === "full") {
4144
+ const docsTools = createHeliosDocsToolDefinitions(docsService);
4145
+ const authTools = createHeliosAuthToolDefinitions(authService);
4146
+ const ledgerTools = createHeliosLedgerToolDefinitions(ledgerService);
4147
+ const marketTools = createHeliosMarketToolDefinitions(marketService);
4148
+ if (!options.hosted) {
4149
+ registerTool(server, authTools[0]);
4150
+ registerTool(server, authTools[1]);
4151
+ registerTool(server, authTools[2]);
4152
+ }
4153
+ registerTool(server, authTools[3]);
4154
+ registerTool(server, marketTools[0]);
4155
+ registerTool(server, marketTools[1]);
4156
+ registerTool(server, marketTools[2]);
4157
+ registerTool(server, ledgerTools[1]);
4158
+ registerTool(server, docsTools[0]);
4159
+ registerTool(server, docsTools[1]);
4160
+ }
4161
+ return {
4162
+ server,
4163
+ authService,
4164
+ providerService,
4165
+ accountService,
4166
+ docsService,
4167
+ ledgerService,
4168
+ marketService,
4169
+ tradingService,
4170
+ sessionStore
4171
+ };
4172
+ }
4173
+ var ACTS_ON_ACCOUNT = /^helios_(trading_|positions_set_tp_sl|positions_delete_tp_sl|orders_cancel)/;
4174
+ var LEAN_ACTS = /* @__PURE__ */ new Set(["trade", "protect"]);
4175
+ var CHANGES_SESSION = /^helios_(auth_start_login|auth_ensure_login|auth_poll_login|provider_set_active)$/;
4176
+ function toolAnnotations(name) {
4177
+ if (ACTS_ON_ACCOUNT.test(name) || LEAN_ACTS.has(name)) return { readOnlyHint: false, destructiveHint: true, openWorldHint: true };
4178
+ if (CHANGES_SESSION.test(name)) return { readOnlyHint: false, destructiveHint: false, openWorldHint: false };
4179
+ return { readOnlyHint: true, destructiveHint: false, openWorldHint: true };
4180
+ }
4181
+ var TEXT_IS_RESULT = /* @__PURE__ */ new Set(["helios_docs_get_document"]);
4182
+ function withDataText(name, content, structuredContent) {
4183
+ if (TEXT_IS_RESULT.has(name)) return content;
4184
+ const summary = content.map((block) => block.text).join("\n\n");
4185
+ return [{ type: "text", text: `${summary}
4186
+
4187
+ ${JSON.stringify(structuredContent)}` }];
4188
+ }
4189
+ function registerTool(server, tool) {
4190
+ server.registerTool(
4191
+ tool.name,
4192
+ {
4193
+ title: tool.title,
4194
+ description: tool.description,
4195
+ inputSchema: tool.inputSchema,
4196
+ annotations: { title: tool.title, ...toolAnnotations(tool.name) }
4197
+ },
4198
+ async (input) => {
4199
+ const result = await tool.execute(tool.inputSchema.parse(input));
4200
+ return {
4201
+ content: tool.textIsResult ? result.content : withDataText(tool.name, result.content, result.structuredContent),
4202
+ structuredContent: result.structuredContent
4203
+ };
4204
+ }
4205
+ );
4206
+ }
4207
+
4208
+ export {
4209
+ DEFAULT_HELIOS_BASE_URL,
4210
+ DEFAULT_HELIOS_SESSION_ISSUER,
4211
+ assertHeliosBaseUrl,
4212
+ isSameHeliosIssuer,
4213
+ retryAfterUnauthorizedRefresh,
4214
+ HOSTED_RECONNECT_MESSAGE,
4215
+ HeliosAgentMcpAuthService,
4216
+ formatHeliosLoginPrompt,
4217
+ HeliosAgentMcpAccountService,
4218
+ HeliosAgentMcpDocsService,
4219
+ getDefaultHeliosAgentMcpDocsRoot,
4220
+ HeliosAgentMcpLedgerService,
4221
+ DEFAULT_SNAPSHOT_TOP,
4222
+ DEFAULT_SNAPSHOT_FIELDS,
4223
+ withSnapshotDefaults,
4224
+ topOfSnapshot,
4225
+ compactSymbolDetail,
4226
+ HeliosAgentMcpMarketService,
4227
+ HeliosAgentMcpProviderService,
4228
+ getDefaultHeliosAgentMcpSessionPath,
4229
+ pendingAgentLoginSchema2 as pendingAgentLoginSchema,
4230
+ FileAgentSessionStore,
4231
+ MemoryAgentMcpSessionStore,
4232
+ HeliosAgentMcpTradingService,
4233
+ usd,
4234
+ pct,
4235
+ price,
4236
+ ago,
4237
+ trader,
4238
+ moveText,
4239
+ entryText,
4240
+ renderMarket,
4241
+ renderBoard,
4242
+ renderMoves,
4243
+ renderCoin,
4244
+ renderAccount,
4245
+ positionLine,
4246
+ renderTrades,
4247
+ renderCandles,
4248
+ smBoardInputSchema,
4249
+ smMovesInputSchema,
4250
+ smCoinInputSchema,
4251
+ marketInputSchema,
4252
+ accountInputSchema,
4253
+ tradeInputSchema,
4254
+ TRADE_KEY_WINDOW_SECONDS,
4255
+ tradeKeyFor,
4256
+ protectInputSchema,
4257
+ tradesInputSchema,
4258
+ candlesInputSchema,
4259
+ wrongSideTpSl,
4260
+ createLeanToolDefinitions,
4261
+ leanToolsFor,
4262
+ createHeliosDocsToolDefinitions,
4263
+ createHeliosAuthToolDefinitions,
4264
+ createHeliosProviderToolDefinitions,
4265
+ createHeliosAccountToolDefinitions,
4266
+ createHeliosLedgerToolDefinitions,
4267
+ createHeliosMarketToolDefinitions,
4268
+ createHeliosTradingToolDefinitions,
4269
+ MCP_PROFILES,
4270
+ MAX_TOOL_INPUT_ELEMENTS,
4271
+ createHeliosAgentMcpServer,
4272
+ toolAnnotations,
4273
+ withDataText
4274
+ };