@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,173 @@
1
+ import { compactObject, looseArray, optionalNumber, optionalRecord, optionalString } from "../../core/cast.js";
2
+ import { providerInputError, ProviderRequestError, providerResponseError, providerUserAgent, readProviderJsonBody, requiredResponseRecord, runProviderRequest, } from "../provider-runtime.js";
3
+ export const mojoHelpdeskApiBaseUrl = "https://app.mojohelpdesk.com/api/v2";
4
+ const validationPath = "/ticket_queues";
5
+ export const mojoHelpdeskActionHandlers = {
6
+ async list_tickets(input, context) {
7
+ return {
8
+ tickets: requireArray(await requestMojo({
9
+ ...context,
10
+ path: "/tickets",
11
+ query: {
12
+ sort_by: optionalString(input.sortBy),
13
+ sort_order: optionalString(input.sortOrder),
14
+ page: optionalNumber(input.page),
15
+ per_page: optionalNumber(input.perPage),
16
+ },
17
+ }), "ticket list"),
18
+ };
19
+ },
20
+ async search_tickets(input, context) {
21
+ return {
22
+ tickets: requireArray(await requestMojo({
23
+ ...context,
24
+ path: "/tickets/search",
25
+ query: {
26
+ query: optionalString(input.query),
27
+ sf: optionalString(input.sortField),
28
+ r: input.reverse === undefined ? undefined : input.reverse ? 1 : 0,
29
+ page: optionalNumber(input.page),
30
+ per_page: optionalNumber(input.perPage),
31
+ },
32
+ }), "ticket search"),
33
+ };
34
+ },
35
+ async get_ticket(input, context) {
36
+ return {
37
+ ticket: requiredResponseRecord(await requestMojo({ ...context, path: `/tickets/${requireId(input.ticketId, "ticketId")}` }), "Mojo Helpdesk ticket response"),
38
+ };
39
+ },
40
+ async create_ticket(input, context) {
41
+ return {
42
+ ticket: requiredResponseRecord(await requestMojo({ ...context, path: "/tickets", method: "POST", body: buildTicketBody(input) }), "Mojo Helpdesk ticket response"),
43
+ };
44
+ },
45
+ async update_ticket(input, context) {
46
+ return {
47
+ ticket: requiredResponseRecord(await requestMojo({
48
+ ...context,
49
+ path: `/tickets/${requireId(input.ticketId, "ticketId")}`,
50
+ method: "PUT",
51
+ body: buildTicketBody(input),
52
+ }), "Mojo Helpdesk ticket response"),
53
+ };
54
+ },
55
+ async list_comments(input, context) {
56
+ return {
57
+ comments: requireArray(await requestMojo({
58
+ ...context,
59
+ path: `/tickets/${requireId(input.ticketId, "ticketId")}/comments`,
60
+ query: { page: optionalNumber(input.page), per_page: optionalNumber(input.perPage) },
61
+ }), "comment list"),
62
+ };
63
+ },
64
+ async create_comment(input, context) {
65
+ return {
66
+ comment: requiredResponseRecord(await requestMojo({
67
+ ...context,
68
+ path: `/tickets/${requireId(input.ticketId, "ticketId")}/comments`,
69
+ method: "POST",
70
+ body: compactObject({
71
+ body: optionalString(input.body),
72
+ time_spent: optionalNumber(input.timeSpent),
73
+ cc: optionalString(input.cc),
74
+ user_id: optionalNumber(input.userId),
75
+ }),
76
+ }), "Mojo Helpdesk comment response"),
77
+ };
78
+ },
79
+ async list_ticket_queues(input, context) {
80
+ return {
81
+ ticketQueues: requireArray(await requestMojo({
82
+ ...context,
83
+ path: validationPath,
84
+ query: { page: optionalNumber(input.page), per_page: optionalNumber(input.perPage) },
85
+ }), "ticket queue list"),
86
+ };
87
+ },
88
+ async list_tags(input, context) {
89
+ return {
90
+ tags: requireArray(await requestMojo({
91
+ ...context,
92
+ path: "/tags",
93
+ query: { sort_by: optionalString(input.sortBy), sort_order: optionalString(input.sortOrder) },
94
+ }), "tag list"),
95
+ };
96
+ },
97
+ };
98
+ export async function validateMojoHelpdeskCredential(apiKey, fetcher, signal) {
99
+ requireArray(await requestMojo({ apiKey, fetcher, signal, path: validationPath, query: { page: 1, per_page: 1 } }), "ticket queue list");
100
+ return {
101
+ profile: { accountId: "mojo_helpdesk", displayName: "Mojo Helpdesk API Key" },
102
+ grantedScopes: [],
103
+ metadata: { apiBaseUrl: mojoHelpdeskApiBaseUrl, validationEndpoint: validationPath },
104
+ };
105
+ }
106
+ function buildTicketBody(input) {
107
+ const body = compactObject({
108
+ title: optionalString(input.title),
109
+ description: optionalString(input.description),
110
+ ticket_queue_id: optionalNumber(input.ticketQueueId),
111
+ priority_id: optionalNumber(input.priorityId),
112
+ status_id: optionalNumber(input.statusId),
113
+ ticket_type_id: optionalNumber(input.ticketTypeId),
114
+ assigned_to_id: optionalNumber(input.assignedToId),
115
+ ticket_form_id: optionalNumber(input.ticketFormId),
116
+ user_id: optionalNumber(input.userId),
117
+ user: input.userEmail ? { email: input.userEmail } : undefined,
118
+ cc: optionalString(input.cc),
119
+ asset_tag: optionalString(input.assetTag),
120
+ asset_id: optionalNumber(input.assetId),
121
+ due_on: optionalString(input.dueOn),
122
+ scheduled_on: optionalString(input.scheduledOn),
123
+ resolution_id: optionalNumber(input.resolutionId),
124
+ });
125
+ for (const [key, value] of Object.entries(optionalRecord(input.customFields) ?? {})) {
126
+ body[`custom_field_${key}`] = value;
127
+ }
128
+ return body;
129
+ }
130
+ async function requestMojo(options) {
131
+ return runProviderRequest({ label: "Mojo Helpdesk", signal: options.signal }, async (signal) => {
132
+ const url = new URL(`${mojoHelpdeskApiBaseUrl}${options.path}`);
133
+ for (const [key, value] of Object.entries(options.query ?? {})) {
134
+ if (value !== undefined)
135
+ url.searchParams.set(key, String(value));
136
+ }
137
+ const headers = new Headers({
138
+ accept: "application/json",
139
+ "user-agent": providerUserAgent,
140
+ "x-api-key": options.apiKey,
141
+ });
142
+ if (options.body !== undefined)
143
+ headers.set("content-type", "application/json");
144
+ const response = await options.fetcher(url, {
145
+ method: options.method ?? "GET",
146
+ headers,
147
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
148
+ signal,
149
+ });
150
+ const payload = await readProviderJsonBody(response, {
151
+ emptyBody: {},
152
+ invalidJsonMessage: "Mojo Helpdesk returned invalid JSON",
153
+ });
154
+ if (!response.ok) {
155
+ const body = optionalRecord(payload);
156
+ throw new ProviderRequestError(response.status || 502, optionalString(body?.message) ??
157
+ optionalString(body?.error) ??
158
+ `Mojo Helpdesk API request failed with status ${response.status}`);
159
+ }
160
+ return payload;
161
+ });
162
+ }
163
+ function requireId(value, fieldName) {
164
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
165
+ throw providerInputError(`${fieldName} must be a positive integer`);
166
+ }
167
+ return value;
168
+ }
169
+ function requireArray(value, label) {
170
+ if (!Array.isArray(value))
171
+ throw providerResponseError(`Mojo Helpdesk ${label} must be an array`);
172
+ return looseArray(value);
173
+ }
@@ -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,99 @@
1
+ import { compactObject, optionalRecord, optionalString, objectArray, requiredBoolean, requiredRecord, requiredString, requiredStringArray, } from "../../core/cast.js";
2
+ import { readProviderTextBody } from "../provider-runtime.js";
3
+ import { defineApiKeyProviderExecutors, defineProviderProxy, ProviderRequestError, providerResponseError, providerUserAgent, } from "../provider-runtime.js";
4
+ const service = "mumble";
5
+ const baseUrl = "https://app.mumble.co.il/mumbleapi";
6
+ const handlers = {
7
+ create_customer: (input, context) => mutate("/add-new-customer", "POST", customerBody(input), context),
8
+ update_customer: (input, context) => mutate("/edit-customer", "POST", { ...optionalRecord(input.customFields), ...customerBody(input) }, context),
9
+ async get_customer(input, context) {
10
+ const data = requiredRecord(await request("/get-customer", "GET", context, undefined, {
11
+ customer_phone: requiredString(input.customerPhone, "customerPhone"),
12
+ }), "Mumble response");
13
+ return { customer: requiredRecord(data.customer, "Mumble customer") };
14
+ },
15
+ async list_customers(input, context) {
16
+ const data = requiredRecord(await request("/get-all-customers", "GET", context, undefined, input.page === undefined ? undefined : { page: String(input.page) }), "Mumble response");
17
+ return {
18
+ customers: objectArray(data.customers, "Mumble customers", providerResponseError),
19
+ total: data.total ?? null,
20
+ currentPage: data.current_page ?? null,
21
+ perPage: data.per_page ?? null,
22
+ totalPages: data.total_pages ?? data.pages ?? null,
23
+ };
24
+ },
25
+ delete_customer: (input, context) => mutate("/delete-customer", "DELETE", { customer_phone: requiredString(input.customerPhone, "customerPhone") }, context),
26
+ create_label: (input, context) => mutate("/add-new-label", "POST", { label_name: requiredString(input.labelName, "labelName") }, context),
27
+ async list_labels(_input, context) {
28
+ const data = requiredRecord(await request("/get-labels", "GET", context), "Mumble response");
29
+ return { labels: requiredStringArray(data.labels, "Mumble labels", providerResponseError) };
30
+ },
31
+ delete_label: (input, context) => mutate("/delete-label", "DELETE", { label_name: requiredString(input.labelName, "labelName") }, context),
32
+ };
33
+ export const executors = defineApiKeyProviderExecutors(service, handlers, {
34
+ skipDnsValidation: true,
35
+ });
36
+ export const proxy = defineProviderProxy({
37
+ service,
38
+ baseUrl,
39
+ auth: { type: "api_key_header", name: "Mumble-Api-Key" },
40
+ skipDnsValidation: true,
41
+ });
42
+ export const credentialValidators = {
43
+ async apiKey(input, { fetcher, signal }) {
44
+ const context = { apiKey: input.apiKey, fetcher, signal };
45
+ await handlers.list_labels({}, context);
46
+ return {
47
+ profile: { displayName: "Mumble API Key" },
48
+ grantedScopes: [],
49
+ metadata: { apiBaseUrl: baseUrl, validationEndpoint: "/get-labels" },
50
+ };
51
+ },
52
+ };
53
+ function customerBody(input) {
54
+ return {
55
+ ...optionalRecord(input.marketingAttribution),
56
+ ...compactObject({
57
+ customer_phone: requiredString(input.customerPhone, "customerPhone"),
58
+ name: optionalString(input.name),
59
+ email: optionalString(input.email),
60
+ source: optionalString(input.source),
61
+ bot_token: optionalString(input.botToken),
62
+ }),
63
+ };
64
+ }
65
+ async function mutate(path, method, body, context) {
66
+ const data = requiredRecord(await request(path, method, context, body), "Mumble response");
67
+ return { success: requiredBoolean(data.success, "Mumble success"), message: optionalString(data.message) ?? null };
68
+ }
69
+ async function request(path, method, context, body, query) {
70
+ const url = new URL(`${baseUrl}${path}`);
71
+ for (const [key, value] of Object.entries(query ?? {}))
72
+ url.searchParams.set(key, value);
73
+ const response = await context.fetcher(url, {
74
+ method,
75
+ headers: {
76
+ accept: "application/json",
77
+ "content-type": "application/json",
78
+ "mumble-api-key": context.apiKey,
79
+ "user-agent": providerUserAgent,
80
+ },
81
+ body: body ? JSON.stringify(body) : undefined,
82
+ signal: context.signal,
83
+ });
84
+ const text = await readProviderTextBody(response, "Mumble response");
85
+ let data = null;
86
+ try {
87
+ data = text ? JSON.parse(text) : null;
88
+ }
89
+ catch {
90
+ throw new ProviderRequestError(502, "Mumble returned invalid JSON");
91
+ }
92
+ if (!response.ok) {
93
+ const record = optionalRecord(data);
94
+ throw new ProviderRequestError(response.status, optionalString(record?.message) ??
95
+ optionalString(record?.error) ??
96
+ `Mumble request failed with status ${response.status}`);
97
+ }
98
+ return data;
99
+ }
@@ -3,6 +3,15 @@ import type { ProviderActionHandlers } from "../provider-runtime.ts";
3
3
  interface NotionActionContext {
4
4
  accessToken: string;
5
5
  fetcher: typeof fetch;
6
+ /**
7
+ * The stored credential's runtime metadata. For an OAuth credential this is
8
+ * the token-exchange response minus its secrets — `workspace_id`, `owner`,
9
+ * `bot_id`, `workspace_name` — merged with the `/users/me` bot object the
10
+ * validator stored, whose `bot` carries `workspace_id`, `owner` and
11
+ * `workspace_name` as well. For an internal integration only the latter
12
+ * exists. `get_current_user` reads both here and makes no request.
13
+ */
14
+ metadata: Record<string, unknown>;
6
15
  }
