@liquiditytech/rapidx-cli 1.0.28 → 1.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/account.js +2 -2
- package/dist/cli/commands/algo.js +2 -2
- package/dist/cli/commands/market.js +2 -2
- package/dist/cli/commands/order.js +6 -4
- package/dist/cli/commands/position.js +2 -2
- package/dist/cli/commands/trade.js +3 -2
- package/dist/cli/envelope.js +2 -1
- package/dist/core/client/capability-executor.js +89 -14
- package/dist/core/client/order-id.js +24 -0
- package/dist/core/client/rapid-x-client.js +45 -9
- package/dist/core/client/symbol.js +6 -2
- package/dist/core/contracts/capabilities.js +4 -1
- package/dist/core/contracts/compatibility.js +1 -1
- package/dist/core/contracts/input-schema.js +53 -13
- package/dist/core/errors/product-error.js +17 -0
- package/dist/core/index.js +2 -0
- package/dist/core/safety/policy.js +6 -3
- package/dist/core/self-check/live-trading-verification-probes.js +8 -1
- package/dist/core/trading/preview-preflight.js +333 -0
- package/dist/core/trading/preview.js +88 -14
- package/dist/core/trading/trading-verification.js +56 -11
- package/dist/core/version.js +1 -1
- package/dist/mcp/tool-runner.js +27 -2
- package/package.json +1 -1
- package/packages/distribution/docs/cli.md +11 -1
- package/packages/distribution/docs/mcp.md +7 -2
- package/packages/distribution/docs/quickstart.md +9 -1
- package/packages/distribution/docs/tools.md +38 -2
- package/packages/distribution/manifests/offline-manifest.json +4 -4
- package/packages/distribution/registry/rapidx.mcp.json +1 -1
|
@@ -4,6 +4,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
4
4
|
import { assertInputMatchesSchema } from "../contracts/input-schema.js";
|
|
5
5
|
import { evaluateSafety, stableParamsHash } from "../safety/policy.js";
|
|
6
6
|
import { ProductError } from "../errors/product-error.js";
|
|
7
|
+
import { parseRapidXSymbol } from "../client/symbol.js";
|
|
7
8
|
export function makePreviewStore() {
|
|
8
9
|
return { records: new Map() };
|
|
9
10
|
}
|
|
@@ -38,10 +39,11 @@ export function savePreviewStoreToFile(filePath, store) {
|
|
|
38
39
|
}
|
|
39
40
|
export function createTradePreview(capability, input, policy, state, store, ttlSeconds = 300) {
|
|
40
41
|
assertInputMatchesSchema(capability.inputSchema, input, {
|
|
41
|
-
allowedExtraFields: ["explicitUserConsent", "acceptedRiskText"]
|
|
42
|
+
allowedExtraFields: ["explicitUserConsent", "acceptedRiskText", "automationMode", "automationConsentText", "automationScope"]
|
|
42
43
|
});
|
|
43
44
|
const tradeParams = normalizeTradeParams(input);
|
|
44
|
-
const
|
|
45
|
+
const automation = makeAutomationDetails(input, tradeParams);
|
|
46
|
+
const previewDetails = makePreviewDetails(capability, tradeParams, automation);
|
|
45
47
|
const decision = evaluateSafety(capability, input, policy, state);
|
|
46
48
|
if (!decision.allowed) {
|
|
47
49
|
throw new ProductError({
|
|
@@ -59,7 +61,8 @@ export function createTradePreview(capability, input, policy, state, store, ttlS
|
|
|
59
61
|
expiresAt: new Date(Date.now() + ttlSeconds * 1000).toISOString(),
|
|
60
62
|
submitted: false,
|
|
61
63
|
status: "PASS",
|
|
62
|
-
confirmation: makeSubmitConfirmation(previewId)
|
|
64
|
+
confirmation: makeSubmitConfirmation(previewId, automation),
|
|
65
|
+
...(automation ? { automation } : {})
|
|
63
66
|
};
|
|
64
67
|
store.records.set(previewId, record);
|
|
65
68
|
return record;
|
|
@@ -73,7 +76,10 @@ export function createTargetedTradePreview(targetCapability, input, policy, stat
|
|
|
73
76
|
"acceptedRiskText",
|
|
74
77
|
"mcpSchemaVersion",
|
|
75
78
|
"skillsVersion",
|
|
76
|
-
"skillsSchemaVersion"
|
|
79
|
+
"skillsSchemaVersion",
|
|
80
|
+
"automationMode",
|
|
81
|
+
"automationConsentText",
|
|
82
|
+
"automationScope"
|
|
77
83
|
],
|
|
78
84
|
ignoredRequiredFields: ["previewId", "continueConsentId"]
|
|
79
85
|
});
|
|
@@ -92,7 +98,8 @@ export function createTargetedTradePreview(targetCapability, input, policy, stat
|
|
|
92
98
|
});
|
|
93
99
|
}
|
|
94
100
|
const tradeParams = normalizeTradeParams(input);
|
|
95
|
-
const
|
|
101
|
+
const automation = makeAutomationDetails(input, tradeParams);
|
|
102
|
+
const previewDetails = makePreviewDetails(targetCapability, tradeParams, automation);
|
|
96
103
|
const decision = evaluateSafety(targetCapability, input, policy, state);
|
|
97
104
|
if (!decision.allowed) {
|
|
98
105
|
throw new ProductError({
|
|
@@ -110,16 +117,19 @@ export function createTargetedTradePreview(targetCapability, input, policy, stat
|
|
|
110
117
|
expiresAt: new Date(Date.now() + ttlSeconds * 1000).toISOString(),
|
|
111
118
|
submitted: false,
|
|
112
119
|
status: "PASS",
|
|
113
|
-
confirmation: makeSubmitConfirmation(previewId)
|
|
120
|
+
confirmation: makeSubmitConfirmation(previewId, automation),
|
|
121
|
+
...(automation ? { automation } : {})
|
|
114
122
|
};
|
|
115
123
|
store.records.set(previewId, record);
|
|
116
124
|
return record;
|
|
117
125
|
}
|
|
118
|
-
function makeSubmitConfirmation(previewId) {
|
|
126
|
+
function makeSubmitConfirmation(previewId, automation) {
|
|
119
127
|
return {
|
|
120
128
|
submitToken: `confirm_${previewId}`,
|
|
121
129
|
requiredFields: ["previewId", "continueConsentId"],
|
|
122
|
-
instruction:
|
|
130
|
+
instruction: automation
|
|
131
|
+
? "Automation mode is enabled for this preview. The agent may pass submitToken as continueConsentId without asking for another per-order chat confirmation, within the authorized scope."
|
|
132
|
+
: "Pass submitToken as continueConsentId only after the user has confirmed this exact preview."
|
|
123
133
|
};
|
|
124
134
|
}
|
|
125
135
|
export function verifyPreview(store, previewId, capability, input, now = new Date()) {
|
|
@@ -135,7 +145,7 @@ export function verifyPreview(store, previewId, capability, input, now = new Dat
|
|
|
135
145
|
throw new ProductError({
|
|
136
146
|
code: "RCORE20002",
|
|
137
147
|
status: "BLOCKED",
|
|
138
|
-
message: "previewId was not found."
|
|
148
|
+
message: "previewId was not found or has expired. Re-run preview and submit within 5 minutes."
|
|
139
149
|
});
|
|
140
150
|
}
|
|
141
151
|
if (new Date(record.expiresAt).getTime() < now.getTime()) {
|
|
@@ -172,16 +182,24 @@ function normalizeTradeParams(input) {
|
|
|
172
182
|
const normalized = {};
|
|
173
183
|
for (const [key, value] of Object.entries(input)) {
|
|
174
184
|
if (!NON_TRADE_HASH_FIELDS.has(key) && value !== undefined) {
|
|
175
|
-
normalized[key] = value;
|
|
185
|
+
normalized[key] = key === "symbol" && typeof value === "string" ? canonicalSymbolOrOriginal(value) : value;
|
|
176
186
|
}
|
|
177
187
|
}
|
|
178
188
|
return normalized;
|
|
179
189
|
}
|
|
180
|
-
function
|
|
190
|
+
function canonicalSymbolOrOriginal(value) {
|
|
191
|
+
try {
|
|
192
|
+
return parseRapidXSymbol(value).canonicalSymbol;
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function makePreviewDetails(capability, tradeParams, automation) {
|
|
181
199
|
return {
|
|
182
200
|
businessParams: { ...tradeParams },
|
|
183
201
|
requestSummary: makeRequestSummary(capability.capabilityId, tradeParams),
|
|
184
|
-
riskNotes: makeRiskNotes(capability.capabilityId, tradeParams)
|
|
202
|
+
riskNotes: makeRiskNotes(capability.capabilityId, tradeParams, automation)
|
|
185
203
|
};
|
|
186
204
|
}
|
|
187
205
|
function makeRequestSummary(capabilityId, params) {
|
|
@@ -190,6 +208,7 @@ function makeRequestSummary(capabilityId, params) {
|
|
|
190
208
|
action: "place_order",
|
|
191
209
|
symbol: params.symbol,
|
|
192
210
|
side: params.side,
|
|
211
|
+
positionSide: params.positionSide,
|
|
193
212
|
orderType: params.orderType,
|
|
194
213
|
price: params.price,
|
|
195
214
|
quantity: params.quantity,
|
|
@@ -240,11 +259,18 @@ function makeRequestSummary(capabilityId, params) {
|
|
|
240
259
|
}
|
|
241
260
|
return compactRecord({ action: capabilityId, ...params });
|
|
242
261
|
}
|
|
243
|
-
function makeRiskNotes(capabilityId, params) {
|
|
262
|
+
function makeRiskNotes(capabilityId, params, automation) {
|
|
244
263
|
const notes = [];
|
|
264
|
+
if (automation) {
|
|
265
|
+
notes.push("Automation mode allows the agent to submit this preview with submitToken without asking for another per-order chat confirmation.");
|
|
266
|
+
}
|
|
245
267
|
if (params.maxNotional !== undefined) {
|
|
246
268
|
notes.push("maxNotional is a safety upper bound, not the target order size.");
|
|
247
269
|
}
|
|
270
|
+
if (String(params.orderType ?? "").toUpperCase() === "MARKET") {
|
|
271
|
+
notes.push("MARKET orders may execute immediately.");
|
|
272
|
+
notes.push("MARKET order fill price is not guaranteed and may include slippage.");
|
|
273
|
+
}
|
|
248
274
|
if (capabilityId === "position.close") {
|
|
249
275
|
notes.push("Do not pass side or quantity for position.close; RapidX determines BUY or SELL from the current position and closes the target symbol/positionSide.");
|
|
250
276
|
notes.push("position.close uses the RapidX close-position API and may execute as a market close with slippage.");
|
|
@@ -252,6 +278,51 @@ function makeRiskNotes(capabilityId, params) {
|
|
|
252
278
|
}
|
|
253
279
|
return notes;
|
|
254
280
|
}
|
|
281
|
+
function makeAutomationDetails(input, tradeParams) {
|
|
282
|
+
if (input.automationMode !== true) {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
const acceptedRiskText = typeof input.automationConsentText === "string" ? input.automationConsentText.trim() : "";
|
|
286
|
+
const missing = automationConsentMissingFields(acceptedRiskText, tradeParams, input);
|
|
287
|
+
if (missing.length > 0) {
|
|
288
|
+
throw new ProductError({
|
|
289
|
+
code: "RCORE20003",
|
|
290
|
+
status: "BLOCKED",
|
|
291
|
+
message: `automationConsentText must include ${missing.join(", ")} for this exact automation scope.`
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
enabled: true,
|
|
296
|
+
confirmationMode: "automation-preview",
|
|
297
|
+
scope: typeof input.automationScope === "string" && input.automationScope.trim().length > 0 ? input.automationScope.trim() : "single-preview",
|
|
298
|
+
...(tradeParams.maxNotional !== undefined ? { maxNotional: String(tradeParams.maxNotional) } : {}),
|
|
299
|
+
acceptedRiskText
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function automationConsentMissingFields(text, tradeParams, input) {
|
|
303
|
+
const missing = [];
|
|
304
|
+
const upperText = text.toUpperCase();
|
|
305
|
+
const lowerText = text.toLowerCase();
|
|
306
|
+
if (text.length < 20) {
|
|
307
|
+
missing.push("automationConsentText");
|
|
308
|
+
}
|
|
309
|
+
const symbol = typeof tradeParams.symbol === "string" ? tradeParams.symbol : undefined;
|
|
310
|
+
if (symbol && !upperText.includes(symbol.toUpperCase())) {
|
|
311
|
+
missing.push("symbol");
|
|
312
|
+
}
|
|
313
|
+
const maxNotional = tradeParams.maxNotional;
|
|
314
|
+
if (maxNotional !== undefined && !text.includes(String(maxNotional))) {
|
|
315
|
+
missing.push("maxNotional");
|
|
316
|
+
}
|
|
317
|
+
if (!lowerText.includes("automation") && !lowerText.includes("automated") && !text.includes("自动")) {
|
|
318
|
+
missing.push("automation intent");
|
|
319
|
+
}
|
|
320
|
+
const scope = typeof input.automationScope === "string" ? input.automationScope.trim() : "";
|
|
321
|
+
if (scope && scope !== "single-preview" && !lowerText.includes(scope.toLowerCase())) {
|
|
322
|
+
missing.push("automationScope");
|
|
323
|
+
}
|
|
324
|
+
return missing;
|
|
325
|
+
}
|
|
255
326
|
function estimateNotional(params) {
|
|
256
327
|
if (params.amount !== undefined && params.amount !== null && params.amount !== "") {
|
|
257
328
|
return String(params.amount);
|
|
@@ -314,7 +385,10 @@ const NON_TRADE_HASH_FIELDS = new Set([
|
|
|
314
385
|
"acceptedRiskText",
|
|
315
386
|
"mcpSchemaVersion",
|
|
316
387
|
"skillsVersion",
|
|
317
|
-
"skillsSchemaVersion"
|
|
388
|
+
"skillsSchemaVersion",
|
|
389
|
+
"automationMode",
|
|
390
|
+
"automationConsentText",
|
|
391
|
+
"automationScope"
|
|
318
392
|
]);
|
|
319
393
|
function isTradePreviewRecord(value) {
|
|
320
394
|
if (!value || typeof value !== "object") {
|
|
@@ -13,7 +13,7 @@ export async function runTradingVerification(rawInput, probes = {}) {
|
|
|
13
13
|
}
|
|
14
14
|
catch (error) {
|
|
15
15
|
if (error instanceof ProductError) {
|
|
16
|
-
return { submittedRealOrder: false, status:
|
|
16
|
+
return { submittedRealOrder: false, status: error.status, cleanupStatus: "NOT_VERIFIED", steps, errorCode: error.code, errorMessage: error.message, errorStage: "input-validation" };
|
|
17
17
|
}
|
|
18
18
|
throw error;
|
|
19
19
|
}
|
|
@@ -39,7 +39,7 @@ export async function runTradingVerification(rawInput, probes = {}) {
|
|
|
39
39
|
errorCode: "RCORE20001",
|
|
40
40
|
errorMessage: consentProblem,
|
|
41
41
|
errorStage: "user-consent",
|
|
42
|
-
recommendedAction: "Ask the human user to confirm the exact symbol, side, maxNotional, real-order risk, and cancel cleanup behavior before running verify-live."
|
|
42
|
+
recommendedAction: "Ask the human user to confirm the exact symbol, side, positionSide when provided, maxNotional, real-order risk, and cancel cleanup behavior before running verify-live."
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
if (!probes.marketRules) {
|
|
@@ -48,8 +48,18 @@ export async function runTradingVerification(rawInput, probes = {}) {
|
|
|
48
48
|
}
|
|
49
49
|
const marketRules = await probes.marketRules(input);
|
|
50
50
|
if (Number(marketRules.minNotional) > Number(input.maxNotional)) {
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const evidence = `minNotional=${marketRules.minNotional};maxNotional=${input.maxNotional}`;
|
|
52
|
+
steps.push({ name: "market", status: "BLOCKED", toolOrCommandEvidence: evidence });
|
|
53
|
+
return {
|
|
54
|
+
submittedRealOrder: false,
|
|
55
|
+
status: "BLOCKED",
|
|
56
|
+
cleanupStatus: "NOT_VERIFIED",
|
|
57
|
+
steps,
|
|
58
|
+
errorCode: "RCORE24001",
|
|
59
|
+
errorMessage: `Verification order blocked because ${evidence}.`,
|
|
60
|
+
errorStage: "market",
|
|
61
|
+
recommendedAction: `Increase maxNotional to at least ${marketRules.minNotional}, or choose a symbol whose minNotional is within the authorized maxNotional.`
|
|
62
|
+
};
|
|
53
63
|
}
|
|
54
64
|
steps.push({ name: "market", status: "PASS", toolOrCommandEvidence: `minNotional=${marketRules.minNotional};tickSize=${marketRules.tickSize}` });
|
|
55
65
|
const capability = findCapabilityById("order.preview");
|
|
@@ -73,7 +83,7 @@ export async function runTradingVerification(rawInput, probes = {}) {
|
|
|
73
83
|
return { submittedRealOrder: false, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, previewId: preview.previewId, errorCode: "RCORE23001", errorStage: "place" };
|
|
74
84
|
}
|
|
75
85
|
const placed = await probes.placeOrder({ ...input, previewId: preview.previewId, orderType: "LIMIT", postOnly: true, price, quantity });
|
|
76
|
-
steps.push({ name: "place", status: "PASS", toolOrCommandEvidence: placed.requestId ?? placed.orderId });
|
|
86
|
+
steps.push({ name: "place", status: "PASS", toolOrCommandEvidence: [placed.requestId ?? placed.orderId, input.positionSide ? `positionSide=${input.positionSide}` : undefined].filter(Boolean).join(";") });
|
|
77
87
|
if (!probes.queryOrder) {
|
|
78
88
|
steps.push({ name: "query", status: "NOT_VERIFIED", toolOrCommandEvidence: "queryOrder probe missing" });
|
|
79
89
|
return { submittedRealOrder: true, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, previewId: preview.previewId, errorCode: "RCORE23001", errorStage: "query" };
|
|
@@ -93,7 +103,11 @@ export async function runTradingVerification(rawInput, probes = {}) {
|
|
|
93
103
|
}
|
|
94
104
|
}
|
|
95
105
|
else {
|
|
96
|
-
steps.push({
|
|
106
|
+
steps.push({
|
|
107
|
+
name: "amend",
|
|
108
|
+
status: "EXPECTED_ERROR",
|
|
109
|
+
toolOrCommandEvidence: "optional verify-live amend check skipped; order.amend remains available outside verify-live"
|
|
110
|
+
});
|
|
97
111
|
}
|
|
98
112
|
if (!probes.cancelOrder) {
|
|
99
113
|
steps.push({ name: "cancel", status: "FAIL", toolOrCommandEvidence: "cancelOrder probe missing" });
|
|
@@ -110,12 +124,12 @@ export async function runTradingVerification(rawInput, probes = {}) {
|
|
|
110
124
|
steps.push({ name: "cleanup-check", status: "NOT_VERIFIED", toolOrCommandEvidence: "cleanupCheck probe missing" });
|
|
111
125
|
return { submittedRealOrder: true, status: "NOT_VERIFIED", cleanupStatus: "NOT_VERIFIED", steps, previewId: preview.previewId, errorCode: "RCORE24002", errorStage: "cleanup-check", recommendedAction: "Query order/list and position/list before retrying verify-live." };
|
|
112
126
|
}
|
|
113
|
-
const
|
|
114
|
-
if (
|
|
115
|
-
steps.push({ name: "cleanup-check", status: "FAIL", toolOrCommandEvidence:
|
|
116
|
-
return cleanupFailureReport(steps, preview.previewId, "cleanup-check", currentOrder.status, `cleanup still shows
|
|
127
|
+
const cleanupConfirmation = await confirmCleanup(currentOrder, probes);
|
|
128
|
+
if (!cleanupConfirmation.confirmed) {
|
|
129
|
+
steps.push({ name: "cleanup-check", status: "FAIL", toolOrCommandEvidence: cleanupConfirmation.evidence });
|
|
130
|
+
return cleanupFailureReport(steps, preview.previewId, "cleanup-check", currentOrder.status, `cleanup still shows ${cleanupEvidence(cleanupConfirmation.cleanup)}`);
|
|
117
131
|
}
|
|
118
|
-
steps.push({ name: "cleanup-check", status: "PASS", toolOrCommandEvidence:
|
|
132
|
+
steps.push({ name: "cleanup-check", status: "PASS", toolOrCommandEvidence: cleanupConfirmation.evidence });
|
|
119
133
|
return {
|
|
120
134
|
submittedRealOrder: true,
|
|
121
135
|
status: "PASS",
|
|
@@ -136,6 +150,9 @@ function normalizeTradingVerificationInput(input) {
|
|
|
136
150
|
if (typeof input.acceptedRiskText === "string") {
|
|
137
151
|
normalized.acceptedRiskText = input.acceptedRiskText;
|
|
138
152
|
}
|
|
153
|
+
if (input.positionSide === "LONG" || input.positionSide === "SHORT") {
|
|
154
|
+
normalized.positionSide = input.positionSide;
|
|
155
|
+
}
|
|
139
156
|
return normalized;
|
|
140
157
|
}
|
|
141
158
|
function validateParameterBoundConsent(input) {
|
|
@@ -155,6 +172,9 @@ function validateParameterBoundConsent(input) {
|
|
|
155
172
|
if (!upperText.includes(input.side)) {
|
|
156
173
|
missing.push("side");
|
|
157
174
|
}
|
|
175
|
+
if (input.positionSide && !upperText.includes(input.positionSide)) {
|
|
176
|
+
missing.push("positionSide");
|
|
177
|
+
}
|
|
158
178
|
if (!text.includes(input.maxNotional)) {
|
|
159
179
|
missing.push("maxNotional");
|
|
160
180
|
}
|
|
@@ -194,6 +214,31 @@ async function confirmCancel(order, probes) {
|
|
|
194
214
|
function isSafeCancelTerminalState(status) {
|
|
195
215
|
return status === "CANCELED" || status === "EXPIRED" || status === "REJECTED";
|
|
196
216
|
}
|
|
217
|
+
async function confirmCleanup(order, probes) {
|
|
218
|
+
const observations = [];
|
|
219
|
+
let current = await probes.cleanupCheck(order);
|
|
220
|
+
observations.push(cleanupEvidence(current));
|
|
221
|
+
if (isCleanupClean(current)) {
|
|
222
|
+
return { confirmed: true, cleanup: current, evidence: observations.join("->") };
|
|
223
|
+
}
|
|
224
|
+
const delays = probes.cleanupCheckDelaysMs ?? probes.cancelConfirmationDelaysMs ?? [100, 250, 500, 1_000, 2_000];
|
|
225
|
+
const wait = probes.wait ?? defaultWait;
|
|
226
|
+
for (const delay of delays) {
|
|
227
|
+
await wait(delay);
|
|
228
|
+
current = await probes.cleanupCheck(order);
|
|
229
|
+
observations.push(cleanupEvidence(current));
|
|
230
|
+
if (isCleanupClean(current)) {
|
|
231
|
+
return { confirmed: true, cleanup: current, evidence: observations.join("->") };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return { confirmed: false, cleanup: current, evidence: observations.join("->") };
|
|
235
|
+
}
|
|
236
|
+
function isCleanupClean(cleanup) {
|
|
237
|
+
return cleanup.openOrders === 0 && cleanup.unexpectedPositions === 0;
|
|
238
|
+
}
|
|
239
|
+
function cleanupEvidence(cleanup) {
|
|
240
|
+
return `openOrders=${cleanup.openOrders};unexpectedPositions=${cleanup.unexpectedPositions}`;
|
|
241
|
+
}
|
|
197
242
|
function defaultWait(milliseconds) {
|
|
198
243
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
199
244
|
}
|
package/dist/core/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RAPIDX_VERSION = "1.0.
|
|
1
|
+
export const RAPIDX_VERSION = "1.0.30";
|
package/dist/mcp/tool-runner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { consumePreview, createTargetedTradePreview, createTradePreview, defaultSafetyPolicy, executeRapidXCapability, findCapabilityById, getSchemaResult, makeEvidence, makePreviewStore, makeSafetyState, normalizeUnknownError, runSelfCheck, runTradingVerification, verifyPreview, buildLiveReadOnlySelfCheck, buildLiveTradingVerificationProbes, checkForUpdate, } from "../core/index.js";
|
|
1
|
+
import { consumePreview, createTargetedTradePreview, createTradePreview, defaultSafetyPolicy, executeRapidXCapability, findCapabilityById, getSchemaResult, makeEvidence, makePreviewStore, makeSafetyState, normalizeUnknownError, publicErrorDetails, runSelfCheck, runPreviewPreflight, runTradingVerification, verifyPreview, buildLiveReadOnlySelfCheck, buildLiveTradingVerificationProbes, checkForUpdate, } from "../core/index.js";
|
|
2
2
|
import { capabilityForTool, getMcpToolDefinitions } from "./tool-registry.js";
|
|
3
3
|
import { writeMcpAudit } from "./audit.js";
|
|
4
4
|
const previewStore = makePreviewStore();
|
|
@@ -6,8 +6,10 @@ const safetyState = makeSafetyState();
|
|
|
6
6
|
export async function runMcpTool(toolName, input = {}) {
|
|
7
7
|
try {
|
|
8
8
|
if (toolName === "rapidx/tools") {
|
|
9
|
+
const schema = getSchemaResult();
|
|
9
10
|
const data = {
|
|
10
|
-
schemaVersion:
|
|
11
|
+
schemaVersion: schema.schemaVersion,
|
|
12
|
+
inputSchemas: schema.inputSchemas,
|
|
11
13
|
tools: getMcpToolDefinitions().map((tool) => ({
|
|
12
14
|
name: tool.name,
|
|
13
15
|
riskLevel: tool.capability.riskLevel,
|
|
@@ -55,6 +57,13 @@ export async function runMcpTool(toolName, input = {}) {
|
|
|
55
57
|
throw new Error("order.preview capability missing");
|
|
56
58
|
}
|
|
57
59
|
const preview = createTradePreview(previewCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
|
|
60
|
+
try {
|
|
61
|
+
Object.assign(preview, await runPreviewPreflight("order.place", input));
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
previewStore.records.delete(preview.previewId);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
58
67
|
const auditId = writeMcpAudit("trade-preview", "PASS", { toolName, capabilityId: capability.capabilityId, previewId: preview.previewId });
|
|
59
68
|
return { ok: true, status: "PASS", data: preview, auditId, evidence: [makeEvidence(toolName)] };
|
|
60
69
|
}
|
|
@@ -65,6 +74,13 @@ export async function runMcpTool(toolName, input = {}) {
|
|
|
65
74
|
throw new Error(`${concretePreviewTarget} capability missing`);
|
|
66
75
|
}
|
|
67
76
|
const preview = createTargetedTradePreview(targetCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
|
|
77
|
+
try {
|
|
78
|
+
Object.assign(preview, await runPreviewPreflight(concretePreviewTarget, input));
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
previewStore.records.delete(preview.previewId);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
68
84
|
const auditId = writeMcpAudit("trade-preview", "PASS", { toolName, capabilityId: targetCapability.capabilityId, previewId: preview.previewId });
|
|
69
85
|
return { ok: true, status: "PASS", data: preview, auditId, evidence: [makeEvidence(toolName)] };
|
|
70
86
|
}
|
|
@@ -78,6 +94,13 @@ export async function runMcpTool(toolName, input = {}) {
|
|
|
78
94
|
throw new Error("trade preview target must be a non-preview trade write capability.");
|
|
79
95
|
}
|
|
80
96
|
const preview = createTargetedTradePreview(targetCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
|
|
97
|
+
try {
|
|
98
|
+
Object.assign(preview, await runPreviewPreflight(targetCapability.capabilityId, input));
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
previewStore.records.delete(preview.previewId);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
81
104
|
const auditId = writeMcpAudit("trade-preview", "PASS", { toolName, capabilityId: targetCapability.capabilityId, previewId: preview.previewId });
|
|
82
105
|
return { ok: true, status: "PASS", data: preview, auditId, evidence: [makeEvidence(toolName)] };
|
|
83
106
|
}
|
|
@@ -101,11 +124,13 @@ export async function runMcpTool(toolName, input = {}) {
|
|
|
101
124
|
}
|
|
102
125
|
catch (error) {
|
|
103
126
|
const productError = normalizeUnknownError(error, "RMCP12003");
|
|
127
|
+
const details = publicErrorDetails(productError);
|
|
104
128
|
return {
|
|
105
129
|
ok: false,
|
|
106
130
|
status: productError.status,
|
|
107
131
|
code: productError.code.replace(/^RCORE/, "RMCP"),
|
|
108
132
|
message: productError.message,
|
|
133
|
+
...(details ? { details } : {}),
|
|
109
134
|
evidence: [makeEvidence(toolName)]
|
|
110
135
|
};
|
|
111
136
|
}
|
package/package.json
CHANGED
|
@@ -21,7 +21,9 @@ Supported conventions:
|
|
|
21
21
|
- Use `rapidx order cancel-preview` for `order.cancel`.
|
|
22
22
|
- Use `rapidx trade preview` with `targetCapabilityId` for non-order trade writes, including position, account, and algo writes.
|
|
23
23
|
- `rapidx trade preview` input is flat JSON; do not wrap target parameters under `params`.
|
|
24
|
+
- CLI preview ids are stored in the local CLI preview store. Do not submit MCP-created preview ids through CLI.
|
|
24
25
|
- Treat `maxNotional` as a safety upper bound, not as the target order amount. Check symbol `minNotional` before increasing a requested amount.
|
|
26
|
+
- Use `positionSide="LONG"` or `positionSide="SHORT"` for hedge-mode order placement or verification. Do not switch account position mode just to place a hedge-side order.
|
|
25
27
|
|
|
26
28
|
Examples:
|
|
27
29
|
|
|
@@ -42,8 +44,16 @@ rapidx account set-position-mode --input @/absolute/path/set-position-mode.json
|
|
|
42
44
|
|
|
43
45
|
`rapidx update check --json` reads the RapidX release manifest and caches the result locally. Use it during setup, review, or session startup. Trade submit paths should not perform a fresh network update check.
|
|
44
46
|
|
|
47
|
+
`rapidx schema --json` returns `capabilities` plus `inputSchemas`. Agents should read the concrete input schema before constructing write inputs.
|
|
48
|
+
|
|
45
49
|
`rapidx account balance` defaults to `mode=portfolio` and reads `/api/v1/trading/portfolio/assets`. `mode=account` reads `/api/v1/account/balance` and requires credentials with account-level permission.
|
|
46
50
|
|
|
47
|
-
`rapidx account set-position-mode` is a trade write. It requires a matching preview token and a `continueConsentId` before execution.
|
|
51
|
+
`rapidx account set-position-mode` is a trade write. Use it only when the user explicitly asks to change account position mode. It requires a matching preview token and a `continueConsentId` before execution.
|
|
52
|
+
|
|
53
|
+
`rapidx order cancel` is asynchronous. A successful response can mean cancel accepted but not terminal. Poll `rapidx order get --json` when `terminalStateConfirmed=false`.
|
|
54
|
+
|
|
55
|
+
When using `orderId`, pass the RapidX 16-digit numeric order id. Invalid `orderId` format is rejected locally as `INVALID_INPUT`. `clientOrderId` can be used instead and does not need to satisfy the `orderId` format. Preview commands for amend/cancel perform a RapidX readback after local format validation; valid-but-missing orders return `NOT_FOUND` or a blocked non-open state.
|
|
56
|
+
|
|
57
|
+
Automation mode still uses preview. Pass `automationMode=true` and the user's exact `automationConsentText` to preview only when the user has enabled automation in chat for the current scope.
|
|
48
58
|
|
|
49
59
|
`rapidx position close` is a close-position action. Do not pass `side` or `quantity`; RapidX determines BUY or SELL from the current position and closes the target symbol/positionSide. Use a reduce-only order flow for partial closes. Verify the final exposure with `rapidx position list --json`, and do not rely only on `order get` to interpret the close intent.
|
|
@@ -24,13 +24,18 @@ Public tool names use the `rapidx/` prefix.
|
|
|
24
24
|
|
|
25
25
|
Key trading tools:
|
|
26
26
|
|
|
27
|
+
- `rapidx/tools` returns the MCP tool list plus `inputSchemas`. Agents should read the concrete input schema before constructing write inputs.
|
|
27
28
|
- `rapidx/update/check` reads the RapidX release manifest and returns current/latest CLI version, minimum write version, skills update advice, and upgrade commands.
|
|
28
29
|
- `rapidx/self-check` can include update state when called with `{"checkUpdates": true}`.
|
|
29
30
|
- `rapidx/order/preview` creates preview tokens for `order.place`.
|
|
30
31
|
- `rapidx/order/place-preview`, `rapidx/order/amend-preview`, and `rapidx/order/cancel-preview` are concrete order preview aliases. Preview responses include `confirmation.submitToken`; pass it as `continueConsentId` when submitting the matching write tool.
|
|
31
32
|
- `rapidx/trade/preview` creates preview tokens for non-place trade writes by `targetCapabilityId`, including position, account, and algo writes.
|
|
33
|
+
- Preview ids are local to the running MCP server. Do not submit a CLI-created preview id through MCP, and do not submit an MCP-created preview id through CLI.
|
|
34
|
+
- `rapidx/order/cancel` returns cancel acceptance plus terminal-state guidance. Poll `rapidx/order/get` when `terminalStateConfirmed=false`.
|
|
35
|
+
- Automation mode still uses preview. Set `automationMode=true` and pass the user's exact `automationConsentText` only when the user has enabled automation in chat for the current scope.
|
|
32
36
|
- `rapidx/position/history` reads historical positions.
|
|
33
|
-
-
|
|
34
|
-
- `rapidx/
|
|
37
|
+
- Hedge-mode order placement and verification use `positionSide="LONG"` or `positionSide="SHORT"` in the order or verify-live input. Do not use account mode switching as a substitute for order-level `positionSide`.
|
|
38
|
+
- `rapidx/account/set-position-mode` changes account position mode and requires preview plus explicit continuation consent. Use it only when the user explicitly asks to change account position mode.
|
|
39
|
+
- `rapidx/trade/verify-live` runs the optional small real-trade verification flow with `explicitUserConsent` and parameter-bound `acceptedRiskText`; it creates its own internal preview and does not accept an external `previewId`. If `positionSide` is provided, `acceptedRiskText` must include that position side. `rapidx/trading-verification` remains supported as a compatibility alias.
|
|
35
40
|
|
|
36
41
|
Agents must discover the full tool list through `rapidx/tools` and must not invent tool names.
|
|
@@ -113,6 +113,8 @@ rapidx schema --json
|
|
|
113
113
|
|
|
114
114
|
For trade writes, create a preview before submit. Use `rapidx order place-preview` for `order.place`, `rapidx order amend-preview` for `order.amend`, and `rapidx order cancel-preview` for `order.cancel`. Use flat JSON with `rapidx trade preview` and `targetCapabilityId` for non-order writes such as `position.set-leverage`, `position.close`, or `account.set-position-mode`.
|
|
115
115
|
|
|
116
|
+
`rapidx schema --json` and `rapidx/tools` return concrete `inputSchemas`. For hedge-mode orders, use `positionSide="LONG"` or `positionSide="SHORT"` in the order or verify-live input. Use `account.set-position-mode` only when the user explicitly asks to change account mode.
|
|
117
|
+
|
|
116
118
|
For clearer order flows, prefer the explicit aliases:
|
|
117
119
|
|
|
118
120
|
```bash
|
|
@@ -124,6 +126,12 @@ rapidx trade preview --input '{"targetCapabilityId":"position.set-leverage","sym
|
|
|
124
126
|
|
|
125
127
|
When submitting the actual write, pass the returned `previewId` and use `confirmation.submitToken` as `continueConsentId`.
|
|
126
128
|
|
|
129
|
+
Preview ids are runtime-local. Submit MCP previews through the same MCP server runtime, and submit CLI previews through the same CLI preview store.
|
|
130
|
+
|
|
131
|
+
`order.cancel` is asynchronous. If the cancel result says `terminalStateConfirmed=false`, poll `order get` until a terminal state before claiming that the order is cancelled.
|
|
132
|
+
|
|
133
|
+
For automation, keep the preview-first flow. Use `automationMode=true` and the user's exact `automationConsentText` only after the user has enabled RapidX automation mode in chat for the current scope.
|
|
134
|
+
|
|
127
135
|
`maxNotional` is a safety upper bound, not the target order amount. If a requested amount is below the symbol `minNotional`, ask the user to confirm the larger amount before submitting. For `position.close`, do not pass `side` or `quantity`; RapidX determines the close side from the current position and closes the target symbol/positionSide. Use a reduce-only order flow for partial closes, then check final exposure with `position list`.
|
|
128
136
|
|
|
129
|
-
Use `rapidx trade verify-live --json` only when the user explicitly authorizes a small real-trade verification. Include `acceptedRiskText` that names the exact symbol, side, maxNotional, real-order risk, and cancel cleanup behavior. The older `rapidx self-check trade-verify --json` command remains supported for compatibility.
|
|
137
|
+
Use `rapidx trade verify-live --json` only when the user explicitly authorizes a small real-trade verification. Include `acceptedRiskText` that names the exact symbol, side, positionSide when provided, maxNotional, real-order risk, and cancel cleanup behavior. The older `rapidx self-check trade-verify --json` command remains supported for compatibility.
|
|
@@ -4,7 +4,7 @@ The canonical source is `rapidx schema --json` or `rapidx/tools`.
|
|
|
4
4
|
|
|
5
5
|
## Discovery And Update
|
|
6
6
|
|
|
7
|
-
- `rapidx/tools`: returns the MCP tool surface and
|
|
7
|
+
- `rapidx/tools`: returns the MCP tool surface and concrete `inputSchemas`.
|
|
8
8
|
- `rapidx/self-check`: verifies discovery, schema, credentials, and optional read-only probes. Pass `checkUpdates=true` to include release manifest status.
|
|
9
9
|
- `rapidx/update/check`: reads the RapidX release manifest, returns version status, minimum write version, skills update advice, and upgrade commands.
|
|
10
10
|
|
|
@@ -24,12 +24,48 @@ The canonical source is `rapidx schema --json` or `rapidx/tools`.
|
|
|
24
24
|
- Position reads: list and position history.
|
|
25
25
|
- Algo reads: list.
|
|
26
26
|
|
|
27
|
+
## Symbol Format
|
|
28
|
+
|
|
29
|
+
Use RapidX symbols in tool inputs:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
BINANCE_PERP_<BASE>_<QUOTE>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`OKX_PERP_<BASE>_<QUOTE>` is also supported for OKX perpetual instruments. `OKX_SWAP_<BASE>_<QUOTE>` is accepted as an input alias and is normalized to `OKX_PERP_<BASE>_<QUOTE>` in preview matching and outputs.
|
|
36
|
+
|
|
37
|
+
Public market adapters may call venue APIs with official venue symbols such as `BTCUSDT` or `BTC-USDT-SWAP`. When the venue response uses a different symbol, RapidX returns canonical `symbol` plus `originalSymbol`.
|
|
38
|
+
|
|
27
39
|
## Write Tools
|
|
28
40
|
|
|
29
41
|
- Order writes: place, amend, and cancel.
|
|
30
42
|
- Position writes: close and set leverage.
|
|
31
43
|
- Account writes: set position mode.
|
|
32
44
|
- Algo writes: place, amend, and cancel.
|
|
33
|
-
- Live verification: `rapidx/trade/verify-live` is the clearer alias for the small real-trade verification flow. It submits a real verification order and requires `acceptedRiskText` bound to the exact symbol, side, maxNotional, real-order risk, and cancel cleanup behavior. `rapidx/trading-verification` remains supported for compatibility.
|
|
45
|
+
- Live verification: `rapidx/trade/verify-live` is the clearer alias for the small real-trade verification flow. It submits a real verification order and requires `acceptedRiskText` bound to the exact symbol, side, maxNotional, real-order risk, and cancel cleanup behavior. If `positionSide` is provided, `acceptedRiskText` must include it. `rapidx/trading-verification` remains supported for compatibility.
|
|
34
46
|
|
|
35
47
|
All write tools require preview where the schema marks `previewRequired: true`.
|
|
48
|
+
MARKET order writes are allowed after preview and consent. Preview includes immediate-execution and slippage risk notes; do not replace MARKET with a synthetic limit strategy unless the user explicitly asks for that order style.
|
|
49
|
+
|
|
50
|
+
For hedge-mode order placement, pass `positionSide="LONG"` or `positionSide="SHORT"` on the order or verify-live input. Use `account.set-position-mode` only when the user explicitly asks to change the account position mode.
|
|
51
|
+
|
|
52
|
+
Preview ids are runtime-local. A preview created by MCP must be submitted through the same MCP server runtime. A preview created by CLI must be submitted through the same CLI preview store. Do not mix MCP preview ids with CLI submit commands, or the reverse.
|
|
53
|
+
|
|
54
|
+
`order.cancel` is asynchronous. A successful cancel response means the cancel request was accepted; it may not prove a terminal order state yet. The response includes `cancelAccepted`, `terminalStateConfirmed`, `lastObservedOrderState`, and `recommendedAction`. Poll `order.get` until `CANCELED`, `REJECTED`, `EXPIRED`, or timeout when `terminalStateConfirmed=false`.
|
|
55
|
+
|
|
56
|
+
Order identifiers use two validation layers. If `orderId` is provided, RapidX validates it locally as a 16-digit numeric id and returns `INVALID_INPUT` before any upstream call when the format is wrong. If `clientOrderId` is provided instead, do not apply `orderId` validation. For `order.get`, `order.amend-preview`, and `order.cancel-preview`, a syntactically valid `orderId` or `clientOrderId` is checked through RapidX readback; missing or non-open orders return `NOT_FOUND` or `BLOCKED` depending on the observed state.
|
|
57
|
+
|
|
58
|
+
For automation, keep preview-first execution. Set `automationMode=true` and pass the user's exact `automationConsentText` only when the user has explicitly enabled RapidX automation mode in chat. The preview response still returns `confirmation.submitToken`; automation mode only means the agent may use that submit token without asking for another per-order chat confirmation within the authorized scope.
|
|
59
|
+
|
|
60
|
+
For TPSL or conditional algo orders, use `rapidx/trade/preview` with `targetCapabilityId="algo.place"`, then submit `rapidx/algo/place`. `conditionType="ENTIRE_CLOSE_POSITION"` may use `orderType="MARKET"` without `quantity` or `amount`; provide at least one take-profit or stop-loss trigger and verify with `rapidx/algo/list`.
|
|
61
|
+
|
|
62
|
+
## Result Statuses
|
|
63
|
+
|
|
64
|
+
- `PASS`: the tool or command completed successfully.
|
|
65
|
+
- `INVALID_INPUT`: local schema or parameter validation failed. Fix the input before retrying.
|
|
66
|
+
- `BLOCKED`: local preview, safety, compatibility, or policy checks rejected the action before submission.
|
|
67
|
+
- `NOT_FOUND`: the requested order, position, or upstream resource was not found.
|
|
68
|
+
- `PERMISSION_SCOPE_ERROR`: credentials are valid but do not cover the requested scope, such as account-level balance with portfolio-scoped credentials.
|
|
69
|
+
- `BUSINESS_ERROR`: RapidX or venue business rules rejected the request.
|
|
70
|
+
- `NOT_VERIFIED`: the tool could not prove the requested state.
|
|
71
|
+
- `FAIL`: startup, auth, network, malformed response, or unexpected execution failure.
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.0.
|
|
2
|
+
"version": "1.0.30",
|
|
3
3
|
"artifacts": [
|
|
4
4
|
{
|
|
5
5
|
"name": "rapidx-mcp-registry",
|
|
6
6
|
"path": "packages/distribution/registry/rapidx.mcp.json",
|
|
7
7
|
"channel": "offline",
|
|
8
|
-
"checksum": "sha256:
|
|
8
|
+
"checksum": "sha256:ad2ef7cf5eae5cadf3d6901574a93219a90dd2b53f0412ee354984ed30edf004",
|
|
9
9
|
"status": "ready"
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"name": "rapidx-quickstart-doc",
|
|
13
13
|
"path": "packages/distribution/docs/quickstart.md",
|
|
14
14
|
"channel": "offline",
|
|
15
|
-
"checksum": "sha256:
|
|
15
|
+
"checksum": "sha256:f1771cbc6e3da39b47afac839ca89fc063bede11ac9d8078b215145953c0145c",
|
|
16
16
|
"status": "ready"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"name": "rapidx-tools-doc",
|
|
20
20
|
"path": "packages/distribution/docs/tools.md",
|
|
21
21
|
"channel": "offline",
|
|
22
|
-
"checksum": "sha256:
|
|
22
|
+
"checksum": "sha256:fc80e63eabc8407c74b01ed707187505c90497af53a25f201c07e13ef948bf18",
|
|
23
23
|
"status": "ready"
|
|
24
24
|
}
|
|
25
25
|
]
|