@oomol-lab/open-connector 1.6.2 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/assets/open-connector/catalog/apps/assured_platform.json +218 -0
- package/assets/open-connector/catalog/apps/baixiao.json +1080 -0
- package/assets/open-connector/catalog/apps/benchmarkone.json +956 -0
- package/assets/open-connector/catalog/apps/bing_webmaster.json +3097 -0
- package/assets/open-connector/catalog/apps/buzzsprout.json +1979 -0
- package/assets/open-connector/catalog/apps/feishu.json +13 -19
- package/assets/open-connector/catalog/apps/feishu_app_bot.json +10 -17
- package/assets/open-connector/catalog/apps/linkfox.json +836 -0
- package/assets/open-connector/catalog/apps/metatraderapi.json +1077 -0
- package/assets/open-connector/catalog/apps/mktindex.json +2411 -0
- package/assets/open-connector/catalog/apps/mojo_helpdesk.json +660 -0
- package/assets/open-connector/catalog/apps/mumble.json +446 -0
- package/assets/open-connector/catalog/apps/notion.json +112 -3
- package/assets/open-connector/catalog/apps/omise.json +438 -0
- package/assets/open-connector/catalog/apps/panjiva.json +2858 -0
- package/assets/open-connector/catalog/apps/papertrail.json +449 -0
- package/assets/open-connector/catalog/apps/peec.json +747 -0
- package/assets/open-connector/catalog/apps/polygon_io.json +5156 -584
- package/assets/open-connector/catalog/apps/quant_data.json +1928 -0
- package/assets/open-connector/catalog/apps/riddle_quiz_maker.json +621 -0
- package/assets/open-connector/catalog/apps/slack.json +327 -4
- package/assets/open-connector/catalog/apps/slackbot.json +280 -3
- package/assets/open-connector/catalog/apps/templatedocs.json +438 -0
- package/assets/open-connector/catalog/apps/terminus_app.json +510 -0
- package/assets/open-connector/catalog/apps/ticket_tailor.json +584 -0
- package/assets/open-connector/catalog/apps/tongdaxin.json +2612 -79
- package/assets/open-connector/catalog/apps/veremark.json +514 -0
- package/assets/open-connector/catalog/apps/walletap.json +549 -0
- package/assets/open-connector/catalog/apps/xiaohongshu_store.json +955 -39
- package/assets/open-connector/catalog/apps-index.json +1 -1
- package/package.json +5 -1
- package/src/connection-service.d.ts +2 -0
- package/src/connection-service.js +8 -1
- package/src/core/provider-id.d.ts +5 -0
- package/src/core/provider-id.js +12 -0
- package/src/oauth/oauth-credential-refresh-service.js +2 -0
- package/src/oauth/oauth-flow-service.js +2 -0
- package/src/providers/action-contracts.generated.d.ts +27 -7
- package/src/providers/assured_platform/executors.d.ts +4 -0
- package/src/providers/assured_platform/executors.js +18 -0
- package/src/providers/assured_platform/runtime.d.ts +7 -0
- package/src/providers/assured_platform/runtime.js +108 -0
- package/src/providers/baixiao/actions.d.ts +2 -0
- package/src/providers/baixiao/actions.js +377 -0
- package/src/providers/baixiao/executors.d.ts +4 -0
- package/src/providers/baixiao/executors.js +106 -0
- package/src/providers/benchmarkone/executors.d.ts +4 -0
- package/src/providers/benchmarkone/executors.js +26 -0
- package/src/providers/benchmarkone/runtime.d.ts +12 -0
- package/src/providers/benchmarkone/runtime.js +113 -0
- package/src/providers/bing_webmaster/actions.d.ts +2 -0
- package/src/providers/bing_webmaster/actions.js +113 -0
- package/src/providers/bing_webmaster/executors.d.ts +4 -0
- package/src/providers/bing_webmaster/executors.js +104 -0
- package/src/providers/bing_webmaster/management-actions.d.ts +2 -0
- package/src/providers/bing_webmaster/management-actions.js +241 -0
- package/src/providers/bing_webmaster/reporting-actions.d.ts +2 -0
- package/src/providers/bing_webmaster/reporting-actions.js +300 -0
- package/src/providers/bing_webmaster/schemas.d.ts +33 -0
- package/src/providers/bing_webmaster/schemas.js +313 -0
- package/src/providers/buzzsprout/executors.d.ts +4 -0
- package/src/providers/buzzsprout/executors.js +21 -0
- package/src/providers/buzzsprout/runtime.d.ts +12 -0
- package/src/providers/buzzsprout/runtime.js +237 -0
- package/src/providers/feishu/shared/drive-actions.d.ts +1 -0
- package/src/providers/feishu/shared/drive-actions.js +13 -9
- package/src/providers/feishu/shared/drive-runtime.js +15 -3
- package/src/providers/feishu/shared/wiki-actions.js +4 -2
- package/src/providers/hithink_finance/runtime.js +39 -60
- package/src/providers/linkfox/actions.js +2 -0
- package/src/providers/linkfox/runtime.js +70 -1
- package/src/providers/linkfox/sif-actions.d.ts +10 -0
- package/src/providers/linkfox/sif-actions.js +193 -0
- package/src/providers/metatraderapi/executors.d.ts +4 -0
- package/src/providers/metatraderapi/executors.js +18 -0
- package/src/providers/metatraderapi/runtime.d.ts +7 -0
- package/src/providers/metatraderapi/runtime.js +216 -0
- package/src/providers/mktindex/actions-mmi.d.ts +2 -0
- package/src/providers/mktindex/actions-mmi.js +510 -0
- package/src/providers/mktindex/actions.d.ts +2 -0
- package/src/providers/mktindex/actions.js +2 -0
- package/src/providers/mktindex/executors.d.ts +4 -0
- package/src/providers/mktindex/executors.js +27 -0
- package/src/providers/mktindex/request.d.ts +17 -0
- package/src/providers/mktindex/request.js +48 -0
- package/src/providers/mktindex/runtime.d.ts +9 -0
- package/src/providers/mktindex/runtime.js +492 -0
- package/src/providers/mojo_helpdesk/executors.d.ts +4 -0
- package/src/providers/mojo_helpdesk/executors.js +18 -0
- package/src/providers/mojo_helpdesk/runtime.d.ts +7 -0
- package/src/providers/mojo_helpdesk/runtime.js +173 -0
- package/src/providers/mumble/executors.d.ts +4 -0
- package/src/providers/mumble/executors.js +99 -0
- package/src/providers/notion/executors.d.ts +9 -0
- package/src/providers/notion/executors.js +161 -6
- package/src/providers/omise/executors.d.ts +4 -0
- package/src/providers/omise/executors.js +120 -0
- package/src/providers/panjiva/actions.d.ts +14 -0
- package/src/providers/panjiva/actions.js +389 -0
- package/src/providers/panjiva/executors.d.ts +4 -0
- package/src/providers/panjiva/executors.js +136 -0
- package/src/providers/papertrail/executors.d.ts +4 -0
- package/src/providers/papertrail/executors.js +15 -0
- package/src/providers/papertrail/runtime.d.ts +7 -0
- package/src/providers/papertrail/runtime.js +167 -0
- package/src/providers/peec/executors.d.ts +4 -0
- package/src/providers/peec/executors.js +15 -0
- package/src/providers/peec/runtime.d.ts +7 -0
- package/src/providers/peec/runtime.js +128 -0
- package/src/providers/polygon_io/actions.d.ts +2 -0
- package/src/providers/polygon_io/actions.js +282 -0
- package/src/providers/polygon_io/expanded-actions.d.ts +2 -0
- package/src/providers/polygon_io/expanded-actions.js +375 -0
- package/src/providers/polygon_io/expanded-runtime.d.ts +16 -0
- package/src/providers/polygon_io/expanded-runtime.js +277 -0
- package/src/providers/polygon_io/runtime.js +21 -2
- package/src/providers/quant_data/executors.d.ts +4 -0
- package/src/providers/quant_data/executors.js +15 -0
- package/src/providers/quant_data/runtime.d.ts +7 -0
- package/src/providers/quant_data/runtime.js +118 -0
- package/src/providers/registry.generated.js +20 -0
- package/src/providers/riddle_quiz_maker/executors.d.ts +4 -0
- package/src/providers/riddle_quiz_maker/executors.js +15 -0
- package/src/providers/riddle_quiz_maker/runtime.d.ts +7 -0
- package/src/providers/riddle_quiz_maker/runtime.js +145 -0
- package/src/providers/slack/actions.js +106 -3
- package/src/providers/slack/runtime.js +260 -36
- package/src/providers/slackbot/actions.js +1 -1
- package/src/providers/templatedocs/executors.d.ts +4 -0
- package/src/providers/templatedocs/executors.js +18 -0
- package/src/providers/templatedocs/runtime.d.ts +13 -0
- package/src/providers/templatedocs/runtime.js +151 -0
- package/src/providers/terminus_app/executors.d.ts +4 -0
- package/src/providers/terminus_app/executors.js +15 -0
- package/src/providers/terminus_app/runtime.d.ts +7 -0
- package/src/providers/terminus_app/runtime.js +172 -0
- package/src/providers/ticket_tailor/executors.d.ts +4 -0
- package/src/providers/ticket_tailor/executors.js +15 -0
- package/src/providers/ticket_tailor/runtime.d.ts +7 -0
- package/src/providers/ticket_tailor/runtime.js +152 -0
- package/src/providers/tongdaxin/actions.d.ts +1 -0
- package/src/providers/tongdaxin/actions.js +6 -3
- package/src/providers/tongdaxin/executors.js +51 -10
- package/src/providers/tongdaxin/named-action-output.d.ts +1 -0
- package/src/providers/tongdaxin/named-action-output.js +149 -0
- package/src/providers/tongdaxin/named-action-routing.d.ts +9 -0
- package/src/providers/tongdaxin/named-action-routing.js +417 -0
- package/src/providers/tongdaxin/named-actions.d.ts +2 -0
- package/src/providers/tongdaxin/named-actions.js +378 -0
- package/src/providers/veremark/executors.d.ts +4 -0
- package/src/providers/veremark/executors.js +129 -0
- package/src/providers/walletap/executors.d.ts +4 -0
- package/src/providers/walletap/executors.js +15 -0
- package/src/providers/walletap/runtime.d.ts +7 -0
- package/src/providers/walletap/runtime.js +147 -0
- package/src/providers/weixin_official_account/access-token.js +31 -7
- package/src/providers/weixin_store/access-token.js +33 -7
- package/src/providers/xiaohongshu_store/executors.js +152 -3
- package/src/server/api/openapi.js +3 -0
- package/src/server/connector-assets.d.ts +4 -0
- package/src/server/connector-assets.js +12 -0
- package/src/server/connector-build.d.ts +33 -0
- package/src/server/connector-build.js +73 -0
- package/src/server/connector-runtime.d.ts +1 -2
- package/src/server/connector-runtime.js +2 -8
- package/src/server/server-assets.d.ts +0 -28
- package/src/server/server-assets.js +0 -50
|
@@ -6,6 +6,7 @@ import { chuhaijiangActions } from "./chuhaijiang-actions.js";
|
|
|
6
6
|
import { maidalvAction } from "./maidalv-actions.js";
|
|
7
7
|
import { mercadoActions } from "./mercado-actions.js";
|
|
8
8
|
import { seerfarActions } from "./seerfar-actions.js";
|
|
9
|
+
import { sifActions } from "./sif-actions.js";
|
|
9
10
|
import { zhihuiyaActions } from "./zhihuiya-actions.js";
|
|
10
11
|
const service = "linkfox";
|
|
11
12
|
const amazonDomains = [
|
|
@@ -974,6 +975,7 @@ const tiktokShopProductInput = s.object("TikTok Shop product detail parameters."
|
|
|
974
975
|
region: s.optional(s.stringEnum("The TikTok Shop region.", ["US", "GB", "ID", "MY", "TH", "VN", "PH", "SG", "DE", "FR", "IT", "ES"])),
|
|
975
976
|
});
|
|
976
977
|
export const linkfoxActions = [
|
|
978
|
+
...sifActions,
|
|
977
979
|
...mercadoActions,
|
|
978
980
|
...zhihuiyaActions,
|
|
979
981
|
...chuhaijiangActions,
|
|
@@ -4,6 +4,7 @@ import { chuhaijiangOperations } from "./chuhaijiang-actions.js";
|
|
|
4
4
|
import { normalizeChuhaijiang, searchChuhaijiangImage } from "./chuhaijiang-runtime.js";
|
|
5
5
|
import { mercadoOperations } from "./mercado-actions.js";
|
|
6
6
|
import { seerfarOperations } from "./seerfar-actions.js";
|
|
7
|
+
import { sifOperations } from "./sif-actions.js";
|
|
7
8
|
import { zhihuiyaOperations } from "./zhihuiya-actions.js";
|
|
8
9
|
import { validateZhihuiyaInput } from "./zhihuiya-runtime.js";
|
|
9
10
|
const zhihuiyaOperationsByName = new Map(zhihuiyaOperations.map(({ action, path }) => [action.name, { path }]));
|
|
@@ -32,6 +33,10 @@ const directOperations = {
|
|
|
32
33
|
normalize: (payload) => requiredResponseRecord(payload, "LinkFox Seerfar response"),
|
|
33
34
|
},
|
|
34
35
|
])),
|
|
36
|
+
...Object.fromEntries(sifOperations.map(({ action, path }) => [
|
|
37
|
+
action.name,
|
|
38
|
+
{ path, gatewayBusinessCodeOnly: true, normalize: normalizeSifResult },
|
|
39
|
+
])),
|
|
35
40
|
get_current_account: {
|
|
36
41
|
path: "/account/currentByAPI",
|
|
37
42
|
method: "GET",
|
|
@@ -299,10 +304,13 @@ export async function executeLinkfoxAction(actionName, input, apiKey, fetcher, p
|
|
|
299
304
|
}
|
|
300
305
|
const direct = directOperations[actionName];
|
|
301
306
|
if (direct) {
|
|
307
|
+
if (direct.gatewayBusinessCodeOnly)
|
|
308
|
+
validateSifInput(actionName, input);
|
|
302
309
|
const body = direct.buildBody ? direct.buildBody(input) : input;
|
|
303
310
|
const payload = await requestLinkfoxPayload(direct.path, {
|
|
304
311
|
method: direct.method ?? "POST",
|
|
305
312
|
timeoutMs: direct.timeoutMs,
|
|
313
|
+
gatewayBusinessCodeOnly: direct.gatewayBusinessCodeOnly,
|
|
306
314
|
...((direct.method ?? "POST") === "POST" ? { body } : {}),
|
|
307
315
|
}, apiKey, fetcher, "execute", false, parentSignal);
|
|
308
316
|
return direct.normalize(payload);
|
|
@@ -313,6 +321,32 @@ export async function executeLinkfoxAction(actionName, input, apiKey, fetcher, p
|
|
|
313
321
|
}
|
|
314
322
|
return handler(input, { apiKey, fetcher, signal: parentSignal });
|
|
315
323
|
}
|
|
324
|
+
function validateSifInput(actionName, input) {
|
|
325
|
+
if (actionName === "list_sif_asin_keywords") {
|
|
326
|
+
const periodType = optionalString(input.timePieceType) ?? "latelyDay";
|
|
327
|
+
const periodValue = optionalString(input.timePieceValue);
|
|
328
|
+
if ((periodType === "month" || periodType === "week") && !periodValue) {
|
|
329
|
+
throw new ProviderRequestError(400, "timePieceValue is required for month or week periods");
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const startDate = optionalString(input.startDate);
|
|
333
|
+
const endDate = optionalString(input.endDate);
|
|
334
|
+
if ((startDate && !endDate) || (!startDate && endDate)) {
|
|
335
|
+
throw new ProviderRequestError(400, "startDate and endDate must be provided together");
|
|
336
|
+
}
|
|
337
|
+
if ((startDate || endDate) && input.last7d !== false) {
|
|
338
|
+
throw new ProviderRequestError(400, "last7d must be false when a date range is provided");
|
|
339
|
+
}
|
|
340
|
+
if (startDate && endDate && startDate > endDate) {
|
|
341
|
+
throw new ProviderRequestError(400, "startDate must not be later than endDate");
|
|
342
|
+
}
|
|
343
|
+
if (actionName === "get_sif_asin_traffic_summary") {
|
|
344
|
+
const asins = optionalString(input.asins);
|
|
345
|
+
if (asins && asins.split(",").length > 10) {
|
|
346
|
+
throw new ProviderRequestError(400, "asins must contain at most 10 comma-separated ASINs");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
316
350
|
function validateLinkfoxActionInput(actionName, input) {
|
|
317
351
|
if (actionName === "search_amazon_by_image" && input.deliveryZip && input.countryOrAreaCode) {
|
|
318
352
|
throw new ProviderRequestError(400, "deliveryZip and countryOrAreaCode cannot be used together");
|
|
@@ -422,7 +456,9 @@ async function requestLinkfoxPayload(path, options, apiKey, fetcher, phase = "ex
|
|
|
422
456
|
signal,
|
|
423
457
|
});
|
|
424
458
|
const payload = await readJsonPayload(response);
|
|
425
|
-
const businessCode =
|
|
459
|
+
const businessCode = options.gatewayBusinessCodeOnly
|
|
460
|
+
? readGatewayBusinessCode(payload)
|
|
461
|
+
: readBusinessCode(payload);
|
|
426
462
|
if (acceptCreditError && businessCode === 402)
|
|
427
463
|
return payload;
|
|
428
464
|
if (!response.ok || (businessCode !== undefined && businessCode !== 200)) {
|
|
@@ -482,6 +518,17 @@ function readBusinessCode(payload) {
|
|
|
482
518
|
}
|
|
483
519
|
return undefined;
|
|
484
520
|
}
|
|
521
|
+
function readGatewayBusinessCode(payload) {
|
|
522
|
+
const record = optionalRecord(payload);
|
|
523
|
+
for (const key of ["errcode", "errorCode"]) {
|
|
524
|
+
const value = record?.[key];
|
|
525
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
526
|
+
return value;
|
|
527
|
+
if (typeof value === "string" && value.trim() && Number.isFinite(Number(value)))
|
|
528
|
+
return Number(value);
|
|
529
|
+
}
|
|
530
|
+
return undefined;
|
|
531
|
+
}
|
|
485
532
|
function readErrorMessage(payload) {
|
|
486
533
|
if (typeof payload === "string" && payload.trim()) {
|
|
487
534
|
return payload.trim();
|
|
@@ -522,6 +569,28 @@ function normalizeDataList(payload) {
|
|
|
522
569
|
costToken: readNumber(record.costToken),
|
|
523
570
|
};
|
|
524
571
|
}
|
|
572
|
+
function normalizeSifResult(payload) {
|
|
573
|
+
const record = requireRecord(payload, "LinkFox returned an invalid SIF response");
|
|
574
|
+
const code = typeof record.code === "string" || typeof record.code === "number" ? String(record.code) : undefined;
|
|
575
|
+
if (code !== "1") {
|
|
576
|
+
throw new ProviderRequestError(502, readString(record.msg) ?? "LinkFox SIF returned an unsuccessful business response", record);
|
|
577
|
+
}
|
|
578
|
+
const data = readArray(record.data);
|
|
579
|
+
if (!data)
|
|
580
|
+
throw new ProviderRequestError(502, "LinkFox SIF response did not include data", record);
|
|
581
|
+
return {
|
|
582
|
+
...record,
|
|
583
|
+
code,
|
|
584
|
+
msg: readString(record.msg) ?? null,
|
|
585
|
+
total: readInteger(record.total) ?? data.length,
|
|
586
|
+
data,
|
|
587
|
+
columns: readArray(record.columns) ?? [],
|
|
588
|
+
type: readString(record.type) ?? null,
|
|
589
|
+
title: readString(record.title) ?? null,
|
|
590
|
+
costTime: readInteger(record.costTime),
|
|
591
|
+
costToken: readNumber(record.costToken),
|
|
592
|
+
};
|
|
593
|
+
}
|
|
525
594
|
function normalizeVideoList(payload) {
|
|
526
595
|
const record = requireRecord(payload, "LinkFox returned an invalid video response");
|
|
527
596
|
const videos = readArray(record.videos) ?? readArray(record.data) ?? [];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ProviderActionDefinition } from "../../core/provider-definition.ts";
|
|
2
|
+
import type { ActionDefinition } from "../../core/types.ts";
|
|
3
|
+
interface SifOperation {
|
|
4
|
+
path: string;
|
|
5
|
+
usage: number;
|
|
6
|
+
action: ProviderActionDefinition;
|
|
7
|
+
}
|
|
8
|
+
export declare const sifOperations: readonly SifOperation[];
|
|
9
|
+
export declare const sifActions: readonly ActionDefinition[];
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { jsonSchema as s } from "../../core/json-schema.js";
|
|
2
|
+
import { defineProviderAction } from "../../core/provider-definition.js";
|
|
3
|
+
const sifCountries = ["US", "UK", "DE", "CA", "JP", "FR", "ES", "IT", "MX", "AU", "AE", "BR", "SA"];
|
|
4
|
+
const summarySortFields = [
|
|
5
|
+
"totalKeywordNum",
|
|
6
|
+
"naturalKeywordNum",
|
|
7
|
+
"brandKeywordNum",
|
|
8
|
+
"vedioKeywordNum",
|
|
9
|
+
"acKeywordNum",
|
|
10
|
+
"erKeywordNum",
|
|
11
|
+
"trKeywordNum",
|
|
12
|
+
"sumScore",
|
|
13
|
+
"totalNfScore",
|
|
14
|
+
"totalSpSocre",
|
|
15
|
+
"totalBrandScore",
|
|
16
|
+
"totalVedioScore",
|
|
17
|
+
"totalAcScore",
|
|
18
|
+
"totalTrScore",
|
|
19
|
+
"totalErScore",
|
|
20
|
+
];
|
|
21
|
+
const keywordSummaryConditions = [
|
|
22
|
+
"nfPosition",
|
|
23
|
+
"isSpAd",
|
|
24
|
+
"isVedioAd",
|
|
25
|
+
"isBrandAd",
|
|
26
|
+
"isPPCAd",
|
|
27
|
+
"isSearchRecommend",
|
|
28
|
+
"acAd",
|
|
29
|
+
"totalPeriod.in",
|
|
30
|
+
"nfKeywordCnt.total",
|
|
31
|
+
"nfKeywordCnt.in",
|
|
32
|
+
"adKeywordCnt.total",
|
|
33
|
+
"adKeywordCnt.in",
|
|
34
|
+
"allSpKeywordCnt.total",
|
|
35
|
+
"allSpKeywordCnt.in",
|
|
36
|
+
"spKeywordCnt.total",
|
|
37
|
+
"spKeywordCnt.in",
|
|
38
|
+
"recSpKeywordCnt.total",
|
|
39
|
+
"recSpKeywordCnt.in",
|
|
40
|
+
"allSbKeywordCnt.total",
|
|
41
|
+
"allSbKeywordCnt.in",
|
|
42
|
+
"sbKeywordCnt.total",
|
|
43
|
+
"sbKeywordCnt.in",
|
|
44
|
+
"sbvKeywordCnt.total",
|
|
45
|
+
"sbvKeywordCnt.in",
|
|
46
|
+
];
|
|
47
|
+
const _asinKeywordConditionValues = [
|
|
48
|
+
"nfPosition",
|
|
49
|
+
"isSpAd",
|
|
50
|
+
"isBrandAd",
|
|
51
|
+
"isVedioAd",
|
|
52
|
+
"isAC",
|
|
53
|
+
"isAccurateKw",
|
|
54
|
+
"isAccurateTailKw",
|
|
55
|
+
"isPurchaseKw",
|
|
56
|
+
"isQualityKw",
|
|
57
|
+
"isStableKw",
|
|
58
|
+
"isLossKw",
|
|
59
|
+
"isInvalidKw",
|
|
60
|
+
"isMultiVariantKw",
|
|
61
|
+
"isSearchVolUpKw",
|
|
62
|
+
"isSearchVolDownKw",
|
|
63
|
+
"totalPeriod.in",
|
|
64
|
+
"nfKeywordCnt.total",
|
|
65
|
+
"nfKeywordCnt.in",
|
|
66
|
+
"adKeywordCnt.total",
|
|
67
|
+
"adKeywordCnt.in",
|
|
68
|
+
"allSpKeywordCnt.total",
|
|
69
|
+
"allSpKeywordCnt.in",
|
|
70
|
+
"spKeywordCnt.total",
|
|
71
|
+
"spKeywordCnt.in",
|
|
72
|
+
"recSpKeywordCnt.total",
|
|
73
|
+
"recSpKeywordCnt.in",
|
|
74
|
+
"allSbKeywordCnt.total",
|
|
75
|
+
"allSbKeywordCnt.in",
|
|
76
|
+
"sbKeywordCnt.total",
|
|
77
|
+
"sbKeywordCnt.in",
|
|
78
|
+
"sbvKeywordCnt.total",
|
|
79
|
+
"sbvKeywordCnt.in",
|
|
80
|
+
];
|
|
81
|
+
const _asinSummaryConditionValues = ["nf", "sp", "sb", "sbv", "ad", "acAd", "totalPeriod.in"];
|
|
82
|
+
const countrySchema = s.optional(s.stringEnum("The Amazon marketplace code. LinkFox defaults to US when omitted.", sifCountries));
|
|
83
|
+
const nullableInteger = (description) => s.nullable(s.integer(description));
|
|
84
|
+
const nullableNumber = (description) => s.nullable(s.number(description));
|
|
85
|
+
const nullableString = (description) => s.nullable(s.string(description));
|
|
86
|
+
function resultSchema(description, recordDescription, extra = {}) {
|
|
87
|
+
return s.looseRequiredObject(description, {
|
|
88
|
+
code: s.string("The SIF business code; 1 indicates success."),
|
|
89
|
+
msg: nullableString("The SIF response message."),
|
|
90
|
+
total: s.integer("The number of records returned by this request.", { minimum: 0 }),
|
|
91
|
+
data: s.array("The SIF records returned by this request.", s.looseObject(recordDescription)),
|
|
92
|
+
columns: s.array("The LinkFox rendering-column definitions.", s.looseObject("A provider-defined rendering-column definition.")),
|
|
93
|
+
type: nullableString("The LinkFox rendering style when provided."),
|
|
94
|
+
title: nullableString("The result title when provided."),
|
|
95
|
+
costTime: nullableInteger("The upstream processing time in milliseconds."),
|
|
96
|
+
costToken: nullableNumber("The LinkFox token cost when provided."),
|
|
97
|
+
...extra,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const asinKeywordsInputBase = s.object("Parameters for reverse-searching one ASIN's SIF keywords.", {
|
|
101
|
+
asin: s.nonEmptyString("The single Amazon ASIN to analyze.", { maxLength: 1000 }),
|
|
102
|
+
country: countrySchema,
|
|
103
|
+
keyword: s.optional(s.string("An optional localized keyword used to filter the result.", { maxLength: 1000 })),
|
|
104
|
+
timePieceType: s.optional(s.stringEnum("The analysis period type. LinkFox defaults to latelyDay.", ["latelyDay", "month", "week"])),
|
|
105
|
+
timePieceValue: s.optional(s.string("The period value: 7 or 30 for latelyDay, YYYY-MM for month, or the week-start date in YYYY-MM-DD format.", { maxLength: 1000 })),
|
|
106
|
+
conditions: s.optional(s.string("Comma-separated SIF keyword filters, including traffic-position, advertising, conversion, stability, and period-entry markers.", { maxLength: 1000 })),
|
|
107
|
+
sortBy: s.optional(s.stringEnum("The keyword sort field. Omit it to use SIF's system order.", [
|
|
108
|
+
"lastRank",
|
|
109
|
+
"adLastRank",
|
|
110
|
+
"updateTime",
|
|
111
|
+
"searchesRank",
|
|
112
|
+
"estSearchesNum",
|
|
113
|
+
])),
|
|
114
|
+
desc: s.optional(s.boolean("Whether to sort descending. LinkFox defaults to true.")),
|
|
115
|
+
pageNum: s.optional(s.integer("The one-based result page. LinkFox defaults to 1.", { minimum: 1 })),
|
|
116
|
+
pageSize: s.optional(s.integer("The number of keyword records per page. LinkFox defaults to 100.", {
|
|
117
|
+
minimum: 10,
|
|
118
|
+
maximum: 100,
|
|
119
|
+
})),
|
|
120
|
+
});
|
|
121
|
+
const asinKeywordsInput = asinKeywordsInputBase;
|
|
122
|
+
const dateRangeFields = {
|
|
123
|
+
last7d: s.optional(s.boolean("Whether to use the latest seven days. Set false to use startDate and endDate; LinkFox defaults to true.")),
|
|
124
|
+
startDate: s.optional(s.date("The period start date, used when last7d is false.")),
|
|
125
|
+
endDate: s.optional(s.date("The period end date, paired with startDate.")),
|
|
126
|
+
};
|
|
127
|
+
const asinSummaryInputBase = s.object("Parameters for summarizing SIF traffic sources by ASIN.", {
|
|
128
|
+
searchValue: s.nonEmptyString("One to ten comma-separated Amazon ASINs.", { maxLength: 1000 }),
|
|
129
|
+
country: countrySchema,
|
|
130
|
+
...dateRangeFields,
|
|
131
|
+
conditions: s.optional(s.string("Comma-separated SIF filters: nf, sp, sb, sbv, ad, acAd, or totalPeriod.in.", {
|
|
132
|
+
maxLength: 1000,
|
|
133
|
+
})),
|
|
134
|
+
sortBy: s.optional(s.stringEnum("The ASIN traffic-summary sort field.", summarySortFields)),
|
|
135
|
+
pageNum: s.optional(s.integer("The one-based result page. LinkFox defaults to 1.", { minimum: 1 })),
|
|
136
|
+
pageSize: s.optional(s.integer("The number of ASIN summaries per page. LinkFox defaults to 10000.", {
|
|
137
|
+
minimum: 10,
|
|
138
|
+
maximum: 10_000,
|
|
139
|
+
})),
|
|
140
|
+
desc: s.optional(s.boolean("Whether to sort descending. LinkFox defaults to true.")),
|
|
141
|
+
});
|
|
142
|
+
const asinSummaryInput = asinSummaryInputBase;
|
|
143
|
+
const keywordOverviewInputBase = s.object("Parameters for retrieving a SIF keyword overview.", {
|
|
144
|
+
keyword: s.nonEmptyString("The localized Amazon keyword to analyze.", { maxLength: 1000 }),
|
|
145
|
+
country: countrySchema,
|
|
146
|
+
...dateRangeFields,
|
|
147
|
+
});
|
|
148
|
+
const keywordOverviewInput = keywordOverviewInputBase;
|
|
149
|
+
const keywordSummaryInputBase = s.object("Parameters for summarizing competitor traffic sources for one keyword.", {
|
|
150
|
+
searchKeyword: s.nonEmptyString("The localized Amazon keyword to analyze.", {
|
|
151
|
+
maxLength: 1000,
|
|
152
|
+
}),
|
|
153
|
+
country: countrySchema,
|
|
154
|
+
asins: s.optional(s.string("An optional comma-separated ASIN filter.", { maxLength: 1000 })),
|
|
155
|
+
condition: s.optional(s.stringEnum("One SIF traffic or period filter, such as nfPosition, isSpAd, isBrandAd, isPPCAd, acAd, or totalPeriod.in.", keywordSummaryConditions)),
|
|
156
|
+
...dateRangeFields,
|
|
157
|
+
sortBy: s.optional(s.stringEnum("The competitor traffic-summary sort field.", summarySortFields)),
|
|
158
|
+
pageNum: s.optional(s.integer("The one-based result page. LinkFox defaults to 1.", { minimum: 1 })),
|
|
159
|
+
pageSize: s.optional(s.integer("The number of competitor ASINs per page. LinkFox defaults to 100.", {
|
|
160
|
+
minimum: 10,
|
|
161
|
+
maximum: 100,
|
|
162
|
+
})),
|
|
163
|
+
desc: s.optional(s.boolean("Whether to sort descending. LinkFox defaults to true.")),
|
|
164
|
+
});
|
|
165
|
+
const keywordSummaryInput = keywordSummaryInputBase;
|
|
166
|
+
function operation(name, path, description, inputSchema, outputSchema) {
|
|
167
|
+
return {
|
|
168
|
+
path,
|
|
169
|
+
usage: 9,
|
|
170
|
+
action: defineProviderAction("linkfox", {
|
|
171
|
+
name,
|
|
172
|
+
description,
|
|
173
|
+
operationType: "read",
|
|
174
|
+
inputSchema,
|
|
175
|
+
outputSchema,
|
|
176
|
+
}),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
export const sifOperations = [
|
|
180
|
+
operation("list_sif_asin_keywords", "/sif/asinKeywords", "Reverse-search one Amazon ASIN's SIF traffic keywords, ranks, traffic shares, and conversion markers through LinkFox.", asinKeywordsInput, resultSchema("SIF keyword intelligence for one Amazon ASIN.", "A SIF keyword record with ranks, search volume, traffic shares, exposure scores, placements, and conversion markers.", {
|
|
181
|
+
isParentAsin: s.optional(s.boolean("Whether the queried ASIN is a parent ASIN.")),
|
|
182
|
+
hasVaiants: s.optional(s.boolean("Whether the queried product has variants.")),
|
|
183
|
+
abaCreateDateWeek: nullableString("The latest week represented by the ABA data."),
|
|
184
|
+
})),
|
|
185
|
+
operation("get_sif_asin_traffic_summary", "/sif/asinSummary", "Summarize natural, advertising, and recommendation traffic sources for up to ten Amazon ASINs through LinkFox and SIF.", asinSummaryInput, resultSchema("SIF traffic-source summaries for Amazon ASINs.", "A SIF ASIN summary with product context, current and previous exposure scores, traffic shares, keyword counts, entry or exit counts, and source markers.", {
|
|
186
|
+
isParentAsin: s.optional(s.boolean("Whether the search target is a parent ASIN.")),
|
|
187
|
+
variantsNum: nullableInteger("The number of variants with traffic keywords."),
|
|
188
|
+
noKeywordVariantsNum: nullableInteger("The number of variants without traffic keywords."),
|
|
189
|
+
})),
|
|
190
|
+
operation("get_sif_keyword_overview", "/sif/keywordOverview", "Get SIF search demand, supply-demand ratio, and advertising or recommendation competition for one Amazon keyword through LinkFox.", keywordOverviewInput, resultSchema("A SIF market-competition overview for one Amazon keyword.", "A SIF keyword overview with search volume, supply-demand ratio, product counts by traffic placement, tracked ASIN totals, and data-period timestamps.")),
|
|
191
|
+
operation("get_sif_keyword_traffic_summary", "/sif/keywordSummary", "Analyze competitor ASIN traffic shares and natural, advertising, or recommendation exposure for one Amazon keyword through LinkFox and SIF.", keywordSummaryInput, resultSchema("SIF competitor traffic-source summaries for one Amazon keyword.", "A SIF competitor record with product context, overall and queried-keyword exposure scores, traffic shares, and source markers.")),
|
|
192
|
+
];
|
|
193
|
+
export const sifActions = sifOperations.map(({ action }) => action);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CredentialValidators, ProviderExecutors, ProviderProxyExecutor } from "../../core/types.ts";
|
|
2
|
+
export declare const executors: ProviderExecutors;
|
|
3
|
+
export declare const proxy: ProviderProxyExecutor;
|
|
4
|
+
export declare const credentialValidators: CredentialValidators;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineApiKeyProviderExecutors, defineProviderProxy } from "../provider-runtime.js";
|
|
2
|
+
import { metatraderapiActionHandlers, metatraderapiApiBaseUrl, validateMetaTraderApiCredential } from "./runtime.js";
|
|
3
|
+
const service = "metatraderapi";
|
|
4
|
+
export const executors = defineApiKeyProviderExecutors(service, metatraderapiActionHandlers);
|
|
5
|
+
export const proxy = defineProviderProxy({
|
|
6
|
+
service,
|
|
7
|
+
baseUrl: metatraderapiApiBaseUrl,
|
|
8
|
+
auth: { type: "api_key_authorization", prefix: "Bearer " },
|
|
9
|
+
skipDnsValidation: true,
|
|
10
|
+
customizeRequest({ headers }) {
|
|
11
|
+
headers.set("accept", "application/json");
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
export const credentialValidators = {
|
|
15
|
+
apiKey(input, { fetcher, signal }) {
|
|
16
|
+
return validateMetaTraderApiCredential(input.apiKey, fetcher, signal);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CredentialValidationResult } from "../../core/types.ts";
|
|
2
|
+
import type { ApiKeyProviderContext, ProviderActionHandlers, ProviderFetch } from "../provider-runtime.ts";
|
|
3
|
+
export declare const metatraderapiApiBaseUrl = "https://metatraderapi.cloud/api/";
|
|
4
|
+
type MetaTraderHandler = (input: Record<string, unknown>, context: ApiKeyProviderContext) => Promise<unknown>;
|
|
5
|
+
export declare const metatraderapiActionHandlers: ProviderActionHandlers<"metatraderapi", MetaTraderHandler>;
|
|
6
|
+
export declare function validateMetaTraderApiCredential(apiKey: string, fetcher: ProviderFetch, signal?: AbortSignal): Promise<CredentialValidationResult>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { compactObject, optionalInteger, optionalRecord, optionalString, recordOrEmpty } from "../../core/cast.js";
|
|
2
|
+
import { providerInputError, ProviderRequestError, providerResponseError, providerUserAgent, readProviderTextBody, requiredInputString, requiredResponseRecord, runProviderRequest, } from "../provider-runtime.js";
|
|
3
|
+
export const metatraderapiApiBaseUrl = "https://metatraderapi.cloud/api/";
|
|
4
|
+
const maxResponseBytes = 16 * 1024 * 1024;
|
|
5
|
+
export const metatraderapiActionHandlers = {
|
|
6
|
+
async list_sessions(_input, context) {
|
|
7
|
+
const payload = await requestMetaTrader({ ...context, path: "sessions/", phase: "execute" });
|
|
8
|
+
return { sessions: readArrayPayload(payload, "sessions", "MetaTraderAPI sessions response") };
|
|
9
|
+
},
|
|
10
|
+
async start_session(input, context) {
|
|
11
|
+
const payload = await requestMetaTrader({
|
|
12
|
+
...context,
|
|
13
|
+
path: "sessions/start/",
|
|
14
|
+
method: "POST",
|
|
15
|
+
phase: "execute",
|
|
16
|
+
body: {
|
|
17
|
+
account_id: requiredInputString(input.accountId, "accountId"),
|
|
18
|
+
password: requirePassword(input.password),
|
|
19
|
+
server: requiredInputString(input.server, "server"),
|
|
20
|
+
platform: requiredInputString(input.platform, "platform"),
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
return { session: requireMutationObject(payload, "MetaTraderAPI start session") };
|
|
24
|
+
},
|
|
25
|
+
async stop_session(input, context) {
|
|
26
|
+
return { result: recordOrEmpty(await requestSession(input, context, "stop/", "POST")) };
|
|
27
|
+
},
|
|
28
|
+
async close_all_sessions(_input, context) {
|
|
29
|
+
return {
|
|
30
|
+
result: recordOrEmpty(await requestMetaTrader({
|
|
31
|
+
...context,
|
|
32
|
+
path: "sessions/close-all/",
|
|
33
|
+
method: "POST",
|
|
34
|
+
phase: "execute",
|
|
35
|
+
})),
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
async get_account_info(input, context) {
|
|
39
|
+
return {
|
|
40
|
+
accountInfo: requiredResponseRecord(await requestSession(input, context, "account-info/"), "MetaTraderAPI account information response"),
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
async get_equity(input, context) {
|
|
44
|
+
return {
|
|
45
|
+
equity: requiredResponseRecord(await requestSession(input, context, "equity/"), "MetaTraderAPI equity response"),
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
async list_deals(input, context) {
|
|
49
|
+
const payload = await requestHistory(input, context, "history/deals/");
|
|
50
|
+
return { deals: readArrayPayload(payload, "deals", "MetaTraderAPI deals response") };
|
|
51
|
+
},
|
|
52
|
+
async list_history_orders(input, context) {
|
|
53
|
+
const payload = await requestHistory(input, context, "history/orders/");
|
|
54
|
+
return { orders: readArrayPayload(payload, "orders", "MetaTraderAPI order history response") };
|
|
55
|
+
},
|
|
56
|
+
async get_margin(input, context) {
|
|
57
|
+
return {
|
|
58
|
+
margin: requiredResponseRecord(await requestSession(input, context, "margin/"), "MetaTraderAPI margin response"),
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
async list_orders(input, context) {
|
|
62
|
+
const payload = await requestSession(input, context, "orders/");
|
|
63
|
+
return { orders: readArrayPayload(payload, "orders", "MetaTraderAPI pending orders response") };
|
|
64
|
+
},
|
|
65
|
+
async ping_session(input, context) {
|
|
66
|
+
return {
|
|
67
|
+
ping: requiredResponseRecord(await requestSession(input, context, "ping/"), "MetaTraderAPI ping response"),
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
async list_positions(input, context) {
|
|
71
|
+
const payload = await requestSession(input, context, "positions/");
|
|
72
|
+
return { positions: readArrayPayload(payload, "positions", "MetaTraderAPI positions response") };
|
|
73
|
+
},
|
|
74
|
+
async get_session_status(input, context) {
|
|
75
|
+
return {
|
|
76
|
+
status: requiredResponseRecord(await requestSession(input, context, "status/"), "MetaTraderAPI session status response"),
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
async list_symbols(input, context) {
|
|
80
|
+
const symbols = readArrayPayload(await requestSession(input, context, "symbols/"), "symbols", "MetaTraderAPI symbols response");
|
|
81
|
+
if (!symbols.every((value) => typeof value === "string")) {
|
|
82
|
+
throw providerResponseError("MetaTraderAPI symbols response must contain only strings");
|
|
83
|
+
}
|
|
84
|
+
return { symbols };
|
|
85
|
+
},
|
|
86
|
+
async get_symbol(input, context) {
|
|
87
|
+
const symbol = requiredInputString(input.symbol, "symbol");
|
|
88
|
+
return {
|
|
89
|
+
symbol: requiredResponseRecord(await requestSession(input, context, `symbols/${encodeURIComponent(symbol)}/`), "MetaTraderAPI symbol response"),
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
export async function validateMetaTraderApiCredential(apiKey, fetcher, signal) {
|
|
94
|
+
const payload = await requestMetaTrader({ apiKey, fetcher, signal, path: "sessions/", phase: "validate" });
|
|
95
|
+
const sessions = readArrayPayload(payload, "sessions", "MetaTraderAPI sessions response");
|
|
96
|
+
return {
|
|
97
|
+
profile: { displayName: "MetaTraderAPI API Key" },
|
|
98
|
+
grantedScopes: [],
|
|
99
|
+
metadata: {
|
|
100
|
+
apiBaseUrl: metatraderapiApiBaseUrl.slice(0, -1),
|
|
101
|
+
validationEndpoint: "/sessions/",
|
|
102
|
+
activeSessionCount: sessions.length,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function requestSession(input, context, suffix, method = "GET") {
|
|
107
|
+
const sessionId = requiredInputString(input.sessionId, "sessionId");
|
|
108
|
+
return requestMetaTrader({
|
|
109
|
+
...context,
|
|
110
|
+
path: `sessions/${encodeURIComponent(sessionId)}/${suffix}`,
|
|
111
|
+
method,
|
|
112
|
+
phase: "execute",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async function requestHistory(input, context, suffix) {
|
|
116
|
+
const sessionId = requiredInputString(input.sessionId, "sessionId");
|
|
117
|
+
return requestMetaTrader({
|
|
118
|
+
...context,
|
|
119
|
+
path: `sessions/${encodeURIComponent(sessionId)}/${suffix}`,
|
|
120
|
+
query: compactObject({
|
|
121
|
+
date_from: optionalString(input.dateFrom),
|
|
122
|
+
date_to: optionalString(input.dateTo),
|
|
123
|
+
days_back: optionalInteger(input.daysBack),
|
|
124
|
+
}),
|
|
125
|
+
phase: "execute",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async function requestMetaTrader(input) {
|
|
129
|
+
try {
|
|
130
|
+
return await runProviderRequest({ label: "MetaTraderAPI", signal: input.signal }, async (signal) => {
|
|
131
|
+
const url = new URL(input.path, metatraderapiApiBaseUrl);
|
|
132
|
+
for (const [key, value] of Object.entries(input.query ?? {})) {
|
|
133
|
+
if (value !== undefined)
|
|
134
|
+
url.searchParams.set(key, String(value));
|
|
135
|
+
}
|
|
136
|
+
const headers = new Headers({
|
|
137
|
+
accept: "application/json",
|
|
138
|
+
authorization: `Bearer ${input.apiKey}`,
|
|
139
|
+
"user-agent": providerUserAgent,
|
|
140
|
+
});
|
|
141
|
+
if (input.body !== undefined)
|
|
142
|
+
headers.set("content-type", "application/json");
|
|
143
|
+
const response = await input.fetcher(url, {
|
|
144
|
+
method: input.method ?? "GET",
|
|
145
|
+
headers,
|
|
146
|
+
body: input.body === undefined ? undefined : JSON.stringify(input.body),
|
|
147
|
+
signal,
|
|
148
|
+
});
|
|
149
|
+
const payload = await readPayload(response);
|
|
150
|
+
if (!response.ok)
|
|
151
|
+
throw createMetaTraderError(response.status, payload, input.phase);
|
|
152
|
+
return payload;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (input.phase === "execute" &&
|
|
157
|
+
input.method === "POST" &&
|
|
158
|
+
error instanceof ProviderRequestError &&
|
|
159
|
+
error.status >= 500) {
|
|
160
|
+
throw new ProviderRequestError(error.status, `${error.message}; the operation may have completed upstream. Check the current session or trading state before retrying.`, { retryable: false, upstreamCompleted: "unknown" });
|
|
161
|
+
}
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function readPayload(response) {
|
|
166
|
+
const text = await readProviderTextBody(response, "MetaTraderAPI response", maxResponseBytes);
|
|
167
|
+
if (text.trim() === "")
|
|
168
|
+
return null;
|
|
169
|
+
try {
|
|
170
|
+
return JSON.parse(text);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (!response.ok)
|
|
174
|
+
return text.trim();
|
|
175
|
+
throw new ProviderRequestError(502, "MetaTraderAPI returned invalid JSON", error);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function createMetaTraderError(status, payload, phase) {
|
|
179
|
+
const message = errorMessage(payload) ?? `MetaTraderAPI request failed with status ${status}`;
|
|
180
|
+
if (phase === "validate" && status >= 400 && status < 500) {
|
|
181
|
+
return providerInputError(message);
|
|
182
|
+
}
|
|
183
|
+
if (status === 400 || status === 404)
|
|
184
|
+
return providerInputError(message);
|
|
185
|
+
return new ProviderRequestError(status || 502, message);
|
|
186
|
+
}
|
|
187
|
+
function errorMessage(payload) {
|
|
188
|
+
if (typeof payload === "string" && payload.trim())
|
|
189
|
+
return payload.trim();
|
|
190
|
+
const record = optionalRecord(payload);
|
|
191
|
+
for (const key of ["detail", "message", "error"]) {
|
|
192
|
+
const value = optionalString(record?.[key])?.trim();
|
|
193
|
+
if (value)
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
function readArrayPayload(payload, field, label) {
|
|
199
|
+
if (Array.isArray(payload))
|
|
200
|
+
return payload;
|
|
201
|
+
const record = optionalRecord(payload);
|
|
202
|
+
if (Array.isArray(record?.[field]))
|
|
203
|
+
return record[field];
|
|
204
|
+
throw providerResponseError(`${label} must contain an array`);
|
|
205
|
+
}
|
|
206
|
+
function requireMutationObject(payload, label) {
|
|
207
|
+
const record = optionalRecord(payload);
|
|
208
|
+
if (record)
|
|
209
|
+
return record;
|
|
210
|
+
throw new ProviderRequestError(502, `${label} returned an empty or invalid response; the operation may have completed upstream. Check the current session or trading state before retrying.`, { retryable: false, upstreamCompleted: "unknown" });
|
|
211
|
+
}
|
|
212
|
+
function requirePassword(value) {
|
|
213
|
+
if (typeof value !== "string" || value.length === 0)
|
|
214
|
+
throw providerInputError("password is required");
|
|
215
|
+
return value;
|
|
216
|
+
}
|