@pgnio/helios-mcp 0.2.9 → 0.5.0

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