@oomol-lab/open-connector 1.6.2 → 1.6.4

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.
Files changed (175) hide show
  1. package/README.md +27 -0
  2. package/assets/open-connector/catalog/apps/assured_platform.json +218 -0
  3. package/assets/open-connector/catalog/apps/baixiao.json +1080 -0
  4. package/assets/open-connector/catalog/apps/benchmarkone.json +956 -0
  5. package/assets/open-connector/catalog/apps/bing_webmaster.json +3097 -0
  6. package/assets/open-connector/catalog/apps/buzzsprout.json +1979 -0
  7. package/assets/open-connector/catalog/apps/feishu.json +13 -19
  8. package/assets/open-connector/catalog/apps/feishu_app_bot.json +12 -19
  9. package/assets/open-connector/catalog/apps/jin10.json +1 -1
  10. package/assets/open-connector/catalog/apps/linkfox.json +836 -0
  11. package/assets/open-connector/catalog/apps/metatraderapi.json +1077 -0
  12. package/assets/open-connector/catalog/apps/mktindex.json +2411 -0
  13. package/assets/open-connector/catalog/apps/mojo_helpdesk.json +660 -0
  14. package/assets/open-connector/catalog/apps/mumble.json +446 -0
  15. package/assets/open-connector/catalog/apps/notion.json +112 -3
  16. package/assets/open-connector/catalog/apps/omise.json +438 -0
  17. package/assets/open-connector/catalog/apps/panjiva.json +2858 -0
  18. package/assets/open-connector/catalog/apps/papertrail.json +449 -0
  19. package/assets/open-connector/catalog/apps/peec.json +747 -0
  20. package/assets/open-connector/catalog/apps/polygon_io.json +5156 -584
  21. package/assets/open-connector/catalog/apps/quant_data.json +1928 -0
  22. package/assets/open-connector/catalog/apps/riddle_quiz_maker.json +621 -0
  23. package/assets/open-connector/catalog/apps/slack.json +327 -4
  24. package/assets/open-connector/catalog/apps/slackbot.json +280 -3
  25. package/assets/open-connector/catalog/apps/templatedocs.json +438 -0
  26. package/assets/open-connector/catalog/apps/terminus_app.json +510 -0
  27. package/assets/open-connector/catalog/apps/ticket_tailor.json +584 -0
  28. package/assets/open-connector/catalog/apps/tongdaxin.json +2186 -82
  29. package/assets/open-connector/catalog/apps/veremark.json +514 -0
  30. package/assets/open-connector/catalog/apps/walletap.json +549 -0
  31. package/assets/open-connector/catalog/apps/xiaohongshu_store.json +955 -39
  32. package/assets/open-connector/catalog/apps-index.json +1 -1
  33. package/package.json +5 -1
  34. package/src/connection-service.d.ts +2 -0
  35. package/src/connection-service.js +8 -1
  36. package/src/core/provider-id.d.ts +5 -0
  37. package/src/core/provider-id.js +12 -0
  38. package/src/oauth/oauth-credential-refresh-service.js +2 -0
  39. package/src/oauth/oauth-flow-service.js +2 -0
  40. package/src/providers/action-contracts.generated.d.ts +27 -7
  41. package/src/providers/assured_platform/executors.d.ts +4 -0
  42. package/src/providers/assured_platform/executors.js +18 -0
  43. package/src/providers/assured_platform/runtime.d.ts +7 -0
  44. package/src/providers/assured_platform/runtime.js +108 -0
  45. package/src/providers/baixiao/actions.d.ts +2 -0
  46. package/src/providers/baixiao/actions.js +377 -0
  47. package/src/providers/baixiao/executors.d.ts +4 -0
  48. package/src/providers/baixiao/executors.js +106 -0
  49. package/src/providers/benchmarkone/executors.d.ts +4 -0
  50. package/src/providers/benchmarkone/executors.js +26 -0
  51. package/src/providers/benchmarkone/runtime.d.ts +12 -0
  52. package/src/providers/benchmarkone/runtime.js +113 -0
  53. package/src/providers/bing_webmaster/actions.d.ts +2 -0
  54. package/src/providers/bing_webmaster/actions.js +113 -0
  55. package/src/providers/bing_webmaster/executors.d.ts +4 -0
  56. package/src/providers/bing_webmaster/executors.js +104 -0
  57. package/src/providers/bing_webmaster/management-actions.d.ts +2 -0
  58. package/src/providers/bing_webmaster/management-actions.js +241 -0
  59. package/src/providers/bing_webmaster/reporting-actions.d.ts +2 -0
  60. package/src/providers/bing_webmaster/reporting-actions.js +300 -0
  61. package/src/providers/bing_webmaster/schemas.d.ts +33 -0
  62. package/src/providers/bing_webmaster/schemas.js +313 -0
  63. package/src/providers/buzzsprout/executors.d.ts +4 -0
  64. package/src/providers/buzzsprout/executors.js +21 -0
  65. package/src/providers/buzzsprout/runtime.d.ts +12 -0
  66. package/src/providers/buzzsprout/runtime.js +237 -0
  67. package/src/providers/dropbox/executors.js +21 -3
  68. package/src/providers/feishu/shared/drive-actions.d.ts +1 -0
  69. package/src/providers/feishu/shared/drive-actions.js +13 -9
  70. package/src/providers/feishu/shared/drive-runtime.js +15 -3
  71. package/src/providers/feishu/shared/wiki-actions.js +4 -2
  72. package/src/providers/feishu_app_bot/scopes.js +1 -1
  73. package/src/providers/hithink_finance/runtime.js +39 -60
  74. package/src/providers/linkfox/actions.js +2 -0
  75. package/src/providers/linkfox/runtime.js +70 -1
  76. package/src/providers/linkfox/sif-actions.d.ts +10 -0
  77. package/src/providers/linkfox/sif-actions.js +193 -0
  78. package/src/providers/mcp-client.d.ts +1 -0
  79. package/src/providers/mcp-client.js +41 -27
  80. package/src/providers/metatraderapi/executors.d.ts +4 -0
  81. package/src/providers/metatraderapi/executors.js +18 -0
  82. package/src/providers/metatraderapi/runtime.d.ts +7 -0
  83. package/src/providers/metatraderapi/runtime.js +216 -0
  84. package/src/providers/mktindex/actions-mmi.d.ts +2 -0
  85. package/src/providers/mktindex/actions-mmi.js +510 -0
  86. package/src/providers/mktindex/actions.d.ts +2 -0
  87. package/src/providers/mktindex/actions.js +2 -0
  88. package/src/providers/mktindex/executors.d.ts +4 -0
  89. package/src/providers/mktindex/executors.js +27 -0
  90. package/src/providers/mktindex/request.d.ts +17 -0
  91. package/src/providers/mktindex/request.js +48 -0
  92. package/src/providers/mktindex/runtime.d.ts +9 -0
  93. package/src/providers/mktindex/runtime.js +492 -0
  94. package/src/providers/mojo_helpdesk/executors.d.ts +4 -0
  95. package/src/providers/mojo_helpdesk/executors.js +18 -0
  96. package/src/providers/mojo_helpdesk/runtime.d.ts +7 -0
  97. package/src/providers/mojo_helpdesk/runtime.js +173 -0
  98. package/src/providers/mumble/executors.d.ts +4 -0
  99. package/src/providers/mumble/executors.js +99 -0
  100. package/src/providers/notion/executors.d.ts +9 -0
  101. package/src/providers/notion/executors.js +161 -6
  102. package/src/providers/omise/executors.d.ts +4 -0
  103. package/src/providers/omise/executors.js +120 -0
  104. package/src/providers/panjiva/actions.d.ts +14 -0
  105. package/src/providers/panjiva/actions.js +389 -0
  106. package/src/providers/panjiva/executors.d.ts +4 -0
  107. package/src/providers/panjiva/executors.js +136 -0
  108. package/src/providers/papertrail/executors.d.ts +4 -0
  109. package/src/providers/papertrail/executors.js +15 -0
  110. package/src/providers/papertrail/runtime.d.ts +7 -0
  111. package/src/providers/papertrail/runtime.js +167 -0
  112. package/src/providers/peec/executors.d.ts +4 -0
  113. package/src/providers/peec/executors.js +15 -0
  114. package/src/providers/peec/runtime.d.ts +7 -0
  115. package/src/providers/peec/runtime.js +128 -0
  116. package/src/providers/polygon_io/actions.d.ts +2 -0
  117. package/src/providers/polygon_io/actions.js +282 -0
  118. package/src/providers/polygon_io/expanded-actions.d.ts +2 -0
  119. package/src/providers/polygon_io/expanded-actions.js +375 -0
  120. package/src/providers/polygon_io/expanded-runtime.d.ts +16 -0
  121. package/src/providers/polygon_io/expanded-runtime.js +277 -0
  122. package/src/providers/polygon_io/runtime.js +21 -2
  123. package/src/providers/quant_data/executors.d.ts +4 -0
  124. package/src/providers/quant_data/executors.js +15 -0
  125. package/src/providers/quant_data/runtime.d.ts +7 -0
  126. package/src/providers/quant_data/runtime.js +118 -0
  127. package/src/providers/registry.generated.js +20 -0
  128. package/src/providers/riddle_quiz_maker/executors.d.ts +4 -0
  129. package/src/providers/riddle_quiz_maker/executors.js +15 -0
  130. package/src/providers/riddle_quiz_maker/runtime.d.ts +7 -0
  131. package/src/providers/riddle_quiz_maker/runtime.js +145 -0
  132. package/src/providers/sellersprite_mcp/executors.js +2 -1
  133. package/src/providers/slack/actions.js +106 -3
  134. package/src/providers/slack/runtime.js +262 -38
  135. package/src/providers/slackbot/actions.js +1 -1
  136. package/src/providers/templatedocs/executors.d.ts +4 -0
  137. package/src/providers/templatedocs/executors.js +18 -0
  138. package/src/providers/templatedocs/runtime.d.ts +13 -0
  139. package/src/providers/templatedocs/runtime.js +151 -0
  140. package/src/providers/terminus_app/executors.d.ts +4 -0
  141. package/src/providers/terminus_app/executors.js +15 -0
  142. package/src/providers/terminus_app/runtime.d.ts +7 -0
  143. package/src/providers/terminus_app/runtime.js +172 -0
  144. package/src/providers/ticket_tailor/executors.d.ts +4 -0
  145. package/src/providers/ticket_tailor/executors.js +15 -0
  146. package/src/providers/ticket_tailor/runtime.d.ts +7 -0
  147. package/src/providers/ticket_tailor/runtime.js +152 -0
  148. package/src/providers/tongdaxin/actions.d.ts +1 -0
  149. package/src/providers/tongdaxin/actions.js +6 -3
  150. package/src/providers/tongdaxin/executors.js +55 -13
  151. package/src/providers/tongdaxin/named-action-output.d.ts +1 -0
  152. package/src/providers/tongdaxin/named-action-output.js +143 -0
  153. package/src/providers/tongdaxin/named-action-routing.d.ts +9 -0
  154. package/src/providers/tongdaxin/named-action-routing.js +417 -0
  155. package/src/providers/tongdaxin/named-actions.d.ts +2 -0
  156. package/src/providers/tongdaxin/named-actions.js +373 -0
  157. package/src/providers/twenty_crm/runtime.js +4 -2
  158. package/src/providers/veremark/executors.d.ts +4 -0
  159. package/src/providers/veremark/executors.js +129 -0
  160. package/src/providers/walletap/executors.d.ts +4 -0
  161. package/src/providers/walletap/executors.js +15 -0
  162. package/src/providers/walletap/runtime.d.ts +7 -0
  163. package/src/providers/walletap/runtime.js +147 -0
  164. package/src/providers/weixin_official_account/access-token.js +31 -7
  165. package/src/providers/weixin_store/access-token.js +33 -7
  166. package/src/providers/xiaohongshu_store/executors.js +152 -3
  167. package/src/server/api/openapi.js +3 -0
  168. package/src/server/connector-assets.d.ts +4 -0
  169. package/src/server/connector-assets.js +12 -0
  170. package/src/server/connector-build.d.ts +33 -0
  171. package/src/server/connector-build.js +73 -0
  172. package/src/server/connector-runtime.d.ts +1 -2
  173. package/src/server/connector-runtime.js +2 -8
  174. package/src/server/server-assets.d.ts +0 -28
  175. package/src/server/server-assets.js +0 -50
