@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.
@@ -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) {
@@ -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.delete("/api/v1/trading/order", orderCancelBody(input));
36
+ return executeOrderCancelCapability(client, input);
36
37
  case "order.get":
37
- return client.get("/api/v1/trading/order", optionalParams(input, ["orderId", "clientOrderId", "symbol"]));
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 symbol = stringValue(input.symbol ?? input.sym, "BINANCE_PERP_BTC_USDT");
74
- const parsed = parseRapidXSymbol(symbol);
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
- return fetchJson(fetchFn, `${base}/fapi/v1/openInterest`, { symbol: parsed.binanceSymbol });
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
- return { symbol, ...(data[0] ?? {}) };
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: "FAIL",
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 === "/api/v1/account/balance" && status >= 500) {
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: CORE_ERRORS.UPSTREAM_REJECTED,
149
- status: "FAIL",
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 type = typeRaw.toUpperCase();
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.2";
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 = [