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