@jskit-ai/connectors-catalog 0.1.67 → 0.2.0

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 (137) hide show
  1. package/CHANGELOG.md +1722 -0
  2. package/README.md +11 -0
  3. package/locales/en.json +3139 -0
  4. package/package.json +7 -6
  5. package/src/server/ai.js +4 -4
  6. package/src/server/amazon-redshift.js +5 -5
  7. package/src/server/apify.js +1 -1
  8. package/src/server/apollo-io.js +1 -1
  9. package/src/server/asana.js +3 -3
  10. package/src/server/ashby.js +3 -3
  11. package/src/server/attention.js +2 -2
  12. package/src/server/aws-athena.js +3 -3
  13. package/src/server/aws-s3.js +1 -1
  14. package/src/server/awsTransport.js +9 -9
  15. package/src/server/brevo.js +3 -3
  16. package/src/server/calendly.js +2 -2
  17. package/src/server/chargebee.js +1 -1
  18. package/src/server/databricks.js +2 -2
  19. package/src/server/dbt-semantic-layer.js +8 -8
  20. package/src/server/elevenlabs.js +4 -4
  21. package/src/server/firebase-cloud-messaging.js +8 -8
  22. package/src/server/firecrawl.js +2 -2
  23. package/src/server/gatewayapi.js +4 -4
  24. package/src/server/github-api.js +3 -3
  25. package/src/server/gitlab-api.js +1 -1
  26. package/src/server/gmail.js +1 -1
  27. package/src/server/gong.js +4 -4
  28. package/src/server/google-ads-search.js +16 -16
  29. package/src/server/google-ads.js +7 -7
  30. package/src/server/google-drive.js +8 -6
  31. package/src/server/google-maps-platform.js +5 -5
  32. package/src/server/google-search-console.js +1 -1
  33. package/src/server/google-sheets.js +3 -2
  34. package/src/server/google.js +3 -2
  35. package/src/server/granola.js +5 -5
  36. package/src/server/hubspot.js +5 -3
  37. package/src/server/inngest.js +4 -4
  38. package/src/server/klipy.js +1 -1
  39. package/src/server/lightspeed.js +4 -4
  40. package/src/server/linear.js +2 -2
  41. package/src/server/linkedin.js +6 -6
  42. package/src/server/mapbox.js +1 -1
  43. package/src/server/mcpRegistration.js +3 -2
  44. package/src/server/mcpTransport.js +1 -1
  45. package/src/server/microsoft-fabric.js +3 -3
  46. package/src/server/microsoft-onedrive.js +1 -1
  47. package/src/server/microsoft-onenote.js +1 -1
  48. package/src/server/microsoft-outlook.js +1 -1
  49. package/src/server/microsoft.js +4 -3
  50. package/src/server/n8n.js +4 -4
  51. package/src/server/oura.js +1 -1
  52. package/src/server/paddle.js +1 -1
  53. package/src/server/pipedrive.js +4 -4
  54. package/src/server/posthog.js +3 -3
  55. package/src/server/salesforce.js +8 -8
  56. package/src/server/semrush.js +25 -25
  57. package/src/server/shopify.js +6 -6
  58. package/src/server/slack.js +10 -10
  59. package/src/server/snowflake.js +5 -5
  60. package/src/server/tally.js +1 -1
  61. package/src/server/telegram.js +1 -1
  62. package/src/server/tiktok.js +14 -14
  63. package/src/server/twilio.js +3 -3
  64. package/src/server/twitch.js +22 -21
  65. package/src/server/wave.js +34 -34
  66. package/src/server/wix.js +73 -73
  67. package/src/server/wiz.js +18 -18
  68. package/src/server/woocommerce.js +23 -23
  69. package/src/server/wordpress-com.js +3 -3
  70. package/src/server/wordpress-self-hosted.js +2 -2
  71. package/src/server/workday.js +3 -3
  72. package/src/server/x-twitter.js +6 -6
  73. package/src/server/xero.js +15 -15
  74. package/src/server/zoho-books.js +8 -8
  75. package/src/server/zoho-crm.js +5 -5
  76. package/src/server/zohoOAuth.js +1 -1
  77. package/src/shared/ai.js +24 -13
  78. package/src/shared/algolia.js +4 -4
  79. package/src/shared/amazon-redshift.js +16 -16
  80. package/src/shared/amplitude.js +6 -6
  81. package/src/shared/atlassian.js +19 -19
  82. package/src/shared/aws.js +18 -18
  83. package/src/shared/canva.js +18 -18
  84. package/src/shared/chargebee.js +3 -3
  85. package/src/shared/clickhouse.js +9 -9
  86. package/src/shared/confidence.js +6 -6
  87. package/src/shared/databricks.js +8 -8
  88. package/src/shared/dbt-semantic-layer.js +7 -7
  89. package/src/shared/english-ai.js +6 -0
  90. package/src/shared/english-definitions.js +34 -0
  91. package/src/shared/english-texts.js +7 -0
  92. package/src/shared/figma.js +2 -2
  93. package/src/shared/firebase-cloud-messaging.js +11 -11
  94. package/src/shared/firecrawl.js +1 -1
  95. package/src/shared/gemini-enterprise.js +7 -7
  96. package/src/shared/gong.js +5 -5
  97. package/src/shared/google-ads.js +5 -5
  98. package/src/shared/google-maps-platform.js +7 -7
  99. package/src/shared/google.js +40 -40
  100. package/src/shared/granola.js +5 -5
  101. package/src/shared/hex.js +7 -7
  102. package/src/shared/inngest.js +8 -8
  103. package/src/shared/lightspeed.js +52 -52
  104. package/src/shared/linkedin.js +6 -6
  105. package/src/shared/logo-dev.js +3 -3
  106. package/src/shared/mapbox.js +6 -6
  107. package/src/shared/mcp.js +9 -9
  108. package/src/shared/microsoft-fabric.js +9 -9
  109. package/src/shared/microsoft.js +52 -52
  110. package/src/shared/miro.js +5 -5
  111. package/src/shared/oura.js +5 -5
  112. package/src/shared/posthog.js +9 -9
  113. package/src/shared/prestashop.js +5 -5
  114. package/src/shared/resend.js +1 -1
  115. package/src/shared/salesforce.js +8 -8
  116. package/src/shared/semrush.js +6 -6
  117. package/src/shared/shopify.js +26 -26
  118. package/src/shared/slack.js +61 -61
  119. package/src/shared/snowflake.js +15 -15
  120. package/src/shared/tiktok.js +7 -7
  121. package/src/shared/tokens.js +126 -126
  122. package/src/shared/twilio.js +7 -7
  123. package/src/shared/twitch.js +24 -24
  124. package/src/shared/wave.js +33 -33
  125. package/src/shared/wix.js +5 -5
  126. package/src/shared/wiz.js +14 -14
  127. package/src/shared/wordpress-com.js +9 -9
  128. package/src/shared/wordpress.js +11 -11
  129. package/src/shared/workday.js +8 -8
  130. package/src/shared/x-twitter.js +5 -5
  131. package/src/shared/xero.js +25 -25
  132. package/src/shared/zoho-books.js +10 -10
  133. package/src/shared/zoho-crm.js +16 -16
  134. package/src/shared/zoho-regions.js +8 -8
  135. package/test/englishDefinitions.test.js +37 -0
  136. package/test/i18nCatalog.test.js +16 -0
  137. package/test/shopify.test.js +3 -1
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@jskit-ai/connectors-catalog",
3
- "version": "0.1.67",
3
+ "version": "0.2.0",
4
4
  "description": "Provider definitions and focused API operations for JSKIT connectors.",