7
16
  type NotionActionHandler = (input: Record<string, unknown>, context: NotionActionContext) => Promise<unknown>;
8
17
  export declare const notionActionHandlers: ProviderActionHandlers<"notion", NotionActionHandler>;
@@ -5,6 +5,9 @@ const notionApiBaseUrl = "https://api.notion.com/v1";
5
5
  const notionFetch = createProviderFetch({ skipDnsValidation: true });
6
6
  const notionCoreVersion = "2026-03-11";
7
7
  export const notionActionHandlers = {
8
+ get_current_user(_input, context) {
9
+ return Promise.resolve(notionGetCurrentUser(context.metadata));
10
+ },
8
11
  search(input, context) {
9
12
  return notionSearch(input, context.accessToken, context.fetcher);
10
13
  },
@@ -86,8 +89,14 @@ export const executors = defineProviderExecutors({
86
89
  handlers: notionActionHandlers,
87
90
  skipDnsValidation: true,
88
91
  async createContext(context, fetcher) {
89
- const credential = await requireBearerCredential(context, service);
90
- return { accessToken: credential.accessToken, fetcher };
92
+ // Resolved ONCE: a resolution may refresh the token, and the bearer
93
+ // helper would resolve it a second time.
94
+ const stored = await context.getCredential(service);
95
+ return {
96
+ accessToken: bearerTokenOf(stored),
97
+ fetcher,
98
+ metadata: stored && stored.authType !== "no_auth" ? stored.metadata : {},
99
+ };
91
100
  },
92
101
  });
93
102
  export const proxy = async (input, context) => {
@@ -124,10 +133,89 @@ export const credentialValidators = {
124
133
  async apiKey(input, { fetcher }) {
125
134
  return validateNotionCredential(input.apiKey, fetcher);
126
135
  },
136
+ /**
137
+ * The profile is the OWNING USER, not the bot.
138
+ *
139
+ * `GET /users/me` under an OAuth token describes the integration's bot, and
140
+ * a bot id is a UUID like any member's — so a consumer that linked the
141
+ * connecting person to this profile's `accountId` would bind them to a
142
+ * principal that authorizes nobody, with nothing anywhere looking wrong.
143
+ * The grant was requested with `owner=user`, so the exchange response
144
+ * already names the human; it is on `input.metadata`. The liveness call
145
+ * still runs, because that is what proves the token works.
146
+ */
127
147
  async oauth2(input, { fetcher }) {
128
- return validateNotionCredential(input.accessToken, fetcher);
148
+ const live = await validateNotionCredential(input.accessToken, fetcher);
149
+ const owner = grantOwner(input.metadata);
150
+ if (!owner) {
151
+ return live;
152
+ }
153
+ return {
154
+ profile: {
155
+ accountId: owner.id,
156
+ displayName: owner.name ?? live.profile.displayName,
157
+ },
158
+ metadata: live.metadata,
159
+ };
129
160
  },
130
161
  };
162
+ /** The token's bearer value, whichever stored kind carries it. */
163
+ function bearerTokenOf(credential) {
164
+ if (credential?.authType === "oauth2") {
165
+ return credential.accessToken;
166
+ }
167
+ if (credential?.authType === "api_key") {
168
+ return credential.apiKey;
169
+ }
170
+ throw new ProviderRequestError(401, `Configure ${service} credentials first.`);
171
+ }
172
+ /**
173
+ * The person named as the credential's owner, or `undefined` when none is
174
+ * (a grant whose `owner.type` is `workspace`, which is every internal
175
+ * integration). Notion states the owner twice: at the top level of the token
176
+ * exchange, and under `bot.owner` of the `/users/me` bot object the validator
177
+ * stored. The exchange is read first, the bot object when it is absent.
178
+ */
179
+ function grantOwner(metadata) {
180
+ const owner = asObject(metadata.owner) ?? asObject(asObject(metadata.bot)?.owner);
181
+ if (owner?.type !== "user") {
182
+ return undefined;
183
+ }
184
+ const user = asObject(owner.user);
185
+ const id = asNonEmptyString(user?.id);
186
+ return id ? { id, name: asNonEmptyString(user?.name) } : undefined;
187
+ }
188
+ /**
189
+ * `get_current_user`: the workspace and owner of the credential, and nothing
190
+ * else, read from what is already stored with it.
191
+ *
192
+ * The workspace id has two sources and neither costs a request: the OAuth
193
+ * token exchange names it as `workspace_id`, and the `/users/me` bot object
194
+ * the validator stored names it as `bot.workspace_id` for OAuth grants and
195
+ * internal integrations alike. Refuses rather than guesses when neither is
196
+ * present, which means the credential was stored without its bot object and
197
+ * needs to be validated again.
198
+ */
199
+ function notionGetCurrentUser(metadata) {
200
+ const bot = asObject(metadata.bot);
201
+ const workspaceId = asNonEmptyString(metadata.workspace_id) ?? asNonEmptyString(bot?.workspace_id);
202
+ if (!workspaceId) {
203
+ // No explicit code: `providerErrorCodes` is the vocabulary a provider may
204
+ // put on the wire, and a missing credential field is not in it. The
205
+ // convention this follows is `provider-runtime.ts`'s own
206
+ // "credential metadata is missing …" — a plain 400 — with a message that
207
+ // says which field and what to do instead of only naming it.
208
+ throw new ProviderRequestError(400, "the stored Notion credential carries no workspace_id; reconnect Notion so its bot object is stored with the credential");
209
+ }
210
+ const owner = grantOwner(metadata);
211
+ return {
212
+ workspaceId,
213
+ workspaceName: asNonEmptyString(metadata.workspace_name) ?? asNonEmptyString(bot?.workspace_name) ?? null,
214
+ userId: owner?.id ?? null,
215
+ userName: owner?.name ?? null,
216
+ isBot: owner === undefined,
217
+ };
218
+ }
131
219
  async function validateNotionCredential(accessToken, fetcher) {
132
220
  const profile = await fetchNotionCurrentAccount(accessToken, fetcher);
133
221
  return {
@@ -171,15 +259,82 @@ async function notionRetrievePage(input, accessToken, fetcher) {
171
259
  }, fetcher);
172
260
  return page ?? {};
173
261
  }
262
+ /**
263
+ * The revision of whatever `pageId` names, and the reachability check that
264
+ * comes with reading it.
265
+ *
266
+ * **Pages and blocks both**, because this action's input has always been
267
+ * documented as "the page or block ID" and its description as "a Notion page
268
+ * **or block subtree**". A preflight that only knew `GET /pages/{id}` turned
269
+ * the advertised block-subtree flow into a 404 before the render was ever
270
+ * attempted — Notion answers `object_not_found` for a block id there.
271
+ *
272
+ * Pages first, since that is the overwhelmingly common input and costs one
273
+ * request; a 404 falls through to `/blocks/{id}`, which carries its own
274
+ * `last_edited_time`. Any other status propagates unchanged: a 401, 403 or
275
+ * 429 is an answer about the credential, not about which kind of id this is,
276
+ * and retrying it against a second endpoint would only double the cost and
277
+ * blur the error.
278
+ */
279
+ async function notionLastEditedTime(id, accessToken, fetcher) {
280
+ for (const path of [`/pages/${id}`, `/blocks/${id}`]) {
281
+ let object;
282
+ try {
283
+ object = (await notionRequest(accessToken, { path }, fetcher)) ?? {};
284
+ }
285
+ catch (error) {
286
+ if (error instanceof ProviderRequestError && error.status === 404 && !path.startsWith("/blocks/")) {
287
+ continue;
288
+ }
289
+ throw error;
290
+ }
291
+ // A successful answer settles which kind of id this is. Only a 404 falls
292
+ // through, so an object Notion returned without a revision is malformed
293
+ // rather than a reason to consult the other endpoint.
294
+ const lastEditedTime = asNonEmptyString(object.last_edited_time);
295
+ if (!lastEditedTime) {
296
+ throw new ProviderRequestError(502, `Notion returned ${id} without a last_edited_time`);
297
+ }
298
+ return lastEditedTime;
299
+ }
300
+ // Unreachable: the block attempt either returns, rethrows its own 404, or
301
+ // throws the 502 above. Kept so the bounded loop has an explicit tail.
302
+ throw new ProviderRequestError(502, `Notion answered neither /pages nor /blocks for ${id}`);
303
+ }
304
+ /**
305
+ * One page or block subtree as Markdown.
306
+ *
307
+ * Two requests, the object first: it is the cheap question ("can this grant
308
+ * reach this, and when did it last change") and answers a 404 before the
309
+ * expensive render is attempted. The markdown response has no revision of its
310
+ * own, so `lastEditedTime` comes from that object.
311
+ *
312
+ * **Notion's fields keep Notion's names.** This action shipped with the
313
+ * provider, so its body is a contract SDK and CLI callers already read; the
314
+ * response is forwarded and the two constructed fields are added beside it.
315
+ * `pageId` is echoed from the INPUT — Notion may spell an id dashed or
316
+ * undashed in its own body, and a consumer joining rows to bindings needs the
317
+ * spelling it asked with.
318
+ */
174
319
  async function notionRetrievePageMarkdown(input, accessToken, fetcher) {
175
320
  const pageId = String(input.pageId);
176
- const payload = await notionRequest(accessToken, {
321
+ const lastEditedTime = await notionLastEditedTime(pageId, accessToken, fetcher);
322
+ const rendered = (await notionRequest(accessToken, {
177
323
  path: `/pages/${pageId}/markdown`,
178
324
  query: compactQuery({
179
325
  include_transcript: typeof input.includeTranscript === "boolean" ? String(input.includeTranscript) : undefined,
180
326
  }),
181
- }, fetcher);
182
- return payload ?? {};
327
+ }, fetcher)) ?? {};
328
+ return {
329
+ ...rendered,
330
+ markdown: typeof rendered.markdown === "string" ? rendered.markdown : "",
331
+ truncated: rendered.truncated === true,
332
+ unknown_block_ids: Array.isArray(rendered.unknown_block_ids)
333
+ ? rendered.unknown_block_ids.filter((id) => typeof id === "string")
334
+ : [],
335
+ pageId,
336
+ lastEditedTime,
337
+ };
183
338
  }
184
339
  async function notionRetrievePageProperty(input, accessToken, fetcher) {
185
340
  const payload = await notionRequest(accessToken, {
@@ -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,120 @@
1
+ import { objectArray, optionalRecord, optionalString, requiredRecord, requiredString } from "../../core/cast.js";
2
+ import { readProviderTextBody } from "../provider-runtime.js";
3
+ import { basicAuthorizationHeader, defineApiKeyProviderExecutors, defineProviderProxy, providerInputError, providerResponseError, providerUserAgent, ProviderRequestError, } from "../provider-runtime.js";
4
+ const service = "omise", baseUrl = "https://api.omise.co";
5
+ const handlers = {
6
+ async get_account(_i, c) {
7
+ return { account: requiredRecord(await request("/account", c), "Omise account") };
8
+ },
9
+ async get_balance(_i, c) {
10
+ return { balance: requiredRecord(await request("/balance", c), "Omise balance") };
11
+ },
12
+ async list_customers(i, c) {
13
+ return normalizeList(await request("/customers", c, "GET", undefined, i), "customers");
14
+ },
15
+ async get_customer(i, c) {
16
+ return {
17
+ customer: requiredRecord(await request(`/customers/${segment(i.customer_id, "customer_id")}`, c), "Omise customer"),
18
+ };
19
+ },
20
+ async create_customer(i, c) {
21
+ return { customer: requiredRecord(await request("/customers", c, "POST", customerForm(i)), "Omise customer") };
22
+ },
23
+ async update_customer(i, c) {
24
+ const form = customerForm(i);
25
+ if (!form.size)
26
+ throw providerInputError("update_customer requires email, description, or metadata");
27
+ return {
28
+ customer: requiredRecord(await request(`/customers/${segment(i.customer_id, "customer_id")}`, c, "PATCH", form), "Omise customer"),
29
+ };
30
+ },
31
+ async delete_customer(i, c) {
32
+ return {
33
+ customer: requiredRecord(await request(`/customers/${segment(i.customer_id, "customer_id")}`, c, "DELETE"), "Omise customer"),
34
+ };
35
+ },
36
+ async list_charges(i, c) {
37
+ return normalizeList(await request("/charges", c, "GET", undefined, i), "charges");
38
+ },
39
+ async get_charge(i, c) {
40
+ return {
41
+ charge: requiredRecord(await request(`/charges/${segment(i.charge_id, "charge_id")}`, c), "Omise charge"),
42
+ };
43
+ },
44
+ };
45
+ export const executors = defineApiKeyProviderExecutors(service, handlers, {
46
+ skipDnsValidation: true,
47
+ });
48
+ export const proxy = defineProviderProxy({
49
+ service,
50
+ baseUrl,
51
+ auth: { type: "api_key_basic", suffix: ":" },
52
+ skipDnsValidation: true,
53
+ });
54
+ export const credentialValidators = {
55
+ async apiKey(input, { fetcher, signal }) {
56
+ const account = requiredRecord(await request("/account", { apiKey: input.apiKey, fetcher, signal }), "Omise account");
57
+ const id = optionalString(account.id)?.trim();
58
+ return {
59
+ profile: {
60
+ accountId: id,
61
+ displayName: optionalString(account.email)?.trim() || (id ? `Omise ${id}` : "Omise Account"),
62
+ },
63
+ grantedScopes: [],
64
+ metadata: { apiBaseUrl: baseUrl, validationEndpoint: "/account" },
65
+ };
66
+ },
67
+ };
68
+ function segment(value, name) {
69
+ return encodeURIComponent(requiredString(value, name));
70
+ }
71
+ function customerForm(input) {
72
+ const form = new URLSearchParams();
73
+ for (const key of ["email", "description"]) {
74
+ const value = optionalString(input[key]);
75
+ if (value !== undefined)
76
+ form.set(key, value);
77
+ }
78
+ for (const [key, value] of Object.entries(optionalRecord(input.metadata) ?? {}))
79
+ if (["string", "number", "boolean"].includes(typeof value))
80
+ form.set(`metadata[${key}]`, String(value));
81
+ return form;
82
+ }
83
+ function normalizeList(value, name) {
84
+ const record = requiredRecord(value, `Omise ${name}`);
85
+ const pagination = { ...record };
86
+ delete pagination.data;
87
+ return { [name]: objectArray(record.data, `Omise ${name} list data`, providerResponseError), pagination };
88
+ }
89
+ async function request(path, context, method = "GET", form, query) {
90
+ const url = new URL(path, baseUrl);
91
+ for (const [key, value] of Object.entries(query ?? {}))
92
+ if (typeof value === "string" || typeof value === "number")
93
+ url.searchParams.set(key, String(value));
94
+ const response = await context.fetcher(url, {
95
+ method,
96
+ headers: {
97
+ accept: "application/json",
98
+ authorization: basicAuthorizationHeader(`${context.apiKey}:`),
99
+ "user-agent": providerUserAgent,
100
+ "content-type": "application/x-www-form-urlencoded",
101
+ },
102
+ body: form,
103
+ signal: context.signal,
104
+ });
105
+ const text = await readProviderTextBody(response, "Omise response");
106
+ let payload = null;
107
+ try {
108
+ payload = text ? JSON.parse(text) : null;
109
+ }
110
+ catch {
111
+ if (response.ok)
112
+ throw new ProviderRequestError(502, "Omise returned invalid JSON");
113
+ payload = text;
114
+ }
115
+ if (!response.ok) {
116
+ const record = optionalRecord(payload);
117
+ throw new ProviderRequestError(response.status, optionalString(record?.message) ?? `Omise request failed with status ${response.status}`, payload, optionalString(record?.code));
118
+ }
119
+ return payload;
120
+ }
@@ -0,0 +1,14 @@
1
+ import type { ActionDefinition, JsonSchema } from "../../core/types.ts";
2
+ type ActionAsyncLifecycle = NonNullable<ActionDefinition["asyncLifecycle"]>;
3
+ interface PanjivaActionSpec {
4
+ name: string;
5
+ path: string;
6
+ method: "GET" | "POST";
7
+ description: string;
8
+ inputSchema: JsonSchema;
9
+ outputSchema: JsonSchema;
10
+ asyncLifecycle?: ActionAsyncLifecycle;
11
+ }
12
+ export declare const panjivaActionSpecs: PanjivaActionSpec[];
13
+ export declare const panjivaActions: readonly ActionDefinition[];
14
+ export {};