@@ -0,0 +1,147 @@
1
+ import { compactObject, optionalInteger, optionalString } from "../../core/cast.js";
2
+ import { readProviderTextBody } from "../provider-runtime.js";
3
+ import { providerInputError, providerUserAgent, ProviderRequestError, requiredInputString, runProviderRequest, } from "../provider-runtime.js";
4
+ export const walletapApiBaseUrl = "https://api.walletap.io";
5
+ export const walletapActionHandlers = {
6
+ list_templates(input, context) {
7
+ return requestWalletapData({
8
+ apiKey: context.apiKey,
9
+ path: "/api/templates",
10
+ query: { limit: optionalInteger(input.limit), starting_after: optionalString(input.startingAfter) },
11
+ fetcher: context.fetcher,
12
+ signal: context.signal,
13
+ });
14
+ },
15
+ get_template(input, context) {
16
+ return requestWalletapData({
17
+ apiKey: context.apiKey,
18
+ path: `/api/templates/${encodeURIComponent(requiredInputString(input.templateId, "templateId"))}`,
19
+ fetcher: context.fetcher,
20
+ signal: context.signal,
21
+ });
22
+ },
23
+ list_passes(input, context) {
24
+ return requestWalletapData({
25
+ apiKey: context.apiKey,
26
+ path: "/api/passes",
27
+ query: {
28
+ templateId: requiredInputString(input.templateId, "templateId"),
29
+ limit: optionalInteger(input.limit),
30
+ startAfter: optionalString(input.startAfter),
31
+ endBefore: optionalString(input.endBefore),
32
+ status: optionalString(input.status),
33
+ },
34
+ fetcher: context.fetcher,
35
+ signal: context.signal,
36
+ });
37
+ },
38
+ search_passes(input, context) {
39
+ return requestWalletapData({
40
+ apiKey: context.apiKey,
41
+ path: "/api/passes/search",
42
+ query: {
43
+ templateId: requiredInputString(input.templateId, "templateId"),
44
+ searchQuery: requiredInputString(input.searchQuery, "searchQuery"),
45
+ },
46
+ fetcher: context.fetcher,
47
+ signal: context.signal,
48
+ });
49
+ },
50
+ get_pass(input, context) {
51
+ requirePassIdentifier(input);
52
+ return requestWalletapData({
53
+ apiKey: context.apiKey,
54
+ path: "/api/pass",
55
+ query: compactObject({
56
+ id: optionalString(input.id),
57
+ externalId: optionalString(input.externalId),
58
+ templateId: optionalString(input.templateId),
59
+ includeTemplate: typeof input.includeTemplate === "boolean" ? input.includeTemplate : undefined,
60
+ locale: optionalString(input.locale),
61
+ }),
62
+ fetcher: context.fetcher,
63
+ signal: context.signal,
64
+ });
65
+ },
66
+ create_pass(input, context) {
67
+ return requestWalletapData({
68
+ apiKey: context.apiKey,
69
+ method: "POST",
70
+ path: "/api/pass",
71
+ body: input,
72
+ fetcher: context.fetcher,
73
+ signal: context.signal,
74
+ });
75
+ },
76
+ update_pass(input, context) {
77
+ requirePassIdentifier(input);
78
+ return requestWalletapData({
79
+ apiKey: context.apiKey,
80
+ method: "PATCH",
81
+ path: "/api/pass",
82
+ body: input,
83
+ fetcher: context.fetcher,
84
+ signal: context.signal,
85
+ });
86
+ },
87
+ notify_pass(input, context) {
88
+ const passId = requiredInputString(input.passId, "passId");
89
+ const { passId: _, ...body } = input;
90
+ return requestWalletapData({
91
+ apiKey: context.apiKey,
92
+ method: "POST",
93
+ path: `/api/pass/${encodeURIComponent(passId)}/notify`,
94
+ body,
95
+ fetcher: context.fetcher,
96
+ signal: context.signal,
97
+ });
98
+ },
99
+ };
100
+ export async function validateWalletapCredential(apiKey, fetcher, signal) {
101
+ await requestWalletapData({ apiKey, path: "/api/templates", query: { limit: 1 }, fetcher, signal });
102
+ return { profile: { accountId: "walletap", displayName: "Walletap API Key" }, grantedScopes: [], metadata: {} };
103
+ }
104
+ function requirePassIdentifier(input) {
105
+ if (optionalString(input.id) || (optionalString(input.externalId) && optionalString(input.templateId)))
106
+ return;
107
+ throw providerInputError("id or externalId together with templateId is required");
108
+ }
109
+ async function requestWalletapData(input) {
110
+ return runProviderRequest({ label: "Walletap", signal: input.signal }, async (signal) => {
111
+ const url = new URL(input.path, walletapApiBaseUrl);
112
+ for (const [key, value] of Object.entries(input.query ?? {}))
113
+ if (value !== undefined)
114
+ url.searchParams.set(key, String(value));
115
+ const response = await input.fetcher(url, {
116
+ method: input.method ?? "GET",
117
+ headers: {
118
+ accept: "application/json",
119
+ "content-type": "application/json",
120
+ "user-agent": providerUserAgent,
121
+ "x-api-key": input.apiKey,
122
+ },
123
+ body: input.body === undefined ? undefined : JSON.stringify(input.body),
124
+ signal,
125
+ });
126
+ const payload = parseWalletapPayload(await readProviderTextBody(response, "Walletap response"));
127
+ if (!response.ok)
128
+ throw new ProviderRequestError(response.status, readWalletapError(payload) ?? `Walletap request failed with HTTP ${response.status}`, payload);
129
+ return { data: payload };
130
+ });
131
+ }
132
+ function parseWalletapPayload(text) {
133
+ if (!text)
134
+ return {};
135
+ try {
136
+ return JSON.parse(text);
137
+ }
138
+ catch {
139
+ throw new ProviderRequestError(502, "Walletap returned an invalid JSON response");
140
+ }
141
+ }
142
+ function readWalletapError(payload) {
143
+ if (typeof payload !== "object" || payload === null)
144
+ return undefined;
145
+ const record = payload;
146
+ return optionalString(record.error) ?? optionalString(record.message);
147
+ }
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { optionalInteger, optionalNumber, optionalRecord, optionalString } from "../../core/cast.js";
2
3
  import { ProviderRequestError, providerResponseError, providerUserAgent, requiredInputString, runProviderRequest, } from "../provider-runtime.js";