5
5
  "type": "module",
6
6
  "exports": {
7
- "./shared/ai": "./src/shared/ai.js",
7
+ "./shared/ai": "./src/shared/english-ai.js",
8
8
  "./server/ai": "./src/server/ai.js",
9
- "./shared": "./src/shared/definitions.js",
9
+ "./shared": "./src/shared/english-definitions.js",
10
10
  "./server/resend": "./src/server/resend.js",
11
11
  "./server/firecrawl": "./src/server/firecrawl.js",
12
12
  "./server/gmail": "./src/server/gmail.js",
@@ -116,21 +116,22 @@
116
116
  "./server/gemini-enterprise": "./src/server/gemini-enterprise.js",
117
117
  "./server/workday": "./src/server/workday.js",
118
118
  "./server/shopify": "./src/server/shopify.js",
119
- "./server/wiz": "./src/server/wiz.js"
119
+ "./server/wiz": "./src/server/wiz.js",
120
+ "./locales/en.json": "./locales/en.json"
120
121
  },
121
122
  "dependencies": {
122
123
  "@aws-sdk/client-athena": "3.1128.0",
123
124
  "@aws-sdk/client-redshift-data": "3.1128.0",
124
125
  "@aws-sdk/client-s3": "3.1128.0",
125
126
  "@aws-sdk/s3-request-presigner": "3.1128.0",
126
- "@jskit-ai/connectors-core": "0.1.65",
127
+ "@jskit-ai/connectors-core": "0.2.0",
127
128
  "@modelcontextprotocol/sdk": "1.32.1",
128
129
  "@smithy/fetch-http-handler": "5.8.0",
129
130
  "jose": "^6.1.3",
130
131
  "json-rest-schema": "^1.0.17"
131
132
  },
132
133
  "peerDependencies": {
133
- "@jskit-ai/kernel": "0.1.248"
134
+ "@jskit-ai/kernel": "0.2.0"
134
135
  },
