@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { executeRapidXCapability, findCapabilityById, normalizeUnknownError } from "../../core/index.js";
|
|
1
|
+
import { executeRapidXCapability, findCapabilityById, normalizeUnknownError, publicErrorDetails } from "../../core/index.js";
|
|
2
2
|
import { writeCliAudit } from "../audit.js";
|
|
3
3
|
import { fail, ok } from "../envelope.js";
|
|
4
4
|
import { enforceCliPreviewGate } from "./trade-gate.js";
|
|
@@ -29,6 +29,6 @@ export async function runAccountCommand(action, input) {
|
|
|
29
29
|
}
|
|
30
30
|
catch (error) {
|
|
31
31
|
const productError = normalizeUnknownError(error, "RCLI01001");
|
|
32
|
-
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx account ${action}
|
|
32
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx account ${action}`, publicErrorDetails(productError));
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { executeRapidXCapability, findCapabilityById, normalizeUnknownError } from "../../core/index.js";
|
|
1
|
+
import { executeRapidXCapability, findCapabilityById, normalizeUnknownError, publicErrorDetails } from "../../core/index.js";
|
|
2
2
|
import { writeCliAudit } from "../audit.js";
|
|
3
3
|
import { fail, ok } from "../envelope.js";
|
|
4
4
|
import { enforceCliPreviewGate } from "./trade-gate.js";
|
|
@@ -30,6 +30,6 @@ export async function runAlgoCommand(action, input) {
|
|
|
30
30
|
}
|
|
31
31
|
catch (error) {
|
|
32
32
|
const productError = normalizeUnknownError(error, "RCLI12001");
|
|
33
|
-
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx algo ${action}
|
|
33
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx algo ${action}`, publicErrorDetails(productError));
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { executeRapidXCapability, normalizeUnknownError } from "../../core/index.js";
|
|
1
|
+
import { executeRapidXCapability, normalizeUnknownError, publicErrorDetails } from "../../core/index.js";
|
|
2
2
|
import { ok } from "../envelope.js";
|
|
3
3
|
import { fail } from "../envelope.js";
|
|
4
4
|
const MARKET_CAPABILITY_BY_ACTION = {
|
|
@@ -21,6 +21,6 @@ export async function runMarketCommand(action, input) {
|
|
|
21
21
|
}
|
|
22
22
|
catch (error) {
|
|
23
23
|
const productError = normalizeUnknownError(error, "RCLI12001");
|
|
24
|
-
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx market ${action}
|
|
24
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx market ${action}`, publicErrorDetails(productError));
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { consumePreview, createTargetedTradePreview, createTradePreview, defaultSafetyPolicy, executeRapidXCapability, findCapabilityById, loadPreviewStoreFromFile, makeSafetyState, normalizeUnknownError, resolvePreviewStoreFile, savePreviewStoreToFile, verifyPreview } from "../../core/index.js";
|
|
1
|
+
import { consumePreview, createTargetedTradePreview, createTradePreview, defaultSafetyPolicy, executeRapidXCapability, findCapabilityById, loadPreviewStoreFromFile, makeSafetyState, normalizeUnknownError, publicErrorDetails, resolvePreviewStoreFile, runPreviewPreflight, savePreviewStoreToFile, verifyPreview } from "../../core/index.js";
|
|
2
2
|
import { fail, ok } from "../envelope.js";
|
|
3
3
|
import { writeCliAudit } from "../audit.js";
|
|
4
4
|
const safetyState = makeSafetyState();
|
|
@@ -12,12 +12,13 @@ export async function runOrderCommand(action, input) {
|
|
|
12
12
|
}
|
|
13
13
|
try {
|
|
14
14
|
const preview = createTradePreview(capability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
|
|
15
|
+
Object.assign(preview, await runPreviewPreflight("order.place", input));
|
|
15
16
|
savePreviewStoreToFile(previewStoreFile, previewStore);
|
|
16
17
|
return ok(preview, `rapidx order ${action}`);
|
|
17
18
|
}
|
|
18
19
|
catch (error) {
|
|
19
20
|
const productError = normalizeUnknownError(error, "RCLI20002");
|
|
20
|
-
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx order ${action}
|
|
21
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx order ${action}`, publicErrorDetails(productError));
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
const previewTarget = previewTargetForAction(action);
|
|
@@ -28,12 +29,13 @@ export async function runOrderCommand(action, input) {
|
|
|
28
29
|
}
|
|
29
30
|
try {
|
|
30
31
|
const preview = createTargetedTradePreview(targetCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
|
|
32
|
+
Object.assign(preview, await runPreviewPreflight(previewTarget, input));
|
|
31
33
|
savePreviewStoreToFile(previewStoreFile, previewStore);
|
|
32
34
|
return ok(preview, `rapidx order ${action}`);
|
|
33
35
|
}
|
|
34
36
|
catch (error) {
|
|
35
37
|
const productError = normalizeUnknownError(error, "RCLI20002");
|
|
36
|
-
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx order ${action}
|
|
38
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx order ${action}`, publicErrorDetails(productError));
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
const capability = findCapabilityById(`order.${action}`);
|
|
@@ -67,7 +69,7 @@ export async function runOrderCommand(action, input) {
|
|
|
67
69
|
}
|
|
68
70
|
catch (error) {
|
|
69
71
|
const productError = normalizeUnknownError(error, "RCLI12001");
|
|
70
|
-
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx order ${action}
|
|
72
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx order ${action}`, publicErrorDetails(productError));
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
function previewTargetForAction(action) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { executeRapidXCapability, findCapabilityById, normalizeUnknownError } from "../../core/index.js";
|
|
1
|
+
import { executeRapidXCapability, findCapabilityById, normalizeUnknownError, publicErrorDetails } from "../../core/index.js";
|
|
2
2
|
import { writeCliAudit } from "../audit.js";
|
|
3
3
|
import { fail, ok } from "../envelope.js";
|
|
4
4
|
import { enforceCliPreviewGate } from "./trade-gate.js";
|
|
@@ -30,6 +30,6 @@ export async function runPositionCommand(action, input) {
|
|
|
30
30
|
}
|
|
31
31
|
catch (error) {
|
|
32
32
|
const productError = normalizeUnknownError(error, "RCLI12001");
|
|
33
|
-
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx position ${action}
|
|
33
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, `rapidx position ${action}`, publicErrorDetails(productError));
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createTargetedTradePreview, defaultSafetyPolicy, findCapabilityById, loadPreviewStoreFromFile, makeSafetyState, normalizeUnknownError, resolvePreviewStoreFile, savePreviewStoreToFile } from "../../core/index.js";
|
|
1
|
+
import { createTargetedTradePreview, defaultSafetyPolicy, findCapabilityById, loadPreviewStoreFromFile, makeSafetyState, normalizeUnknownError, publicErrorDetails, resolvePreviewStoreFile, runPreviewPreflight, savePreviewStoreToFile } from "../../core/index.js";
|
|
2
2
|
import { fail, ok } from "../envelope.js";
|
|
3
3
|
const safetyState = makeSafetyState();
|
|
4
4
|
export async function runTradeCommand(action, input) {
|
|
@@ -17,12 +17,13 @@ export async function runTradeCommand(action, input) {
|
|
|
17
17
|
const previewStore = loadPreviewStoreFromFile(previewStoreFile);
|
|
18
18
|
try {
|
|
19
19
|
const preview = createTargetedTradePreview(targetCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore);
|
|
20
|
+
Object.assign(preview, await runPreviewPreflight(targetCapability.capabilityId, input));
|
|
20
21
|
savePreviewStoreToFile(previewStoreFile, previewStore);
|
|
21
22
|
return ok(preview, "rapidx trade preview");
|
|
22
23
|
}
|
|
23
24
|
catch (error) {
|
|
24
25
|
const productError = normalizeUnknownError(error, "RCLI20002");
|
|
25
|
-
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, "rapidx trade preview");
|
|
26
|
+
return fail(productError.code.replace(/^RCORE/, "RCLI"), productError.message, productError.status, "rapidx trade preview", publicErrorDetails(productError));
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
function isPreviewCapability(capabilityId) {
|
package/dist/cli/envelope.js
CHANGED
|
@@ -8,12 +8,13 @@ export function ok(data, evidenceText, status = "PASS", source = "local_check",
|
|
|
8
8
|
evidence: [makeEvidence(evidenceText, source)]
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
export function fail(code, message, status = "FAIL", evidenceText = "rapidx cli") {
|
|
11
|
+
export function fail(code, message, status = "FAIL", evidenceText = "rapidx cli", details) {
|
|
12
12
|
return {
|
|
13
13
|
ok: false,
|
|
14
14
|
status,
|
|
15
15
|
code,
|
|
16
16
|
message,
|
|
17
|
+
...(details ? { details } : {}),
|
|
17
18
|
evidence: [makeEvidence(evidenceText)]
|
|
18
19
|
};
|
|
19
20
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ProductError } from "../errors/product-error.js";
|
|
2
2
|
import { parseRapidXSymbol } from "./symbol.js";
|
|
3
3
|
import { RapidXClient } from "./rapid-x-client.js";
|
|
4
|
+
import { assertOrderLookupReference, assertValidOrderId } from "./order-id.js";
|
|
4
5
|
import { findCapabilityById } from "../contracts/capabilities.js";
|
|
5
6
|
import { assertInputMatchesSchema } from "../contracts/input-schema.js";
|
|
6
7
|
export async function executeRapidXCapability(capabilityId, input = {}, options = {}) {
|
|
@@ -32,9 +33,9 @@ export async function executeRapidXCapability(capabilityId, input = {}, options
|
|
|
32
33
|
case "order.amend":
|
|
33
34
|
return client.put("/api/v1/trading/order", orderAmendBody(input));
|
|
34
35
|
case "order.cancel":
|
|
35
|
-
return client
|
|
36
|
+
return executeOrderCancelCapability(client, input);
|
|
36
37
|
case "order.get":
|
|
37
|
-
return client.get("/api/v1/trading/order",
|
|
38
|
+
return client.get("/api/v1/trading/order", orderReadParams(input));
|
|
38
39
|
case "order.list":
|
|
39
40
|
return client.get("/api/v1/trading/orders", { pageSize: stringValue(input.pageSize, "1000"), ...optionalParams(input, ["symbol"]) });
|
|
40
41
|
case "order.history":
|
|
@@ -49,10 +50,11 @@ export async function executeRapidXCapability(capabilityId, input = {}, options
|
|
|
49
50
|
case "position.close":
|
|
50
51
|
return executePositionCloseCapability(client, input);
|
|
51
52
|
case "position.set-leverage":
|
|
52
|
-
return client.post("/api/v1/trading/position/leverage", {
|
|
53
|
+
return client.post("/api/v1/trading/position/leverage", compactObject({
|
|
53
54
|
sym: String(input.symbol),
|
|
54
|
-
leverage: String(input.leverage)
|
|
55
|
-
|
|
55
|
+
leverage: String(input.leverage),
|
|
56
|
+
positionSide: input.positionSide
|
|
57
|
+
}));
|
|
56
58
|
case "algo.place":
|
|
57
59
|
return client.post("/api/v1/algo/order", algoPlaceBody(input));
|
|
58
60
|
case "algo.amend":
|
|
@@ -70,8 +72,9 @@ export async function executeRapidXCapability(capabilityId, input = {}, options
|
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
async function executeMarketCapability(capabilityId, input, fetchFn) {
|
|
73
|
-
const
|
|
74
|
-
const parsed = parseRapidXSymbol(
|
|
75
|
+
const inputSymbol = stringValue(input.symbol ?? input.sym, "BINANCE_PERP_BTC_USDT");
|
|
76
|
+
const parsed = parseRapidXSymbol(inputSymbol);
|
|
77
|
+
const symbol = parsed.canonicalSymbol;
|
|
75
78
|
if (capabilityId === "market.funding-rate" || capabilityId === "market.mark-price" || capabilityId === "market.symbol-info") {
|
|
76
79
|
const client = new RapidXClient({ fetchFn });
|
|
77
80
|
const path = capabilityId === "market.funding-rate"
|
|
@@ -96,6 +99,7 @@ async function executeMarketCapability(capabilityId, input, fetchFn) {
|
|
|
96
99
|
case "market.ticker": {
|
|
97
100
|
const raw = await fetchJson(fetchFn, `${base}${parsed.isPerp ? "/fapi/v1/ticker/24hr" : "/api/v3/ticker/24hr"}`, { symbol: parsed.binanceSymbol });
|
|
98
101
|
return {
|
|
102
|
+
...symbolMetadata(symbol, inputSymbol, parsed.binanceSymbol),
|
|
99
103
|
symbol,
|
|
100
104
|
lastPrice: raw.lastPrice,
|
|
101
105
|
priceChange: raw.priceChange,
|
|
@@ -108,7 +112,7 @@ async function executeMarketCapability(capabilityId, input, fetchFn) {
|
|
|
108
112
|
}
|
|
109
113
|
case "market.orderbook": {
|
|
110
114
|
const raw = await fetchJson(fetchFn, `${base}${parsed.isPerp ? "/fapi/v1/depth" : "/api/v3/depth"}`, { symbol: parsed.binanceSymbol, limit: stringValue(input.depth, "20") });
|
|
111
|
-
return normalizeOrderbook(symbol, raw.bids, raw.asks);
|
|
115
|
+
return normalizeOrderbook(symbol, raw.bids, raw.asks, symbolMetadata(symbol, inputSymbol, parsed.binanceSymbol));
|
|
112
116
|
}
|
|
113
117
|
case "market.klines": {
|
|
114
118
|
const raw = await fetchJson(fetchFn, `${base}${parsed.isPerp ? "/fapi/v1/klines" : "/api/v3/klines"}`, {
|
|
@@ -116,13 +120,14 @@ async function executeMarketCapability(capabilityId, input, fetchFn) {
|
|
|
116
120
|
interval: stringValue(input.interval, "1h"),
|
|
117
121
|
limit: stringValue(input.limit, "100")
|
|
118
122
|
});
|
|
119
|
-
return { symbol, interval: stringValue(input.interval, "1h"), count: raw.length, candles: raw };
|
|
123
|
+
return { ...symbolMetadata(symbol, inputSymbol, parsed.binanceSymbol), symbol, interval: stringValue(input.interval, "1h"), count: raw.length, candles: raw };
|
|
120
124
|
}
|
|
121
125
|
case "market.open-interest": {
|
|
122
126
|
if (!parsed.isPerp) {
|
|
123
127
|
throw new ProductError({ code: "RCORE00001", status: "FAIL", message: "open interest only supports perpetual symbols" });
|
|
124
128
|
}
|
|
125
|
-
|
|
129
|
+
const raw = await fetchJson(fetchFn, `${base}/fapi/v1/openInterest`, { symbol: parsed.binanceSymbol });
|
|
130
|
+
return { ...raw, ...symbolMetadata(symbol, inputSymbol, String(raw.symbol ?? parsed.binanceSymbol)), symbol };
|
|
126
131
|
}
|
|
127
132
|
default:
|
|
128
133
|
throw new ProductError({ code: "RCORE30002", status: "FAIL", message: `Unknown market capability: ${capabilityId}` });
|
|
@@ -159,23 +164,39 @@ async function executePositionCloseCapability(client, input) {
|
|
|
159
164
|
response
|
|
160
165
|
};
|
|
161
166
|
}
|
|
167
|
+
async function executeOrderCancelCapability(client, input) {
|
|
168
|
+
const response = await client.delete("/api/v1/trading/order", orderCancelBody(input));
|
|
169
|
+
const lastObservedOrderState = normalizeCancelOrderState(extractOrderState(response));
|
|
170
|
+
const terminalStateConfirmed = isCancelTerminalState(lastObservedOrderState);
|
|
171
|
+
return {
|
|
172
|
+
cancelAccepted: true,
|
|
173
|
+
terminalStateConfirmed,
|
|
174
|
+
...compactObject({ lastObservedOrderState }),
|
|
175
|
+
recommendedAction: terminalStateConfirmed
|
|
176
|
+
? "verify final status with order/get or order/list when needed"
|
|
177
|
+
: "poll order/get until CANCELED, REJECTED, EXPIRED, or timeout",
|
|
178
|
+
response
|
|
179
|
+
};
|
|
180
|
+
}
|
|
162
181
|
async function executeOkxMarketCapability(capabilityId, symbol, instId, input, fetchFn) {
|
|
182
|
+
const inputSymbol = stringValue(input.symbol ?? input.sym, symbol);
|
|
163
183
|
switch (capabilityId) {
|
|
164
184
|
case "market.ticker": {
|
|
165
185
|
const data = await fetchOkx(fetchFn, "/api/v5/market/ticker", { instId });
|
|
166
186
|
const raw = data[0] ?? {};
|
|
167
|
-
return { symbol, lastPrice: raw.last, highPrice: raw.high24h, lowPrice: raw.low24h, volume: raw.vol24h, quoteVolume: raw.volCcy24h };
|
|
187
|
+
return { ...symbolMetadata(symbol, inputSymbol, String(raw.instId ?? instId)), symbol, lastPrice: raw.last, highPrice: raw.high24h, lowPrice: raw.low24h, volume: raw.vol24h, quoteVolume: raw.volCcy24h };
|
|
168
188
|
}
|
|
169
189
|
case "market.orderbook": {
|
|
170
190
|
const data = await fetchOkx(fetchFn, "/api/v5/market/books", { instId, sz: stringValue(input.depth, "20") });
|
|
171
191
|
const raw = data[0] ?? { bids: [], asks: [] };
|
|
172
192
|
const bids = Array.isArray(raw.bids) ? raw.bids : [];
|
|
173
193
|
const asks = Array.isArray(raw.asks) ? raw.asks : [];
|
|
174
|
-
return normalizeOrderbook(symbol, bids, asks);
|
|
194
|
+
return normalizeOrderbook(symbol, bids, asks, symbolMetadata(symbol, inputSymbol, instId));
|
|
175
195
|
}
|
|
176
196
|
case "market.open-interest": {
|
|
177
197
|
const data = await fetchOkx(fetchFn, "/api/v5/public/open-interest", { instId });
|
|
178
|
-
|
|
198
|
+
const raw = data[0] ?? {};
|
|
199
|
+
return { ...raw, ...symbolMetadata(symbol, inputSymbol, String(raw.instId ?? instId)), symbol };
|
|
179
200
|
}
|
|
180
201
|
default:
|
|
181
202
|
throw new ProductError({ code: "RCORE30002", status: "FAIL", message: `OKX adapter not registered for ${capabilityId}` });
|
|
@@ -200,10 +221,11 @@ async function fetchOkx(fetchFn, path, params) {
|
|
|
200
221
|
}
|
|
201
222
|
return payload.data ?? [];
|
|
202
223
|
}
|
|
203
|
-
function normalizeOrderbook(symbol, bidsRaw, asksRaw) {
|
|
224
|
+
function normalizeOrderbook(symbol, bidsRaw, asksRaw, metadata = {}) {
|
|
204
225
|
const bids = bidsRaw.map(([price, quantity]) => ({ price, quantity }));
|
|
205
226
|
const asks = asksRaw.map(([price, quantity]) => ({ price, quantity }));
|
|
206
227
|
return {
|
|
228
|
+
...metadata,
|
|
207
229
|
symbol,
|
|
208
230
|
bestBid: bids[0] ?? null,
|
|
209
231
|
bestAsk: asks[0] ?? null,
|
|
@@ -211,6 +233,12 @@ function normalizeOrderbook(symbol, bidsRaw, asksRaw) {
|
|
|
211
233
|
asks
|
|
212
234
|
};
|
|
213
235
|
}
|
|
236
|
+
function symbolMetadata(symbol, inputSymbol, originalSymbol) {
|
|
237
|
+
return compactObject({
|
|
238
|
+
inputSymbol: inputSymbol !== symbol ? inputSymbol : undefined,
|
|
239
|
+
originalSymbol: originalSymbol !== symbol ? originalSymbol : undefined
|
|
240
|
+
});
|
|
241
|
+
}
|
|
214
242
|
function orderPlaceBody(input) {
|
|
215
243
|
const body = requiredParams(input, ["symbol", "side", "orderType", "clientOrderId"]);
|
|
216
244
|
const quantity = input.quantity ?? input.qty;
|
|
@@ -232,6 +260,9 @@ function orderPlaceBody(input) {
|
|
|
232
260
|
if (input.reduceOnly !== undefined) {
|
|
233
261
|
body.reduceOnly = input.reduceOnly === true ? "TRUE" : "FALSE";
|
|
234
262
|
}
|
|
263
|
+
if (input.positionSide !== undefined && input.positionSide !== null && input.positionSide !== "") {
|
|
264
|
+
body.positionSide = String(input.positionSide);
|
|
265
|
+
}
|
|
235
266
|
return {
|
|
236
267
|
sym: String(body.symbol),
|
|
237
268
|
side: String(body.side),
|
|
@@ -241,6 +272,7 @@ function orderPlaceBody(input) {
|
|
|
241
272
|
};
|
|
242
273
|
}
|
|
243
274
|
function orderAmendBody(input) {
|
|
275
|
+
assertValidOrderId(input.orderId);
|
|
244
276
|
const body = optionalParams(input, ["orderId", "clientOrderId"]);
|
|
245
277
|
if (input.quantity !== undefined) {
|
|
246
278
|
body.replaceQty = String(input.quantity);
|
|
@@ -251,8 +283,13 @@ function orderAmendBody(input) {
|
|
|
251
283
|
return body;
|
|
252
284
|
}
|
|
253
285
|
function orderCancelBody(input) {
|
|
286
|
+
assertValidOrderId(input.orderId);
|
|
254
287
|
return optionalParams(input, ["orderId", "clientOrderId"]);
|
|
255
288
|
}
|
|
289
|
+
function orderReadParams(input) {
|
|
290
|
+
assertOrderLookupReference(input);
|
|
291
|
+
return optionalParams(input, ["orderId", "clientOrderId", "symbol"]);
|
|
292
|
+
}
|
|
256
293
|
function positionCloseBody(input) {
|
|
257
294
|
const body = {
|
|
258
295
|
sym: String(input.symbol)
|
|
@@ -377,6 +414,44 @@ function positionMatchesSymbol(item, symbol) {
|
|
|
377
414
|
}
|
|
378
415
|
return [item.sym, item.symbol, item.instrumentId, item.instrument].some((value) => value === symbol);
|
|
379
416
|
}
|
|
417
|
+
function extractOrderState(value) {
|
|
418
|
+
if (!isRecord(value)) {
|
|
419
|
+
return undefined;
|
|
420
|
+
}
|
|
421
|
+
const directState = value.orderState ?? value.state ?? value.status;
|
|
422
|
+
if (directState !== undefined && directState !== null && directState !== "") {
|
|
423
|
+
return String(directState);
|
|
424
|
+
}
|
|
425
|
+
if (isRecord(value.data)) {
|
|
426
|
+
const nestedState = extractOrderState(value.data);
|
|
427
|
+
if (nestedState) {
|
|
428
|
+
return nestedState;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (isRecord(value.order)) {
|
|
432
|
+
const nestedState = extractOrderState(value.order);
|
|
433
|
+
if (nestedState) {
|
|
434
|
+
return nestedState;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return undefined;
|
|
438
|
+
}
|
|
439
|
+
function normalizeCancelOrderState(state) {
|
|
440
|
+
if (!state) {
|
|
441
|
+
return undefined;
|
|
442
|
+
}
|
|
443
|
+
const normalized = state.trim().toUpperCase().replace(/[-\s]+/g, "_");
|
|
444
|
+
if (["CANCELLED", "CANCEL_COMPLETE", "CANCEL_SUCCESS", "CANCELED"].includes(normalized)) {
|
|
445
|
+
return "CANCELED";
|
|
446
|
+
}
|
|
447
|
+
if (["CANCEL_PENDING", "PENDING_CANCEL", "CANCELING"].includes(normalized)) {
|
|
448
|
+
return "CANCEL_PENDING";
|
|
449
|
+
}
|
|
450
|
+
return normalized;
|
|
451
|
+
}
|
|
452
|
+
function isCancelTerminalState(state) {
|
|
453
|
+
return state === "CANCELED" || state === "REJECTED" || state === "EXPIRED";
|
|
454
|
+
}
|
|
380
455
|
function compactObject(input) {
|
|
381
456
|
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined && value !== null && value !== ""));
|
|
382
457
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CORE_ERRORS, ProductError } from "../errors/product-error.js";
|
|
2
|
+
export function assertValidOrderId(orderId) {
|
|
3
|
+
if (orderId === undefined || orderId === null || orderId === "") {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
if (!/^\d{16}$/.test(String(orderId))) {
|
|
7
|
+
throw new ProductError({
|
|
8
|
+
code: CORE_ERRORS.INVALID_ORDER_ID,
|
|
9
|
+
status: "INVALID_INPUT",
|
|
10
|
+
message: "orderId must be 16 digits."
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function assertOrderLookupReference(input) {
|
|
15
|
+
assertValidOrderId(input.orderId);
|
|
16
|
+
if ((input.orderId === undefined || input.orderId === null || input.orderId === "")
|
|
17
|
+
&& (input.clientOrderId === undefined || input.clientOrderId === null || input.clientOrderId === "")) {
|
|
18
|
+
throw new ProductError({
|
|
19
|
+
code: "RCORE00001",
|
|
20
|
+
status: "INVALID_INPUT",
|
|
21
|
+
message: "orderId or clientOrderId is required."
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -65,12 +65,12 @@ export class RapidXClient {
|
|
|
65
65
|
await delay(this.readOnlyRetryDelayMs * (2 ** (attempt - 1)));
|
|
66
66
|
continue;
|
|
67
67
|
}
|
|
68
|
-
const hint = upstreamHttpErrorHint(path, response.status);
|
|
68
|
+
const hint = upstreamHttpErrorHint(path, response.status, text);
|
|
69
69
|
throw new ProductError({
|
|
70
|
-
code: upstreamHttpErrorCode(response.status),
|
|
71
|
-
status:
|
|
70
|
+
code: upstreamHttpErrorCode(path, response.status, text),
|
|
71
|
+
status: upstreamHttpErrorStatus(path, response.status, text),
|
|
72
72
|
message: hint ? `RapidX upstream error ${response.status}. ${hint}` : `RapidX upstream error ${response.status}`,
|
|
73
|
-
details: { status: response.status, body: text.slice(0, 500), attempts: attempt, ...(hint ? { hint } : {}) }
|
|
73
|
+
details: { upstreamStatus: response.status, status: response.status, body: text.slice(0, 500), attempts: attempt, ...(hint ? { hint } : {}) }
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
76
|
if (!text) {
|
|
@@ -111,13 +111,19 @@ function resolveBaseUrl(explicitBaseUrl) {
|
|
|
111
111
|
function firstNonEmpty(...values) {
|
|
112
112
|
return values.find((value) => value !== undefined && value.length > 0);
|
|
113
113
|
}
|
|
114
|
-
function upstreamHttpErrorHint(path, status) {
|
|
115
|
-
if (path
|
|
114
|
+
function upstreamHttpErrorHint(path, status, body) {
|
|
115
|
+
if (isAccountBalanceScopeError(path, status, body)) {
|
|
116
116
|
return "account balance mode=account requires account-level credentials; portfolio keys should use mode=portfolio.";
|
|
117
117
|
}
|
|
118
118
|
return undefined;
|
|
119
119
|
}
|
|
120
|
-
function upstreamHttpErrorCode(status) {
|
|
120
|
+
function upstreamHttpErrorCode(path, status, body) {
|
|
121
|
+
if (isAccountBalanceScopeError(path, status, body)) {
|
|
122
|
+
return CORE_ERRORS.PERMISSION_SCOPE_ERROR;
|
|
123
|
+
}
|
|
124
|
+
if (status === 404) {
|
|
125
|
+
return CORE_ERRORS.NOT_FOUND;
|
|
126
|
+
}
|
|
121
127
|
if (status === 429) {
|
|
122
128
|
return CORE_ERRORS.UPSTREAM_RATE_LIMITED;
|
|
123
129
|
}
|
|
@@ -126,6 +132,28 @@ function upstreamHttpErrorCode(status) {
|
|
|
126
132
|
}
|
|
127
133
|
return CORE_ERRORS.INVALID_CREDENTIAL;
|
|
128
134
|
}
|
|
135
|
+
function upstreamHttpErrorStatus(path, status, body) {
|
|
136
|
+
if (isAccountBalanceScopeError(path, status, body)) {
|
|
137
|
+
return "PERMISSION_SCOPE_ERROR";
|
|
138
|
+
}
|
|
139
|
+
if (status === 404) {
|
|
140
|
+
return "NOT_FOUND";
|
|
141
|
+
}
|
|
142
|
+
return "FAIL";
|
|
143
|
+
}
|
|
144
|
+
function isAccountBalanceScopeError(path, status, body) {
|
|
145
|
+
if (path !== "/api/v1/account/balance" || status < 400) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (status >= 500) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
const lowerBody = body.toLowerCase();
|
|
152
|
+
return lowerBody.includes("account-level")
|
|
153
|
+
|| lowerBody.includes("permission scope")
|
|
154
|
+
|| lowerBody.includes("credential scope")
|
|
155
|
+
|| (lowerBody.includes("portfolio") && lowerBody.includes("account"));
|
|
156
|
+
}
|
|
129
157
|
function delay(ms) {
|
|
130
158
|
if (ms <= 0) {
|
|
131
159
|
return Promise.resolve();
|
|
@@ -144,9 +172,10 @@ function assertRapidXBusinessSuccess(payload) {
|
|
|
144
172
|
const upstreamMessage = typeof body.message === "string" && body.message.length > 0
|
|
145
173
|
? `: ${body.message}`
|
|
146
174
|
: "";
|
|
175
|
+
const classification = classifyBusinessError(code, typeof body.message === "string" ? body.message : "");
|
|
147
176
|
throw new ProductError({
|
|
148
|
-
code:
|
|
149
|
-
status:
|
|
177
|
+
code: classification.code,
|
|
178
|
+
status: classification.status,
|
|
150
179
|
message: `RapidX upstream business error ${code}${upstreamMessage}`,
|
|
151
180
|
details: {
|
|
152
181
|
upstreamCode: code,
|
|
@@ -154,3 +183,10 @@ function assertRapidXBusinessSuccess(payload) {
|
|
|
154
183
|
}
|
|
155
184
|
});
|
|
156
185
|
}
|
|
186
|
+
function classifyBusinessError(upstreamCode, upstreamMessage) {
|
|
187
|
+
const lowerMessage = upstreamMessage.toLowerCase();
|
|
188
|
+
if (lowerMessage.includes("not found") || lowerMessage.includes("not exist") || lowerMessage.includes("does not exist")) {
|
|
189
|
+
return { code: CORE_ERRORS.NOT_FOUND, status: "NOT_FOUND" };
|
|
190
|
+
}
|
|
191
|
+
return { code: CORE_ERRORS.UPSTREAM_REJECTED, status: "BUSINESS_ERROR" };
|
|
192
|
+
}
|
|
@@ -10,18 +10,22 @@ export function parseRapidXSymbol(symbol) {
|
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
12
|
const exchange = exchangeRaw.toUpperCase();
|
|
13
|
-
const
|
|
13
|
+
const requestedType = typeRaw.toUpperCase();
|
|
14
|
+
const type = requestedType === "SWAP" && exchange === "OKX" ? "PERP" : requestedType;
|
|
14
15
|
if (type !== "PERP" && type !== "SPOT") {
|
|
15
16
|
throw new ProductError({
|
|
16
17
|
code: "RCORE12001",
|
|
17
18
|
status: "FAIL",
|
|
18
|
-
message: `Unsupported RapidX symbol type: ${typeRaw}
|
|
19
|
+
message: `Unsupported RapidX symbol type: ${typeRaw}. Use PERP or SPOT; for OKX perpetual swaps, use OKX_PERP_<BASE>_<QUOTE> or OKX_SWAP_<BASE>_<QUOTE> as an input alias.`
|
|
19
20
|
});
|
|
20
21
|
}
|
|
21
22
|
const base = baseRaw.toUpperCase();
|
|
22
23
|
const quote = quoteRaw.toUpperCase();
|
|
23
24
|
const isPerp = type === "PERP";
|
|
25
|
+
const canonicalSymbol = `${exchange}_${type}_${base}_${quote}`;
|
|
24
26
|
const parsed = {
|
|
27
|
+
inputSymbol: symbol,
|
|
28
|
+
canonicalSymbol,
|
|
25
29
|
exchange,
|
|
26
30
|
base,
|
|
27
31
|
quote,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SCHEMA_VERSION } from "./types.js";
|
|
2
|
+
import { inputSchemaForName } from "./input-schema.js";
|
|
2
3
|
export const CAPABILITIES = [
|
|
3
4
|
{ capabilityId: "config.get", cliCommand: "rapidx config get", operationType: "READ", riskLevel: "read", inputSchema: "ConfigGetInput", outputSchema: "ConfigResult", previewRequired: false },
|
|
4
5
|
{ capabilityId: "config.set", cliCommand: "rapidx config set", operationType: "DIAGNOSTIC", riskLevel: "write-config", inputSchema: "ConfigSetInput", outputSchema: "ConfigResult", previewRequired: false },
|
|
@@ -43,9 +44,11 @@ export const CAPABILITIES = [
|
|
|
43
44
|
{ capabilityId: "invocation.check", cliCommand: "rapidx invocation check", operationType: "DIAGNOSTIC", riskLevel: "read", inputSchema: "InvocationCheckInput", outputSchema: "InvocationCompliance", previewRequired: false }
|
|
44
45
|
];
|
|
45
46
|
export function getSchemaResult() {
|
|
47
|
+
const schemaNames = [...new Set(CAPABILITIES.map((capability) => capability.inputSchema))].sort();
|
|
46
48
|
return {
|
|
47
49
|
schemaVersion: SCHEMA_VERSION,
|
|
48
|
-
capabilities: [...CAPABILITIES]
|
|
50
|
+
capabilities: [...CAPABILITIES],
|
|
51
|
+
inputSchemas: Object.fromEntries(schemaNames.map((name) => [name, inputSchemaForName(name)]))
|
|
49
52
|
};
|
|
50
53
|
}
|
|
51
54
|
export function listMcpCapabilities() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SCHEMA_VERSION } from "./types.js";
|
|
2
2
|
import { RAPIDX_VERSION } from "../version.js";
|
|
3
3
|
export const RAPIDX_SKILLS_DISTRIBUTION = "github";
|
|
4
|
-
export const RAPIDX_SKILLS_VERSION = "1.0.
|
|
4
|
+
export const RAPIDX_SKILLS_VERSION = "1.0.4";
|
|
5
5
|
export const RAPIDX_SKILLS_SCHEMA_VERSION = "1.0.0";
|
|
6
6
|
export function buildCompatibilityReport(input = {}) {
|
|
7
7
|
const checks = [
|