3
4
  const wechatStableTokenUrl = "https://api.weixin.qq.com/cgi-bin/stable_token";
@@ -6,6 +7,10 @@ const accessTokenRefreshSkewMs = 120_000;
6
7
  const ipWhitelistHint = "Add this runtime's public egress IP to the official account's API IP whitelist in the 微信开发者平台 console (公众号 → 基础信息 → 开发密钥 → API IP白名单) and try again.";
7
8
  const accessTokenCache = new Map();
8
9
  const accessTokenInFlight = new Map();
10
+ const accessTokenRefreshInFlight = new Map();
11
+ function accessTokenCacheKey(credential) {
12
+ return createHash("sha256").update(credential.appId).update("\0").update(credential.appSecret).digest("hex");
13
+ }
9
14
  export function readWechatOfficialAccountCredential(values) {
10
15
  return {
11
16
  appId: requiredInputString(values.appId, "appId"),
@@ -20,18 +25,37 @@ export function readWechatOfficialAccountCredential(values) {
20
25
  * one invalidates the previous token, so duplicate mints must not happen.
21
26
  */
22
27
  export async function getWechatAccessToken(input) {
23
- const cacheKey = `${input.credential.appId}\0${input.credential.appSecret}`;
28
+ const cacheKey = accessTokenCacheKey(input.credential);
29
+ const refreshPending = accessTokenRefreshInFlight.get(cacheKey);
30
+ if (refreshPending)
31
+ return refreshPending;
24
32
  const pending = accessTokenInFlight.get(cacheKey);
25
- if (pending) {
26
- return pending;
33
+ if (!input.forceRefresh) {
34
+ if (pending)
35
+ return pending;
36
+ const request = mintAndCacheAccessToken(input, cacheKey);
37
+ accessTokenInFlight.set(cacheKey, request);
38
+ try {
39
+ return await request;
40
+ }
41
+ finally {
42
+ if (accessTokenInFlight.get(cacheKey) === request)
43
+ accessTokenInFlight.delete(cacheKey);
44
+ }
27
45
  }
28
- const request = mintAndCacheAccessToken(input, cacheKey);
29
- accessTokenInFlight.set(cacheKey, request);
46
+ const refresh = (async () => {
47
+ if (pending)
48
+ await pending.catch(() => undefined);
49
+ return mintAndCacheAccessToken(input, cacheKey);
50
+ })();
51
+ accessTokenRefreshInFlight.set(cacheKey, refresh);
30
52
  try {
31
- return await request;
53
+ return await refresh;
32
54
  }
33
55
  finally {
34
- accessTokenInFlight.delete(cacheKey);
56
+ if (accessTokenRefreshInFlight.get(cacheKey) === refresh) {
57
+ accessTokenRefreshInFlight.delete(cacheKey);
58
+ }
35
59
  }
36
60
  }
37
61
  async function mintAndCacheAccessToken(input, cacheKey) {
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { optionalInteger, optionalNumber, optionalRecord, optionalString } from "../../core/cast.js";
2
3
  import { ProviderRequestError, providerResponseError, providerUserAgent, requiredInputString, runProviderRequest, } from "../provider-runtime.js";
3
4
  const wechatStableTokenUrl = "https://api.weixin.qq.com/cgi-bin/stable_token";
@@ -5,6 +6,7 @@ const defaultAccessTokenTtlSeconds = 7200;
5
6
  const accessTokenRefreshSkewMs = 120_000;
6
7
  const accessTokenCache = new Map();
7
8
  const accessTokenInFlight = new Map();
9
+ const accessTokenRefreshInFlight = new Map();
8
10
  export function readWeixinStoreCredential(values) {
9
11
  return {
10
12
  appId: requiredInputString(values.appId, "appId"),
@@ -24,18 +26,42 @@ export function readWeixinStoreCredential(values) {
24
26
  * follow-up request; the shared mint still runs under the default 30 s timeout.
25
27
  */
26
28
  export async function getWechatAccessToken(input) {
27
- const cacheKey = `${input.credential.appId}\0${input.credential.appSecret}`;
29
+ const cacheKey = createHash("sha256")
30
+ .update(input.credential.appId)
31
+ .update("\0")
32
+ .update(input.credential.appSecret)
33
+ .digest("hex");
34
+ const refreshPending = accessTokenRefreshInFlight.get(cacheKey);
35
+ if (refreshPending)
36
+ return refreshPending;
28
37
  const pending = accessTokenInFlight.get(cacheKey);
29
- if (pending) {
30
- return pending;
38
+ if (input.rejectedToken === undefined) {
39
+ if (pending)
40
+ return pending;
41
+ const request = mintAndCacheAccessToken(input, cacheKey);
42
+ accessTokenInFlight.set(cacheKey, request);
43
+ try {
44
+ return await request;
45
+ }
46
+ finally {
47
+ if (accessTokenInFlight.get(cacheKey) === request)
48
+ accessTokenInFlight.delete(cacheKey);
49
+ }
31
50
  }
32
- const request = mintAndCacheAccessToken(input, cacheKey);
33
- accessTokenInFlight.set(cacheKey, request);
51
+ const refresh = (async () => {
52
+ const pendingToken = pending ? await pending.catch(() => undefined) : undefined;
53
+ if (pendingToken !== undefined && pendingToken !== input.rejectedToken)
54
+ return pendingToken;
55
+ return mintAndCacheAccessToken(input, cacheKey);
56
+ })();
57
+ accessTokenRefreshInFlight.set(cacheKey, refresh);
34
58
  try {
35
- return await request;
59
+ return await refresh;
36
60
  }
37
61
  finally {
38
- accessTokenInFlight.delete(cacheKey);
62
+ if (accessTokenRefreshInFlight.get(cacheKey) === refresh) {
63
+ accessTokenRefreshInFlight.delete(cacheKey);
64
+ }
39
65
  }
40
66
  }
41
67
  async function mintAndCacheAccessToken(input, cacheKey) {
@@ -36,6 +36,15 @@ const actionMethodByName = {
36
36
  list_categories: "common.getCategories",
37
37
  get_category_attributes: "common.getAttributeLists",
38
38
  list_express_companies: "common.getExpressCompanyList",
39
+ list_item_skus: "product.getDetailSkuList",
40
+ create_item: "product.createItemAndSku",
41
+ update_item: "product.updateItemAndSku",
42
+ upload_material: "material.uploadMaterial",
43
+ list_materials: "material.queryMaterial",
44
+ search_brands: "common.brandSearch",
45
+ list_carriage_templates: "common.getCarriageTemplateList",
46
+ list_logistics_plans: "common.getLogisticsList",
47
+ list_address_records: "common.getAddressRecord",
39
48
  };
40
49
  const handlers = mapProviderActionSources("xiaohongshu_store", actionMethodByName, (actionName, method) => async (input, context) => {
41
50
  const data = await requestXiaohongshuStore({
@@ -147,9 +156,14 @@ async function requestXiaohongshuStore(input) {
147
156
  function isXiaohongshuStoreSuccess(payload) {
148
157
  if (payload.success === false)
149
158
  return false;
150
- if (payload.success === true)
151
- return true;
152
- return (optionalInteger(payload.error_code) ?? optionalInteger(payload.errorCode)) === 0;
159
+ const errorCode = optionalInteger(payload.error_code) ?? optionalInteger(payload.errorCode);
160
+ // The item publish APIs report some business rejections as { success: true, error_code: 50209,
161
+ // error_msg: "..." } with no data; without this check the message would be lost behind a generic
162
+ // "invalid item" response error.
163
+ if (payload.success === true) {
164
+ return payload.data != null || errorCode === undefined || errorCode === 0;
165
+ }
166
+ return errorCode === 0;
153
167
  }
154
168
  // The newer after-sale APIs wrap their payload in a second { code, msg, success, data }
155
169
  // envelope inside the standard one; both layers have to succeed.
@@ -383,6 +397,61 @@ function buildActionParameters(actionName, input) {
383
397
  }
384
398
  case "adjust_sku_stock":
385
399
  return { skuId: requiredInputString(input.skuId, "skuId"), qty: integer(input.qty, "qty", providerInputError) };
400
+ case "list_item_skus":
401
+ return compactObject({
402
+ id: optionalString(input.id),
403
+ createTimeFrom: optionalInteger(input.createTimeFrom),
404
+ createTimeTo: optionalInteger(input.createTimeTo),
405
+ updateTimeFrom: optionalInteger(input.updateTimeFrom),
406
+ updateTimeTo: optionalInteger(input.updateTimeTo),
407
+ buyable: optionalBoolean(input.buyable),
408
+ stockGte: optionalInteger(input.stockGte),
409
+ stockLte: optionalInteger(input.stockLte),
410
+ barcode: optionalString(input.barcode),
411
+ scSkucode: optionalString(input.scSkucode),
412
+ singlePackOnly: optionalBoolean(input.singlePackOnly),
413
+ lastId: optionalString(input.lastId),
414
+ isChannel: optionalBoolean(input.isChannel),
415
+ pageNo: optionalInteger(input.pageNo) ?? 1,
416
+ pageSize: optionalInteger(input.pageSize) ?? 50,
417
+ });
418
+ case "create_item":
419
+ return buildItemParameters(input, false);
420
+ case "update_item":
421
+ return buildItemParameters(input, true);
422
+ case "upload_material":
423
+ return {
424
+ name: requiredInputString(input.name, "name"),
425
+ type: requiredInputString(input.type, "type"),
426
+ materialContent: requiredInputString(input.contentBase64, "contentBase64"),
427
+ };
428
+ case "list_materials":
429
+ return compactObject({
430
+ materialId: optionalString(input.materialId),
431
+ name: optionalString(input.name),
432
+ type: optionalString(input.type),
433
+ status: optionalInteger(input.status),
434
+ createTimeFrom: optionalInteger(input.createTimeFrom),
435
+ createTimeTo: optionalInteger(input.createTimeTo),
436
+ ascByCreateTime: optionalBoolean(input.ascByCreateTime),
437
+ pageNo: optionalInteger(input.pageNo) ?? 1,
438
+ pageSize: optionalInteger(input.pageSize) ?? 50,
439
+ });
440
+ case "search_brands":
441
+ return {
442
+ categoryId: requiredInputString(input.categoryId, "categoryId"),
443
+ keyword: optionalString(input.keyword),
444
+ pageNo: optionalInteger(input.pageNo) ?? 1,
445
+ pageSize: optionalInteger(input.pageSize) ?? 20,
446
+ };
447
+ case "list_carriage_templates":
448
+ case "list_address_records":
449
+ return {
450
+ pageIndex: optionalInteger(input.pageIndex) ?? 1,
451
+ pageSize: optionalInteger(input.pageSize) ?? 20,
452
+ };
453
+ case "list_logistics_plans":
454
+ return {};
386
455
  case "list_categories":
387
456
  return compactObject({ categoryId: optionalString(input.categoryId) });
388
457
  case "get_category_attributes":
@@ -488,6 +557,50 @@ function normalizeActionOutput(actionName, data) {
488
557
  if (!record)
489
558
  throw providerResponseError("Xiaohongshu returned an invalid SKU stock result");
490
559
  return { stock: record };
560
+ case "list_item_skus":
561
+ if (!record)
562
+ throw providerResponseError("Xiaohongshu returned an invalid item SKU list");
563
+ return {
564
+ items: requireArray(record.data, "item SKU list"),
565
+ total: requireNonNegativeInteger(record.total, "item total"),
566
+ pageNo: optionalInteger(record.pageNO) ?? 1,
567
+ pageSize: optionalInteger(record.pageSize) ?? 50,
568
+ };
569
+ case "create_item":
570
+ case "update_item":
571
+ if (!record)
572
+ throw providerResponseError("Xiaohongshu returned an invalid item");
573
+ return { item: record };
574
+ case "upload_material":
575
+ if (!record)
576
+ throw providerResponseError("Xiaohongshu returned an invalid material");
577
+ return { material: record };
578
+ case "list_materials":
579
+ if (!record)
580
+ throw providerResponseError("Xiaohongshu returned an invalid material list");
581
+ return { materials: requireArray(record.materialDetailList, "material list") };
582
+ case "search_brands":
583
+ if (!record)
584
+ throw providerResponseError("Xiaohongshu returned an invalid brand list");
585
+ return { brands: requireArray(record.brands, "brand list") };
586
+ case "list_carriage_templates":
587
+ if (!record)
588
+ throw providerResponseError("Xiaohongshu returned an invalid carriage template list");
589
+ return {
590
+ templates: requireArray(record.carriageTemplateList, "carriage template list"),
591
+ total: requireNonNegativeInteger(record.totalCount, "carriage template total"),
592
+ };
593
+ case "list_logistics_plans":
594
+ if (!record)
595
+ throw providerResponseError("Xiaohongshu returned an invalid logistics plan list");
596
+ return { plans: requireArray(record.logisticsPlans, "logistics plan list") };
597
+ case "list_address_records":
598
+ if (!record)
599
+ throw providerResponseError("Xiaohongshu returned an invalid address record list");
600
+ return {
601
+ addresses: requireArray(record.sellerAddressRecordList, "address record list"),
602
+ total: requireNonNegativeInteger(record.total, "address record total"),
603
+ };
491
604
  case "list_categories":
492
605
  if (!record)
493
606
  throw providerResponseError("Xiaohongshu returned an invalid category list");
@@ -510,6 +623,42 @@ function normalizeActionOutput(actionName, data) {
510
623
  return { success: true };
511
624
  }
512
625
  }
626
+ function buildItemParameters(input, update) {
627
+ const images = looseArray(input.images);
628
+ if (images.length === 0) {
629
+ throw providerInputError("images must be a non-empty array");
630
+ }
631
+ const skuListField = update ? "updateSkuList" : "createSkuList";
632
+ const skuList = looseArray(input[skuListField]);
633
+ if (skuList.length === 0) {
634
+ throw providerInputError(`${skuListField} must be a non-empty array`);
635
+ }
636
+ return compactObject({
637
+ itemId: update ? requiredInputString(input.itemId, "itemId") : undefined,
638
+ name: requiredInputString(input.name, "name"),
639
+ categoryId: requiredInputString(input.categoryId, "categoryId"),
640
+ shippingTemplateId: requiredInputString(input.shippingTemplateId, "shippingTemplateId"),
641
+ shippingGrossWeight: optionalInteger(input.shippingGrossWeight),
642
+ images,
643
+ brandId: optionalString(input.brandId),
644
+ attributes: optionalNonEmptyArray(input.attributes),
645
+ variantIds: optionalStringArray(input.variantIds),
646
+ articleNo: optionalString(input.articleNo),
647
+ description: optionalString(input.description),
648
+ itemShortTitle: optionalString(input.itemShortTitle),
649
+ deliveryMode: optionalInteger(input.deliveryMode),
650
+ freeReturn: optionalInteger(input.freeReturn),
651
+ videos: optionalNonEmptyArray(input.videos),
652
+ imageDescriptions: optionalNonEmptyArray(input.imageDescriptions),
653
+ createSkuList: update ? optionalNonEmptyArray(input.createSkuList) : undefined,
654
+ deleteSkuIdList: update ? optionalStringArray(input.deleteSkuIdList) : undefined,
655
+ [skuListField]: skuList,
656
+ });
657
+ }
658
+ function optionalNonEmptyArray(value) {
659
+ const items = looseArray(value);
660
+ return items.length > 0 ? items : undefined;
661
+ }
513
662
  function requireOrderListTimestamp(value, fieldName) {
514
663
  const timestamp = integer(value, fieldName, providerInputError);
515
664
  // Anything this large is a millisecond timestamp, which the order list would silently misread.
@@ -419,6 +419,9 @@ export function createOpenApiDocument(providers, options = {}) {
419
419
  service: jsonSchema.string({ description: "Provider service identifier." }),
420
420
  authType: jsonSchema.string({ description: "Connection authentication type." }),
421
421
  configured: jsonSchema.boolean({ description: "Whether the provider is connected." }),
422
+ oauthAuthorizationId: jsonSchema.string({
423
+ description: "Completed OAuth consent state. Omitted for legacy and non-OAuth connections.",
424
+ }),
422
425
  virtual: jsonSchema.boolean({
423
426
  description: "Whether the connection needs no stored secret.",
424
427
  }),
@@ -0,0 +1,4 @@
1
+ /** Directory to include in a host's Bun compile.assets. Its basename keeps connector assets namespaced. */
2
+ export declare function getConnectorAssetDirectory(): string;
3
+ /** Only Bun defines the `Bun` global; Node and workerd never do, so the read is safe everywhere. */
4
+ export declare function isStandaloneExecutable(): boolean;
@@ -0,0 +1,12 @@
1
+ import { join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ /** Directory to include in a host's Bun compile.assets. Its basename keeps connector assets namespaced. */
4
+ export function getConnectorAssetDirectory() {
5
+ return isStandaloneExecutable()
6
+ ? join(import.meta.dirname, "open-connector")
7
+ : fileURLToPath(new URL("../../assets/open-connector/", import.meta.url));
8
+ }
9
+ /** Only Bun defines the `Bun` global; Node and workerd never do, so the read is safe everywhere. */
10
+ export function isStandaloneExecutable() {
11
+ return globalThis.Bun?.isStandaloneExecutable === true;
12
+ }
@@ -0,0 +1,33 @@
1
+ export interface ConnectorBuildOptions {
2
+ /** Omit to include all providers. An empty array includes none. */
3
+ providers?: readonly string[];
4
+ }
5
+ export interface ConnectorBuild {
6
+ /** Pass to Bun.build's compile.assets. */
7
+ assets: string[];
8
+ /** Pass to Bun.build alongside plugins. */
9
+ external: string[];
10
+ plugins: ConnectorBuildPlugin[];
11
+ /** Remove staged assets after the build, including when it fails. */
12
+ dispose(): Promise<void>;
13
+ }
14
+ interface ConnectorBuildPlugin {
15
+ name: string;
16
+ setup(build: ConnectorBuildHooks): void;
17
+ }
18
+ interface ConnectorBuildHooks {
19
+ onLoad(options: {
20
+ filter: RegExp;
21
+ }, callback: (args: {
22
+ path: string;
23
+ }) => {
24
+ contents: string;
25
+ loader: "js";
26
+ } | undefined): unknown;
27
+ onEnd(callback: (result: {
28
+ success: boolean;
29
+ }) => void): unknown;
30
+ }
31
+ /** Prepare a headless Bun executable with only the requested provider code and catalog entries. */
32
+ export declare function getConnectorBuildOptions(options?: ConnectorBuildOptions): Promise<ConnectorBuild>;
33
+ export {};
@@ -0,0 +1,73 @@
1
+ import { access, cp, mkdir, mkdtemp, readdir, readFile, realpath, rm, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { extname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { catalogIndexFileName, createCatalogIndex } from "../catalog-index.js";
6
+ import { assertProviderId } from "../core/provider-id.js";
7
+ import { getConnectorAssetDirectory } from "./connector-assets.js";
8
+ /** Prepare a headless Bun executable with only the requested provider code and catalog entries. */
9
+ export async function getConnectorBuildOptions(options = {}) {
10
+ const sourceAssets = getConnectorAssetDirectory();
11
+ const external = ["proxy-agent"];
12
+ if (options.providers === undefined) {
13
+ return { assets: [sourceAssets], external, plugins: [], dispose: () => Promise.resolve() };
14
+ }
15
+ const services = [...new Set(options.providers)].sort();
16
+ const catalogDir = join(sourceAssets, "catalog/apps");
17
+ const files = new Set(await readdir(catalogDir));
18
+ for (const service of services) {
19
+ assertProviderId(service);
20
+ if (!files.has(`${service}.json`))
21
+ throw new Error(`Unknown provider: ${service}`);
22
+ }
23
+ const directory = await mkdtemp(join(tmpdir(), "open-connector-build-"));
24
+ const dispose = () => rm(directory, { recursive: true, force: true });
25
+ try {
26
+ const assets = join(directory, "open-connector");
27
+ const apps = join(assets, "catalog/apps");
28
+ await mkdir(apps, { recursive: true });
29
+ const providerDir = fileURLToPath(new URL("../providers/", import.meta.url));
30
+ const extension = extname(import.meta.filename);
31
+ const registry = await realpath(join(providerDir, `registry.generated${extension}`));
32
+ const sources = [];
33
+ for (const service of services) {
34
+ await access(join(providerDir, service, `executors${extension}`));
35
+ const file = `${service}.json`;
36
+ const content = await readFile(join(catalogDir, file));
37
+ const provider = JSON.parse(content.toString());
38
+ if (provider.service !== service)
39
+ throw new Error(`Catalog service does not match ${file}`);
40
+ await writeFile(join(apps, file), content);
41
+ sources.push({ file, bytes: content.byteLength, provider });
42
+ }
43
+ // Bun embeds files, so preserve the directory that the runtime reads even with no providers.
44
+ if (!services.length)
45
+ await writeFile(join(apps, "empty"), "");
46
+ await writeFile(join(assets, "catalog", catalogIndexFileName), JSON.stringify(createCatalogIndex(sources)));
47
+ await cp(join(sourceAssets, "migrations"), join(assets, "migrations"), { recursive: true });
48
+ const contents = `export const executorModules = {\n${services
49
+ .map((service) => `${JSON.stringify(service)}: () => import(${JSON.stringify(`./${service}/executors${extension}`)}),`)
50
+ .join("\n")}\n};`;
51
+ const plugin = {
52
+ name: "open-connector-providers",
53
+ setup(build) {
54
+ let replaced = false;
55
+ build.onLoad({ filter: /[/\\]providers[/\\]registry\.generated\.[jt]s$/ }, ({ path }) => {
56
+ if (path !== registry)
57
+ return;
58
+ replaced = true;
59
+ return { contents, loader: "js" };
60
+ });
61
+ build.onEnd((result) => {
62
+ if (result.success && !replaced)
63
+ throw new Error("The build did not load Open Connector's executor registry.");
64
+ });
65
+ },
66
+ };
67
+ return { assets: [assets], external, plugins: [plugin], dispose };
68
+ }
69
+ catch (error) {
70
+ await dispose();
71
+ throw error;
72
+ }
73
+ }
@@ -79,8 +79,7 @@ export interface ConnectorRuntime {
79
79
  /** Abort active requests, wait for their handlers, and close owned resources. Safe to call repeatedly. */
80
80
  close(): Promise<void>;
81
81
  }
82
- /** Directory to include in a host's Bun compile.assets. Its basename keeps connector assets namespaced. */
83
- export declare function getConnectorAssetDirectory(): string;
82
+ export { getConnectorAssetDirectory } from "./connector-assets.ts";
84
83
  /** Create a headless runtime without opening a listener or installing process signal handlers. */
85
84
  export declare function createConnectorRuntime(options: ConnectorRuntimeOptions): Promise<ConnectorRuntime>;
86
85
  export type { RuntimeProviderSetup } from "./api/runtime-api.ts";
@@ -1,6 +1,5 @@
1
1
  import { mkdir } from "node:fs/promises";
2
2
  import { join, resolve } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
3
  import { loadCatalog } from "../catalog-store.js";
5
4
  import { ActionPolicyService } from "../core/action-policy.js";
6
5
  import { setEgressTrustedHosts, setPrivateNetworkAccessAllowed } from "../core/request.js";
@@ -8,20 +7,15 @@ import { ProviderLoader } from "../providers/provider-loader.js";
8
7
  import { executorModules } from "../providers/registry.generated.js";
9
8
  import { createRuntimeJwtVerifier } from "./api/runtime-jwt.js";
10
9
  import { createConnectApp } from "./connect-app.js";
10
+ import { getConnectorAssetDirectory } from "./connector-assets.js";
11
11
  import { cleanupStagedTransitFiles, createNodeTransitFileUpload } from "./files/node-transit-file-upload.js";
12
12
  import { TransitFileService } from "./files/transit-files.js";
13
13
  import { createSecretCodec } from "./secrets/secret-codec.js";
14
- import { isStandaloneExecutable } from "./server-assets.js";
15
14
  import { createDirectoryMigrationSource } from "./storage/migration-source.js";
16
15
  import { createNodeRuntimeDatabase } from "./storage/node-runtime-database.js";
17
16
  import { DEFAULT_RUN_LIMIT } from "./storage/runtime-store.js";
18
17
  let runtimeActive = false;
19
- /** Directory to include in a host's Bun compile.assets. Its basename keeps connector assets namespaced. */
20
- export function getConnectorAssetDirectory() {
21
- return isStandaloneExecutable()
22
- ? join(import.meta.dirname, "open-connector")
23
- : fileURLToPath(new URL("../../assets/open-connector/", import.meta.url));
24
- }
18
+ export { getConnectorAssetDirectory } from "./connector-assets.js";
25
19
  /** Create a headless runtime without opening a listener or installing process signal handlers. */
26
20
  export async function createConnectorRuntime(options) {
27
21
  if (runtimeActive)