135
136
  "jskit": {
136
137
  "kind": "runtime",
package/src/server/ai.js CHANGED
@@ -10,10 +10,10 @@ function createAiConnectionResolver({ configuration, authorize, resolveReference
10
10
 
11
11
  async function resolve({ context, integrationId }) {
12
12
  const integration = Object.hasOwn(config.integrations, integrationId) ? config.integrations[integrationId] : null;
13
- if (integration?.provider !== "ai") throw new ConnectorError("connector_not_found", "This AI connection is not configured.", { statusCode: 404 });
13
+ if (integration?.provider !== "ai") throw new ConnectorError("connector_not_found", "This AI connection is not configured.", { statusCode: 404, messageCode: "connectors.ai.connectionNotConfigured" });
14
14
  const owner = await authorize(context, { integrationId, operation: "ai.resolve", accountMode: integration.accountMode });
15
15
  if (!owner?.applicationId || !owner?.subjectId) {
16
- throw new ConnectorError("connector_access_denied", "Access to this AI connection was denied.", { statusCode: 403 });
16
+ throw new ConnectorError("connector_access_denied", "Access to this AI connection was denied.", { statusCode: 403, messageCode: "connectors.ai.accessConnectionDenied" });
17
17
  }
18
18
  const model = getAiModel(integration.settings.model);
19
19
  const provider = aiCatalogue.providers.find((item) => item.id === model.providerId);
@@ -24,7 +24,7 @@ function createAiConnectionResolver({ configuration, authorize, resolveReference
24
24
  apiKey = "public";
25
25
  } else {
26
26
  if (integration.accountMode === "per-user" && integration.authentication.secretRef.startsWith("env:")) {
27
- throw new ConnectorError("connector_binding_invalid", "Individual AI accounts require a user-owned reference, not a shared environment variable.", { statusCode: 422 });
27
+ throw new ConnectorError("connector_binding_invalid", "Individual AI accounts require a user-owned reference, not a shared environment variable.", { statusCode: 422, messageCode: "connectors.ai.individualAccountsRequireUserOwned" });
28
28
  }
29
29
  try {
30
30
  apiKey = await resolveReference(integration.authentication.secretRef, { ...owner }, {
@@ -32,7 +32,7 @@ function createAiConnectionResolver({ configuration, authorize, resolveReference
32
32
  });
33
33
  if (typeof apiKey !== "string" || !apiKey.trim() || /[\r\n\0]/u.test(apiKey) || apiKey.length > 16384) throw new Error();
34
34
  } catch {
35
- throw new ConnectorError("connector_binding_missing", "The authorized AI account's API key is unavailable.", { statusCode: 422 });
35
+ throw new ConnectorError("connector_binding_missing", "The authorized AI account's API key is unavailable.", { statusCode: 422, messageCode: "connectors.ai.authorizedAccountApiKeyUnavailable" });
36
36
  }
37
37
  }
38
38
  // Never expose this result through a browser API or include it in logs.
@@ -23,7 +23,7 @@ function assertTarget(result, settings) {
23
23
  const matches = result.Database === settings.database && (settings.deploymentType === "provisioned"
24
24
  ? result.ClusterIdentifier === settings.clusterIdentifier && !result.WorkgroupName && (!settings.databaseUser || result.DbUser === settings.databaseUser)
25
25
  : result.WorkgroupName === settings.workgroup && !result.ClusterIdentifier);
26
- if (!matches) throw new ConnectorError("connector_access_denied", "This statement belongs to a different Redshift target.", { statusCode: 403 });
26
+ if (!matches) throw new ConnectorError("connector_access_denied", "This statement belongs to a different Redshift target.", { statusCode: 403, messageCode: "connectors.amazonRedshift.statementBelongsDifferentTarget" });
27
27
  }
28
28
 
29
29
  function validCell(cell) {
@@ -48,16 +48,16 @@ const amazonRedshiftProvider = Object.freeze({
48
48
  if (body.command === "DescribeTable") return send(new DescribeTableCommand(body.input));
49
49
  if (body.command === "ExecuteStatement") {
50
50
  const result = await send(new ExecuteStatementCommand(body.input));
51
- if (!validId(result)) throw new ConnectorError("connector_response_invalid", "Redshift did not return a statement identifier.", { statusCode: 502 });
51
+ if (!validId(result)) throw new ConnectorError("connector_response_invalid", "Redshift did not return a statement identifier.", { statusCode: 502, messageCode: "connectors.amazonRedshift.didNotReturnStatementIdentifier" });
52
52
  assertTarget(result, settings);
53
53
  return result;
54
54
  }
55
55
  const statement = await send(new DescribeStatementCommand({ Id: body.input.Id }));
56
- if (!validStatement(statement) || statement.Id !== body.input.Id) throw new ConnectorError("connector_response_invalid", "Redshift returned an unexpected statement.", { statusCode: 502 });
56
+ if (!validStatement(statement) || statement.Id !== body.input.Id) throw new ConnectorError("connector_response_invalid", "Redshift returned an unexpected statement.", { statusCode: 502, messageCode: "connectors.amazonRedshift.returnedUnexpectedStatement" });
57
57
  assertTarget(statement, settings);
58
58
  if (body.command === "DescribeStatement") return statement;
59
59
  if (body.command === "CancelStatement") return send(new CancelStatementCommand(body.input));
60
- if (statement.Status !== "FINISHED" || !statement.HasResultSet) throw new ConnectorError("connector_result_unavailable", "This statement has no finished result set.", { statusCode: 409 });
60
+ if (statement.Status !== "FINISHED" || !statement.HasResultSet) throw new ConnectorError("connector_result_unavailable", "This statement has no finished result set.", { statusCode: 409, messageCode: "connectors.amazonRedshift.statementNoFinishedResultSet" });
61
61
  return send(new GetStatementResultCommand(body.input));
62
62
  }),
63
63
  operations: {
@@ -81,7 +81,7 @@ const amazonRedshiftProvider = Object.freeze({
81
81
  name: { type: "string", required: true, pattern: "^[a-zA-Z0-9_]+$" }, value: { ...text, required: true, maxLength: 65536 }
82
82
  }) } }
83
83
  }, (v, s) => {
84
- if (v.parameters && new Set(v.parameters.map((p) => p.name)).size !== v.parameters.length) throw new ConnectorError("connector_input_invalid", "Use each SQL parameter name only once.", { statusCode: 422 });
84
+ if (v.parameters && new Set(v.parameters.map((p) => p.name)).size !== v.parameters.length) throw new ConnectorError("connector_input_invalid", "Use each SQL parameter name only once.", { statusCode: 422, messageCode: "connectors.amazonRedshift.useSqlParameterNameOnce" });
85
85
  return { input: { ...target(s), Sql: v.sql, ClientToken: v.clientToken, ResultFormat: "JSON", ...(v.parameters ? { Parameters: v.parameters } : {}) } };
86
86
  }, validId),
87
87
  "query.status": awsOperation("redshift-data", "DescribeStatement", { statementId }, (v) => ({ input: { Id: v.statementId } }), validStatement),
@@ -25,7 +25,7 @@ const apifyProvider = Object.freeze({
25
25
  size += value.byteLength;
26
26
  if (size > 8 * 1024 * 1024) {
27
27
  await reader.cancel();
28
- throw new ConnectorError("connector_response_too_large", "Use the framework's native Apify client to stream records larger than 8 MiB.", { statusCode: 413 });
28
+ throw new ConnectorError("connector_response_too_large", "Use the framework's native Apify client to stream records larger than 8 MiB.", { statusCode: 413, messageCode: "connectors.apify.useFrameworkNativeClientStream" });
29
29
  }
30
30
  chunks.push(value);
31
31
  }
@@ -34,7 +34,7 @@ function write(method, endpoint, fields, key, requiredNames = []) {
34
34
  return validatedOperation({ ...(method === "PATCH" ? { id: { ...id, required: true } } : {}), ...fields },
35
35
  ({ id: recordId, ...body }) => {
36
36
  if (!Object.keys(body).length || (requiredNames.length && !requiredNames.some(name => body[name]))) {
37
- throw new ConnectorError("connector_input_invalid", "Enter the record's required identifying fields or changes.", { statusCode: 422 });
37
+ throw new ConnectorError("connector_input_invalid", "Enter the record's required identifying fields or changes.", { statusCode: 422, messageCode: "connectors.apolloIo.enterRecordRequiredIdentifyingFields" });
38
38
  }
39
39
  return { method, url: `https://api.apollo.io/api/v1/${endpoint}${recordId ? `/${recordId}` : ""}`, body };
40
40
  }, record(key));
@@ -26,10 +26,10 @@ function read(endpoint, input = {}, fields) {
26
26
  }
27
27
  function write(method, endpoint, fields, resultFields) {
28
28
  return validatedOperation({ ...(method === "PUT" ? { gid: requiredGid } : {}), ...fields }, ({ gid: resource, ...data }) => {
29
- if (!Object.keys(data).length) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422 });
30
- if (Object.hasOwn(data, "start_on") && !Object.hasOwn(data, "due_on")) throw new ConnectorError("connector_input_invalid", "Include due_on when setting or clearing start_on.", { statusCode: 422 });
29
+ if (!Object.keys(data).length) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422, messageCode: "connectors.shared.supplyLeastChange" });
30
+ if (Object.hasOwn(data, "start_on") && !Object.hasOwn(data, "due_on")) throw new ConnectorError("connector_input_invalid", "Include due_on when setting or clearing start_on.", { statusCode: 422, messageCode: "connectors.asana.includeDueSettingClearingStart" });
31
31
  if (data.start_on && data.due_on && (data.start_on > data.due_on || endpoint === "projects" && data.start_on === data.due_on)) {
32
- throw new ConnectorError("connector_input_invalid", "The start date must precede the project due date, or be no later than the task due date.", { statusCode: 422 });
32
+ throw new ConnectorError("connector_input_invalid", "The start date must precede the project due date, or be no later than the task due date.", { statusCode: 422, messageCode: "connectors.asana.startDateMustPrecedeProject" });
33
33
  }
34
34
  const request = read(`${endpoint}${resource ? `/${resource}` : ""}`, {}, resultFields);
35
35
  return { ...request, method, body: { data } };
@@ -10,7 +10,7 @@ const page = { limit: { type: "integer", min: 1, max: 100, defaultTo: 25 },
10
10
  const candidate = { name: text, email: { ...text, pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" }, phoneNumber: text };
11
11
  function response(check) {
12
12
  return result => {
13
- if (result?.success === false) throw new ConnectorError("connector_provider_failed", "Ashby could not complete the operation.", { statusCode: 502 });
13
+ if (result?.success === false) throw new ConnectorError("connector_provider_failed", "Ashby could not complete the operation.", { statusCode: 502, messageCode: "connectors.ashby.couldNotCompleteOperation" });
14
14
  return result?.success === true && check(result);
15
15
  };
16
16
  }
@@ -26,7 +26,7 @@ const ashbyProvider = Object.freeze({
26
26
  ...ashbyDefinition,
27
27
  apiOrigins: ["https://api.ashbyhq.com"],
28
28
  apiKey: { headers: (key) => {
29
- if (/[:\s\p{Cc}]/u.test(key)) throw new ConnectorError("connector_binding_missing", "Use the Ashby API key without spaces, control characters or colons.");
29
+ if (/[:\s\p{Cc}]/u.test(key)) throw new ConnectorError("connector_binding_missing", "Use the Ashby API key without spaces, control characters or colons.", { messageCode: "connectors.shared.apiKeyFormat", messageParams: { provider: "Ashby" } });
30
30
  return { Authorization: `Basic ${Buffer.from(`${key}:`).toString("base64")}`, Accept: "application/json; version=1" };
31
31
  } },
32
32
  checkOperation: "jobs.list",
@@ -39,7 +39,7 @@ const ashbyProvider = Object.freeze({
39
39
  "candidates.update": { ...candidateUpdate, request(input) {
40
40
  const request = candidateUpdate.request(input);
41
41
  if (!["name", "email", "phoneNumber"].some(key => Object.hasOwn(request.body, key))) {
42
- throw new ConnectorError("connector_input_invalid", "Supply at least one candidate profile change.", { statusCode: 422 });
42
+ throw new ConnectorError("connector_input_invalid", "Supply at least one candidate profile change.", { statusCode: 422, messageCode: "connectors.ashby.supplyLeastCandidateProfileChange" });
43
43
  }
44
44
  return request;
45
45
  } },
@@ -19,7 +19,7 @@ function operation(method, endpoint, fields, validateResult, prepare = value =>
19
19
  const url = `https://api.attention.tech/v2/${endpoint.replace("{id}", resource || "")}`;
20
20
  if (method === "GET") return { method, url: withQuery(url, body) };
21
21
  if (method === "DELETE") return { method, url: new URL(url).href };
22
- if (!Object.keys(body).length) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422 });
22
+ if (!Object.keys(body).length) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422, messageCode: "connectors.shared.supplyLeastChange" });
23
23
  return { method, url: new URL(url).href, body };
24
24
  }, validateResult);
25
25
  }
@@ -54,7 +54,7 @@ const attentionProvider = Object.freeze({
54
54
  "scorecardItems.list": operation("GET", "scorecards/{id}/items", { id: requiredId, ...page }, list),
55
55
  "scorecards.summary": operation("POST", "scorecards/summary", { fromDateTime: { ...dateTime, required: true }, toDateTime: { ...dateTime, required: true },
56
56
  scorecardUUID: requiredId, scorecardsItemsUUIDs: { ...ids, required: true }, teamUUIDs: { ...ids, required: true }, userUUIDs: { ...ids, required: true }
57
- }, list, value => { if (Date.parse(value.fromDateTime) >= Date.parse(value.toDateTime)) throw new ConnectorError("connector_input_invalid", "End time must follow start time.", { statusCode: 422 }); return value; }),
57
+ }, list, value => { if (Date.parse(value.fromDateTime) >= Date.parse(value.toDateTime)) throw new ConnectorError("connector_input_invalid", "End time must follow start time.", { statusCode: 422, messageCode: "connectors.attention.endTimeMustFollowStart" }); return value; }),
58
58
  "scorecardResults.create": operation("POST", "createScorecardResult", { scorecard_uuid: requiredId, conversation_uuid: requiredId,
59
59
  summary: { ...text, required: true, maxLength: 20000 }, items: { type: "array", required: true,
60
60
  items: { type: "object", schema: createSchema({ scorecard_item_uuid: requiredId, numeric_result: { type: "integer" }, description: { ...text, required: true } }) },
@@ -25,7 +25,7 @@ const awsAthenaProvider = Object.freeze({
25
25
  const send = (command) => client.send(command, { abortSignal: signal });
26
26
  if (body.command === "GetWorkGroup") {
27
27
  const result = await send(new GetWorkGroupCommand(body.input));
28
- if (result.WorkGroup?.Name !== settings.workgroup) throw new ConnectorError("connector_response_invalid", "Athena returned a different workgroup.", { statusCode: 502 });
28
+ if (result.WorkGroup?.Name !== settings.workgroup) throw new ConnectorError("connector_response_invalid", "Athena returned a different workgroup.", { statusCode: 502, messageCode: "connectors.awsAthena.returnedDifferentWorkgroup" });
29
29
  return result;
30
30
  }
31
31
  if (body.command === "ListDataCatalogs") return send(new ListDataCatalogsCommand(body.input));
@@ -34,10 +34,10 @@ const awsAthenaProvider = Object.freeze({
34
34
  if (body.command === "StartQueryExecution") return send(new StartQueryExecutionCommand(body.input));
35
35
  const query = await send(new GetQueryExecutionCommand({ QueryExecutionId: body.input.QueryExecutionId }));
36
36
  if (!validQuery(query) || query.QueryExecution.QueryExecutionId !== body.input.QueryExecutionId) {
37
- throw new ConnectorError("connector_response_invalid", "Athena returned an unexpected query.", { statusCode: 502 });
37
+ throw new ConnectorError("connector_response_invalid", "Athena returned an unexpected query.", { statusCode: 502, messageCode: "connectors.awsAthena.returnedUnexpectedQuery" });
38
38
  }
39
39
  if (query.QueryExecution.WorkGroup !== settings.workgroup) {
40
- throw new ConnectorError("connector_access_denied", "This query belongs to a different workgroup.", { statusCode: 403 });
40
+ throw new ConnectorError("connector_access_denied", "This query belongs to a different workgroup.", { statusCode: 403, messageCode: "connectors.awsAthena.queryBelongsDifferentWorkgroup" });
41
41
  }
42
42
  if (body.command === "GetQueryExecution") return query;
43
43
  if (body.command === "GetQueryResults") return send(new GetQueryResultsCommand(body.input));
@@ -17,7 +17,7 @@ const awsS3Provider = Object.freeze({
17
17
  if (body.command === "ListObjectsV2") {
18
18
  const result = await client.send(new ListObjectsV2Command(body.input), { abortSignal: signal });
19
19
  if (result.Name !== settings.bucket || (result.Contents?.length || 0) > body.input.MaxKeys) {
20
- throw new ConnectorError("connector_response_invalid", "S3 returned an unexpected bucket or page size.", { statusCode: 502 });
20
+ throw new ConnectorError("connector_response_invalid", "S3 returned an unexpected bucket or page size.", { statusCode: 502, messageCode: "connectors.awsS3.returnedUnexpectedBucketPageSize" });
21
21
  }
22
22
  return result;
23
23
  }
@@ -28,16 +28,16 @@ function awsExchange(service, Client, execute) {
28
28
  (accessKeyId.startsWith("ASIA") && !sessionToken)) throw new Error("Invalid credentials");
29
29
  credentials = { accessKeyId, secretAccessKey: apiKey, ...(sessionToken ? { sessionToken } : {}) };
30
30
  } catch {
31
- throw new ConnectorError("connector_binding_missing", "Provide the AWS access key ID, secret access key and any required session token bindings.");
31
+ throw new ConnectorError("connector_binding_missing", "Provide the AWS access key ID, secret access key and any required session token bindings.", { messageCode: "connectors.aws.provideAccessKeyIdSecret" });
32
32
  }
33
33
  const origin = awsOrigin(service, settings);
34
- if (new URL(endpoint).origin !== origin) throw new ConnectorError("connector_destination_invalid", "The AWS region does not match the operation destination.");
34
+ if (new URL(endpoint).origin !== origin) throw new ConnectorError("connector_destination_invalid", "The AWS region does not match the operation destination.", { messageCode: "connectors.aws.regionDoesNotMatchOperation" });
35
35
  const client = new Client({ region: settings.region, endpoint: origin, credentials, maxAttempts: 1,
36
36
  ...(service === "s3" ? { forcePathStyle: true, followRegionRedirects: false, requestChecksumCalculation: "WHEN_REQUIRED" } : {}),
37
37
  requestHandler: new FetchHttpHandler({
38
38
  requestInit: () => ({ credentials: "omit", redirect: "error" }),
39
39
  customFetch: async (request) => {
40
- if (new URL(request.url).origin !== origin) throw new ConnectorError("connector_destination_invalid", "The AWS SDK selected an unexpected destination.");
40
+ if (new URL(request.url).origin !== origin) throw new ConnectorError("connector_destination_invalid", "The AWS SDK selected an unexpected destination.", { messageCode: "connectors.aws.sdkSelectedUnexpectedDestination" });
41
41
  options.signal.throwIfAborted();
42
42
  return fetchImpl(request.url, { method: request.method, headers: request.headers,
43
43
  ...(request.body ? { body: await request.text() } : {}), signal: options.signal, credentials: "omit", redirect: "error" });
@@ -55,17 +55,17 @@ function awsExchange(service, Client, execute) {
55
55
  if (error instanceof ConnectorError || ["AbortError", "TimeoutError"].includes(error.name)) throw error;
56
56
  const status = error.$metadata?.httpStatusCode;
57
57
  if (["ExpiredToken", "ExpiredTokenException", "InvalidAccessKeyId", "InvalidClientTokenId", "UnrecognizedClientException", "InvalidToken", "SignatureDoesNotMatch"].includes(error.name) || status === 401) {
58
- throw new ConnectorError("connector_reconnect_required", "Renew the AWS credential bindings and connect again.", { statusCode: 401 });
58
+ throw new ConnectorError("connector_reconnect_required", "Renew the AWS credential bindings and connect again.", { statusCode: 401, messageCode: "connectors.aws.renewCredentialBindingsConnectAgain" });
59
59
  }
60
60
  if (["TooManyRequestsException", "ThrottlingException", "SlowDown", "RequestLimitExceeded", "ActiveStatementsExceededException", "ActiveSessionsExceededException", "ActiveWaitingRequestsExceededException"].includes(error.name) || status === 429) {
61
- throw new ConnectorError("connector_rate_limited", "The AWS request limit was reached.", { statusCode: 429 });
61
+ throw new ConnectorError("connector_rate_limited", "The AWS request limit was reached.", { statusCode: 429, messageCode: "connectors.aws.requestLimitReached" });
62
62
  }
63
- if (status === 403) throw new ConnectorError("connector_permission_denied", "AWS denied this operation. Check IAM, resource and database permissions.", { statusCode: 403 });
64
- if (status === 404 || error.name === "ResourceNotFoundException") throw new ConnectorError("connector_not_found", "The AWS resource was not found.", { statusCode: 404 });
63
+ if (status === 403) throw new ConnectorError("connector_permission_denied", "AWS denied this operation. Check IAM, resource and database permissions.", { statusCode: 403, messageCode: "connectors.aws.deniedOperationCheckIamResource" });
64
+ if (status === 404 || error.name === "ResourceNotFoundException") throw new ConnectorError("connector_not_found", "The AWS resource was not found.", { statusCode: 404, messageCode: "connectors.aws.resourceNotFound" });
65
65
  if (status === 301 || status === 307 || ["AuthorizationHeaderMalformed", "PermanentRedirect", "InvalidRequestException", "ValidationException"].includes(error.name)) {
66
- throw new ConnectorError("connector_configuration_invalid", "Check the AWS region, resource and operation settings.", { statusCode: 422 });
66
+ throw new ConnectorError("connector_configuration_invalid", "Check the AWS region, resource and operation settings.", { statusCode: 422, messageCode: "connectors.aws.checkRegionResourceOperationSettings" });
67
67
  }
68
- throw new ConnectorError("connector_provider_failed", "The AWS request failed or returned an unreadable response.", { statusCode: 502 });
68
+ throw new ConnectorError("connector_provider_failed", "The AWS request failed or returned an unreadable response.", { statusCode: 502, messageCode: "connectors.aws.requestFailedReturnedUnreadableResponse" });
69
69
  } finally {
70
70
  client.destroy();
71
71
  }
@@ -24,12 +24,12 @@ function operation(method, endpoint, fields, validateResult, prepare = value =>
24
24
  }, validateResult);
25
25
  }
26
26
  function changed(value) {
27
- if (Object.keys(value).every(key => key === "resource")) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422 });
27
+ if (Object.keys(value).every(key => key === "resource")) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422, messageCode: "connectors.shared.supplyLeastChange" });
28
28
  return value;
29
29
  }
30
30
  const content = value => {
31
31
  if (value.templateId === undefined && !(value.subject && (value.htmlContent || value.textContent))) {
32
- throw new ConnectorError("connector_input_invalid", "Choose a template or provide a subject and email content.", { statusCode: 422 });
32
+ throw new ConnectorError("connector_input_invalid", "Choose a template or provide a subject and email content.", { statusCode: 422, messageCode: "connectors.brevo.chooseTemplateProvideSubjectEmail" });
33
33
  }
34
34
  return value;
35
35
  };
@@ -49,7 +49,7 @@ const brevoProvider = Object.freeze({
49
49
  recipient: { type: "string", required: true, pattern: "^\\+?[0-9]{6,15}$" }, type: { type: "string", required: true, enum: ["transactional", "marketing"] },
50
50
  content: { ...text, maxLength: 10000 }, templateId: { ...id, required: false }, params: bag, unicodeEnabled: bool, organisationPrefix: text, tag: text
51
51
  }, result => Number.isInteger(result?.messageId) && typeof result.reference === "string", value => {
52
- if ((value.content === undefined) === (value.templateId === undefined)) throw new ConnectorError("connector_input_invalid", "Choose SMS content or a template, not both.", { statusCode: 422 }); return value;
52
+ if ((value.content === undefined) === (value.templateId === undefined)) throw new ConnectorError("connector_input_invalid", "Choose SMS content or a template, not both.", { statusCode: 422, messageCode: "connectors.brevo.smsContentOrTemplate" }); return value;
53
53
  }),
54
54
  "contacts.get": operation("GET", "contacts/{resource}", { resource: { ...email, required: true } }, record),
55
55
  "contacts.create": operation("POST", "contacts", { email: { ...email, required: true }, ...contact,
@@ -35,12 +35,12 @@ const calendlyProvider = Object.freeze({
35
35
  start_time: { ...date, required: true }, end_time: { ...date, required: true }
36
36
  }, "availability:read", result => Array.isArray(result?.collection), value => {
37
37
  const span = Date.parse(value.end_time) - Date.parse(value.start_time);
38
- if (span <= 0 || span > 31 * 86400000) throw new ConnectorError("connector_input_invalid", "Choose a positive range of at most 31 days.", { statusCode: 422 }); return value;
38
+ if (span <= 0 || span > 31 * 86400000) throw new ConnectorError("connector_input_invalid", "Choose a positive range of at most 31 days.", { statusCode: 422, messageCode: "connectors.calendly.choosePositiveRangeMost31" }); return value;
39
39
  }),
40
40
  "events.list": operation("GET", "scheduled_events", { user: uri("users"), organization: uri("organizations"), group: uri("groups"),
41
41
  invitee_email: email, status, ...page, sort: { type: "string", enum: ["start_time:asc", "start_time:desc"] }, min_start_time: date, max_start_time: date
42
42
  }, "scheduled_events:read", collection, value => {
43
- if (!(value.user || value.organization || value.group)) throw new ConnectorError("connector_input_invalid", "Choose the user, organization or group whose events to read.", { statusCode: 422 }); return value;
43
+ if (!(value.user || value.organization || value.group)) throw new ConnectorError("connector_input_invalid", "Choose the user, organization or group whose events to read.", { statusCode: 422, messageCode: "connectors.calendly.chooseEventOwner" }); return value;
44
44
  }),
45
45
  "events.get": operation("GET", "scheduled_events/{uuid}", { uuid: id }, "scheduled_events:read", record),
46
46
  "events.cancel": operation("POST", "scheduled_events/{uuid}/cancellation", { uuid: id, reason: { ...text, maxLength: 10000 } },
@@ -36,7 +36,7 @@ const chargebeeProvider = Object.freeze({
36
36
  apiOrigins: (settings) => [origin(settings)],
37
37
  apiKey: { headers: (key) => {
38
38
  if (/[:\s\p{Cc}]/u.test(key)) {
39
- throw new ConnectorError("connector_binding_missing", "Use the Chargebee API key without spaces, control characters or colons.");
39
+ throw new ConnectorError("connector_binding_missing", "Use the Chargebee API key without spaces, control characters or colons.", { messageCode: "connectors.shared.apiKeyFormat", messageParams: { provider: "Chargebee" } });
40
40
  }
41
41
  return { Authorization: `Basic ${Buffer.from(`${key}:`).toString("base64")}` };
42
42
  } },
@@ -31,7 +31,7 @@ const databricksProvider = Object.freeze({
31
31
  if (!Number.isSafeInteger(value?.expires_in) || value.expires_in <= 40 ||
32
32
  (value.scope !== undefined && (typeof value.scope !== "string" || !/^[^\s]+(?: [^\s]+)*$/u.test(value.scope))) ||
33
33
  (value.refresh_token !== undefined && (typeof value.refresh_token !== "string" || !value.refresh_token.trim()))) {
34
- throw new ConnectorError("connector_response_invalid", "Databricks returned an invalid token grant.", { statusCode: 502 });
34
+ throw new ConnectorError("connector_response_invalid", "Databricks returned an invalid token grant.", { statusCode: 502, messageCode: "connectors.shared.invalidTokenGrant", messageParams: { provider: "Databricks" } });
35
35
  }
36
36
  }
37
37
  return response;
@@ -47,7 +47,7 @@ const databricksProvider = Object.freeze({
47
47
  if (response.status !== 200 || !object(result) || result.error_code || result.error ||
48
48
  (url.pathname.endsWith("/list") && result?.jobs?.length > Number(url.searchParams.get("limit"))) ||
49
49
  (url.pathname.endsWith("/get") && result?.job_id !== Number(url.searchParams.get("job_id")))) {
50
- throw new ConnectorError("connector_response_invalid", "Databricks returned an unexpected job response.", { statusCode: 502 });
50
+ throw new ConnectorError("connector_response_invalid", "Databricks returned an unexpected job response.", { statusCode: 502, messageCode: "connectors.databricks.returnedUnexpectedJobResponse" });
51
51
  }
52
52
  return result;
53
53
  }
@@ -30,7 +30,7 @@ const dbtSemanticLayerProvider = Object.freeze({
30
30
  ...dbtSemanticLayerDefinition,
31
31
  apiOrigins: (settings) => [origin(settings)],
32
32
  apiKey: { headers: (key) => {
33
- if (/[\s\p{Cc}]/u.test(key)) throw new ConnectorError("connector_binding_missing", "Use a dbt service token without spaces or control characters.");
33
+ if (/[\s\p{Cc}]/u.test(key)) throw new ConnectorError("connector_binding_missing", "Use a dbt service token without spaces or control characters.", { messageCode: "connectors.dbtSemanticLayer.useServiceTokenWithoutSpaces" });
34
34
  return { Authorization: `Bearer ${key}` };
35
35
  } },
36
36
  checkOperation: "environment.read",
@@ -64,29 +64,29 @@ const dbtSemanticLayerProvider = Object.freeze({
64
64
  async exchange(address, options, { request }) {
65
65
  const result = await request(address, options);
66
66
  if (Array.isArray(result?.errors) && result.errors.length > 0) {
67
- throw new ConnectorError("connector_provider_failed", "dbt could not complete the requested operation. Check the environment and service token permissions.", { statusCode: 502 });
67
+ throw new ConnectorError("connector_provider_failed", "dbt could not complete the requested operation. Check the environment and service token permissions.", { statusCode: 502, messageCode: "connectors.dbtSemanticLayer.couldNotCompleteRequestedOperation" });
68
68
  }
69
69
  const queryResult = result?.data?.query;
70
70
  if (queryResult) {
71
71
  if (queryResult.status === "FAILED") {
72
- throw new ConnectorError("connector_provider_failed", "dbt query failed. Inspect its status in dbt; do not automatically submit it again.", { statusCode: 502 });
72
+ throw new ConnectorError("connector_provider_failed", "dbt query failed. Inspect its status in dbt; do not automatically submit it again.", { statusCode: 502, messageCode: "connectors.dbtSemanticLayer.queryFailedInspectStatusDo" });
73
73
  }
74
74
  if (queryResult.jsonResult !== null && queryResult.jsonResult !== undefined) {
75
75
  if (typeof queryResult.jsonResult !== "string" || Buffer.byteLength(queryResult.jsonResult) > 5 * 1024 * 1024) {
76
- throw new ConnectorError("connector_response_invalid", "dbt returned an invalid or oversized JSON result page.", { statusCode: 502 });
76
+ throw new ConnectorError("connector_response_invalid", "dbt returned an invalid or oversized JSON result page.", { statusCode: 502, messageCode: "connectors.dbtSemanticLayer.returnedInvalidOversizedJsonResult" });
77
77
  }
78
78
  try { queryResult.jsonResult = JSON.parse(queryResult.jsonResult); }
79
- catch { throw new ConnectorError("connector_response_invalid", "dbt returned an invalid JSON result page.", { statusCode: 502 }); }
79
+ catch { throw new ConnectorError("connector_response_invalid", "dbt returned an invalid JSON result page.", { statusCode: 502, messageCode: "connectors.dbtSemanticLayer.returnedInvalidJsonResultPage" }); }
80
80
  if (!Array.isArray(queryResult.jsonResult?.data) || queryResult.jsonResult.data.length > 1024 || !Array.isArray(queryResult.jsonResult?.schema?.fields)) {
81
- throw new ConnectorError("connector_response_invalid", "dbt returned an invalid JSON table.", { statusCode: 502 });
81
+ throw new ConnectorError("connector_response_invalid", "dbt returned an invalid JSON table.", { statusCode: 502, messageCode: "connectors.dbtSemanticLayer.returnedInvalidJsonTable" });
82
82
  }
83
83
  } else if (queryResult.status === "SUCCESSFUL") {
84
- throw new ConnectorError("connector_response_invalid", "dbt completed without a JSON result table.", { statusCode: 502 });
84
+ throw new ConnectorError("connector_response_invalid", "dbt completed without a JSON result table.", { statusCode: 502, messageCode: "connectors.dbtSemanticLayer.completedWithoutJsonResultTable" });
85
85
  }
86
86
  }
87
87
  const page = result?.data?.metricsPaginated || result?.data?.dimensionsPaginated || result?.data?.savedQueriesPaginated;
88
88
  if (page && (page.pageNum !== options.body.variables.pageNum || page.pageSize !== options.body.variables.pageSize || page.items?.length > options.body.variables.pageSize)) {
89
- throw new ConnectorError("connector_response_invalid", "dbt returned an unexpected metadata page.", { statusCode: 502 });
89
+ throw new ConnectorError("connector_response_invalid", "dbt returned an unexpected metadata page.", { statusCode: 502, messageCode: "connectors.dbtSemanticLayer.returnedUnexpectedMetadataPage" });
90
90
  }
91
91
  return result;
92
92
  }
@@ -39,18 +39,18 @@ const elevenlabsProvider = Object.freeze({
39
39
  if (!response.ok) { await response.body?.cancel(); throw Object.assign(new Error("ElevenLabs speech request failed."), { status: response.status }); }
40
40
  const contentType = response.headers.get("content-type")?.split(";")[0] || "application/octet-stream";
41
41
  if (!contentType.startsWith("audio/") && contentType !== "application/octet-stream") {
42
- await response.body?.cancel(); throw new ConnectorError("connector_response_invalid", "ElevenLabs did not return audio.", { statusCode: 502 });
42
+ await response.body?.cancel(); throw new ConnectorError("connector_response_invalid", "ElevenLabs did not return audio.", { statusCode: 502, messageCode: "connectors.elevenlabs.didNotReturnAudio" });
43
43
  }
44
44
  const reader = response.body?.getReader(); const chunks = []; let size = 0;
45
45
  if (reader) try {
46
46
  while (true) {
47
47
  const { done, value } = await reader.read(); if (done) break;
48
48
  size += value.byteLength;
49
- if (size > 16 * 1024 * 1024) { await reader.cancel(); throw new ConnectorError("connector_response_too_large", "Use native streaming for audio above 16 MiB.", { statusCode: 413 }); }
49
+ if (size > 16 * 1024 * 1024) { await reader.cancel(); throw new ConnectorError("connector_response_too_large", "Use native streaming for audio above 16 MiB.", { statusCode: 413, messageCode: "connectors.elevenlabs.useNativeStreamingAudioAbove" }); }
50
50
  chunks.push(value);
51
51
  }
52
52
  } finally { reader.releaseLock(); }
53
- if (!size) throw new ConnectorError("connector_response_invalid", "ElevenLabs returned empty audio.", { statusCode: 502 });
53
+ if (!size) throw new ConnectorError("connector_response_invalid", "ElevenLabs returned empty audio.", { statusCode: 502, messageCode: "connectors.elevenlabs.returnedEmptyAudio" });
54
54
  return { bodyBase64: Buffer.concat(chunks).toString("base64"), contentType, size };
55
55
  },
56
56
  operations: {
@@ -63,7 +63,7 @@ const elevenlabsProvider = Object.freeze({
63
63
  const body = new FormData(); let total = 0;
64
64
  for (const file of files) {
65
65
  const bytes = Buffer.from(file.base64, "base64"); total += bytes.byteLength;
66
- if (total > 16 * 1024 * 1024) throw new ConnectorError("connector_input_invalid", "Audio samples must total at most 16 MiB.", { statusCode: 422 });
66
+ if (total > 16 * 1024 * 1024) throw new ConnectorError("connector_input_invalid", "Audio samples must total at most 16 MiB.", { statusCode: 422, messageCode: "connectors.elevenlabs.audioSamplesMustTotalMost" });
67
67
  body.append("files", new Blob([bytes], { type: file.contentType }), file.name);
68
68
  }
69
69
  for (const [key, value] of Object.entries(values)) body.append(key, String(value));
@@ -8,7 +8,7 @@ import { jsonOperation } from "./jsonOperation.js";
8
8
  const tokenUrl = "https://oauth2.googleapis.com/token";
9
9
  const endpoint = ({ projectId }) => `https://fcm.googleapis.com/v1/projects/${projectId}/messages:send`;
10
10
  const credentialSchema = createSchema({ project_id: firebaseProjectId });
11
- const invalidCredential = () => new ConnectorError("connector_binding_missing", "Use a valid Firebase service-account JSON credential for the default Google API domain.");
11
+ const invalidCredential = () => new ConnectorError("connector_binding_missing", "Use a valid Firebase service-account JSON credential for the default Google API domain.", { messageCode: "connectors.firebaseCloudMessaging.useValidServiceAccountJson" });
12
12
 
13
13
  async function serviceAccountGrant({ credential, scopes, fetchImpl, signal, now }) {
14
14
  let account, key;
@@ -33,16 +33,16 @@ async function serviceAccountGrant({ credential, scopes, fetchImpl, signal, now
33
33
  body: new URLSearchParams({ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion }).toString()
34
34
  });
35
35
  if (response.redirected || (response.status >= 300 && response.status < 400)) {
36
- throw new ConnectorError("connector_response_invalid", "Google returned an unexpected token redirect.", { statusCode: 502 });
36
+ throw new ConnectorError("connector_response_invalid", "Google returned an unexpected token redirect.", { statusCode: 502, messageCode: "connectors.firebaseCloudMessaging.returnedUnexpectedTokenRedirect" });
37
37
  }
38
38
  let value;
39
39
  try { value = await response.json(); } catch {
40
- throw new ConnectorError("connector_response_invalid", "Google returned an invalid token response.", { statusCode: 502 });
40
+ throw new ConnectorError("connector_response_invalid", "Google returned an invalid token response.", { statusCode: 502, messageCode: "connectors.shared.invalidTokenResponse", messageParams: { provider: "Google" } });
41
41
  }
42
42
  if (!response.ok) {
43
43
  const denied = response.status === 401 || ["invalid_grant", "invalid_client"].includes(value?.error);
44
44
  throw new ConnectorError(denied ? "connector_reconnect_required" : response.status === 403 ? "connector_permission_denied" : response.status === 429 ? "connector_rate_limited" : "connector_provider_failed",
45
- "Google could not authorize the service account. Check the credential and project permissions.", { statusCode: denied ? 401 : [403, 429].includes(response.status) ? response.status : 502 });
45
+ "Google could not authorize the service account. Check the credential and project permissions.", { statusCode: denied ? 401 : [403, 429].includes(response.status) ? response.status : 502, messageCode: "connectors.firebaseCloudMessaging.couldNotAuthorizeServiceAccount" });
46
46
  }
47
47
  return value;
48
48
  }
@@ -89,20 +89,20 @@ const firebaseCloudMessagingProvider = Object.freeze({
89
89
  headers: { ...options.headers, "Content-Type": "application/json" } });
90
90
  let result;
91
91
  try { result = await response.json(); }
92
- catch { throw new ConnectorError("connector_response_invalid", "Firebase returned invalid message JSON.", { statusCode: 502 }); }
92
+ catch { throw new ConnectorError("connector_response_invalid", "Firebase returned invalid message JSON.", { statusCode: 502, messageCode: "connectors.firebaseCloudMessaging.returnedInvalidMessageJson" }); }
93
93
  if (!response.ok) {
94
94
  const details = result?.error?.details;
95
95
  const fcmError = Array.isArray(details) ? details.find(detail => detail?.["@type"] === "type.googleapis.com/google.firebase.fcm.v1.FcmError")?.errorCode : undefined;
96
96
  if (response.status === 404 && fcmError === "UNREGISTERED" && (options.body.message.token || options.body.message.fid)) {
97
- throw new ConnectorError("connector_recipient_unregistered", "This Firebase recipient is no longer registered. Remove its association and wait for client registration again.", { statusCode: 410 });
97
+ throw new ConnectorError("connector_recipient_unregistered", "This Firebase recipient is no longer registered. Remove its association and wait for client registration again.", { statusCode: 410, messageCode: "connectors.firebaseCloudMessaging.recipientNoLongerRegisteredRemove" });
98
98
  }
99
99
  if (response.status === 400) {
100
- throw new ConnectorError("connector_message_invalid", "Firebase rejected the message or target. Do not delete a registration unless the payload is known to be valid.", { statusCode: 422 });
100
+ throw new ConnectorError("connector_message_invalid", "Firebase rejected the message or target. Do not delete a registration unless the payload is known to be valid.", { statusCode: 422, messageCode: "connectors.firebaseCloudMessaging.rejectedMessageTargetDoNot" });
101
101
  }
102
102
  throw Object.assign(new Error("Firebase message request failed."), { status: response.status });
103
103
  }
104
104
  if (!receipt(result) || !result.name.startsWith(`projects/${settings.projectId}/messages/`)) {
105
- throw new ConnectorError("connector_response_invalid", "Firebase returned an unexpected message receipt.", { statusCode: 502 });
105
+ throw new ConnectorError("connector_response_invalid", "Firebase returned an unexpected message receipt.", { statusCode: 502, messageCode: "connectors.firebaseCloudMessaging.returnedUnexpectedMessageReceipt" });
106
106
  }
107
107
  return result;
108
108
  }
@@ -43,8 +43,8 @@ const firecrawlProvider = Object.freeze({
43
43
  } }), result => result?.success === true && typeof result.id === "string"),
44
44
  "crawls.get": validatedOperation({ id: jobId, next: { type: "string", maxLength: 8192 } }, ({ id, next }) => {
45
45
  const expected = `https://api.firecrawl.dev/v2/crawl/${id}`;
46
- let url; try { url = new URL(next || expected); } catch { throw new ConnectorError("connector_input_invalid", "Use the returned next-page URL for this crawl.", { statusCode: 422 }); }
47
- if (url.origin + url.pathname !== expected || url.username || url.password || url.hash) throw new ConnectorError("connector_input_invalid", "Use the returned next-page URL for this crawl.", { statusCode: 422 });
46
+ let url; try { url = new URL(next || expected); } catch { throw new ConnectorError("connector_input_invalid", "Use the returned next-page URL for this crawl.", { statusCode: 422, messageCode: "connectors.firecrawl.useReturnedNextPageUrl" }); }
47
+ if (url.origin + url.pathname !== expected || url.username || url.password || url.hash) throw new ConnectorError("connector_input_invalid", "Use the returned next-page URL for this crawl.", { statusCode: 422, messageCode: "connectors.firecrawl.useReturnedNextPageUrl" });
48
48
  return { method: "GET", url: url.href };
49
49
  }, result => typeof result?.status === "string" && Array.isArray(result.data) && Number.isInteger(result.total) && result.total >= 0 && Number.isInteger(result.completed) && result.completed >= 0),
50
50
  "crawls.errors": validatedOperation({ id: jobId }, ({ id }) => ({ method: "GET", url: `https://api.firecrawl.dev/v2/crawl/${id}/errors` }), result => Array.isArray(result?.errors)),
@@ -34,14 +34,14 @@ const gatewayApiProvider = Object.freeze({
34
34
  // Call at the application's webhook route before trusting or persisting its body.
35
35
  function verifyGatewayApiEvent({ rawBody, signature, secret }) {
36
36
  if (!(rawBody instanceof Uint8Array) || rawBody.byteLength > 1048576 || typeof secret !== "string" || secret.length < 1 || typeof signature !== "string" || !/^v1=[a-fA-F0-9]{64}$/.test(signature)) {
37
- throw new ConnectorError("connector_webhook_invalid", "Invalid GatewayAPI webhook signature.", { statusCode: 401 });
37
+ throw new ConnectorError("connector_webhook_invalid", "Invalid GatewayAPI webhook signature.", { statusCode: 401, messageCode: "connectors.shared.invalidWebhookSignature", messageParams: { provider: "GatewayAPI" } });
38
38
  }
39
39
  const expected = createHmac("sha256", secret).update(rawBody).digest();
40
- if (!timingSafeEqual(expected, Buffer.from(signature.slice(3), "hex"))) throw new ConnectorError("connector_webhook_invalid", "Invalid GatewayAPI webhook signature.", { statusCode: 401 });
40
+ if (!timingSafeEqual(expected, Buffer.from(signature.slice(3), "hex"))) throw new ConnectorError("connector_webhook_invalid", "Invalid GatewayAPI webhook signature.", { statusCode: 401, messageCode: "connectors.shared.invalidWebhookSignature", messageParams: { provider: "GatewayAPI" } });
41
41
  let event;
42
- try { event = JSON.parse(Buffer.from(rawBody).toString("utf8")); } catch { throw new ConnectorError("connector_webhook_invalid", "Invalid GatewayAPI webhook event.", { statusCode: 401 }); }
42
+ try { event = JSON.parse(Buffer.from(rawBody).toString("utf8")); } catch { throw new ConnectorError("connector_webhook_invalid", "Invalid GatewayAPI webhook event.", { statusCode: 401, messageCode: "connectors.gatewayapi.invalidWebhookEvent" }); }
43
43
  if (typeof event?.event_id !== "string" || !event.event_id || typeof event.event_type !== "string" || !Number.isFinite(Date.parse(event.timestamp)) || !event.event || typeof event.event !== "object" || Array.isArray(event.event)) {
44
- throw new ConnectorError("connector_webhook_invalid", "Invalid GatewayAPI webhook event.", { statusCode: 401 });
44
+ throw new ConnectorError("connector_webhook_invalid", "Invalid GatewayAPI webhook event.", { statusCode: 401, messageCode: "connectors.gatewayapi.invalidWebhookEvent" });
45
45
  }
46
46
  return event;
47
47
  }
@@ -8,11 +8,11 @@ async function normalizeTokenResponse(response) {
8
8
  if (!response.ok) return response;
9
9
  let value;
10
10
  try { value = await response.clone().json(); } catch {
11
- throw new ConnectorError("connector_response_invalid", "GitHub returned an invalid token response.", { statusCode: 502 });
11
+ throw new ConnectorError("connector_response_invalid", "GitHub returned an invalid token response.", { statusCode: 502, messageCode: "connectors.shared.invalidTokenResponse", messageParams: { provider: "GitHub" } });
12
12
  }
13
13
  if (value.error || value.scope === undefined) return response;
14
14
  if (typeof value.scope !== "string" || !/^[a-zA-Z0-9_:.-]*(?:,[a-zA-Z0-9_:.-]+)*$/u.test(value.scope)) {
15
- throw new ConnectorError("connector_response_invalid", "GitHub returned an invalid permission grant.", { statusCode: 502 });
15
+ throw new ConnectorError("connector_response_invalid", "GitHub returned an invalid permission grant.", { statusCode: 502, messageCode: "connectors.shared.invalidPermissionGrant", messageParams: { provider: "GitHub" } });
16
16
  }
17
17
  return Response.json({ ...value, scope: value.scope.replaceAll(",", " ") }, { status: response.status });
18
18
  }
@@ -34,7 +34,7 @@ function repositoryOperation(suffix, fields, validateResult, method = "GET") {
34
34
  for (const [key, value] of Object.entries(values)) url.searchParams.set(key, String(value));
35
35
  return { method, url: url.href };
36
36
  }
37
- if (!Object.keys(values).length) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422 });
37
+ if (!Object.keys(values).length) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422, messageCode: "connectors.shared.supplyLeastChange" });
38
38
  return { method, url: url.href, body: values };
39
39
  }, validateResult };
40
40
  }
@@ -21,7 +21,7 @@ function projectOperation(suffix, fields, validateResult, method = "GET") {
21
21
  for (const [key, value] of Object.entries(values)) url.searchParams.set(key, String(value));
22
22
  return { method, url: url.href };
23
23
  }
24
- if (!Object.keys(values).length) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422 });
24
+ if (!Object.keys(values).length) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422, messageCode: "connectors.shared.supplyLeastChange" });
25
25
  return { method, url: url.href, body: values };
26
26
  }, validateResult };
27
27
  }
@@ -25,7 +25,7 @@ const gmailProvider = Object.freeze({ ...googleProvider(gmailDefinition, origin,
25
25
  "attachments.get": googleRead(contentScopes, { messageId: documentId, id: documentId }, ({ messageId, id }) => ({ url: `${origin}/gmail/v1/users/me/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(id)}` }), value => Number.isInteger(value?.size) && typeof value.data === "string"),
26
26
  "messages.send": gmailWrite(["gmail.send", ...composeScopes], { raw: rawField, threadId: { ...documentId, required: false } }, body => ({ path: "/messages/send", body }), messageResult),
27
27
  "messages.modify": gmailWrite(modifyScopes, { id: documentId, addLabelIds: labelIds, removeLabelIds: labelIds }, ({ id, ...body }) => {
28
- if (!body.addLabelIds?.length && !body.removeLabelIds?.length) throw new ConnectorError("connector_input_invalid", "Supply labels to add or remove.", { statusCode: 422 });
28
+ if (!body.addLabelIds?.length && !body.removeLabelIds?.length) throw new ConnectorError("connector_input_invalid", "Supply labels to add or remove.", { statusCode: 422, messageCode: "connectors.gmail.supplyLabelsAddRemove" });
29
29
  return { path: `/messages/${encodeURIComponent(id)}/modify`, body };
30
30
  }, messageResult),
31
31
  "messages.trash": gmailWrite(modifyScopes, { id: documentId }, ({ id }) => ({ path: `/messages/${encodeURIComponent(id)}/trash` }), messageResult),