@jskit-ai/connectors-catalog 0.2.3 → 0.2.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.
- package/README.md +4 -4
- package/docs/ai.md +3 -3
- package/docs/airtable.md +1 -1
- package/docs/algolia.md +2 -2
- package/docs/amazon-redshift.md +2 -2
- package/docs/apollo-io.md +2 -2
- package/docs/asana.md +2 -2
- package/docs/ashby.md +1 -1
- package/docs/attention.md +2 -2
- package/docs/aws-athena.md +1 -1
- package/docs/aws-credentials.md +3 -3
- package/docs/aws-s3.md +1 -1
- package/docs/bigquery.md +2 -2
- package/docs/brevo.md +2 -2
- package/docs/calendly.md +3 -3
- package/docs/canva.md +2 -2
- package/docs/chargebee.md +2 -2
- package/docs/clay.md +2 -2
- package/docs/clickhouse.md +1 -1
- package/docs/confidence-flags.md +1 -1
- package/docs/contentful.md +1 -1
- package/docs/dbt-semantic-layer.md +2 -2
- package/docs/elevenlabs.md +2 -2
- package/docs/firebase-cloud-messaging.md +3 -3
- package/docs/firecrawl.md +4 -4
- package/docs/fireflies.md +1 -1
- package/docs/gatewayapi.md +1 -1
- package/docs/gemini-enterprise.md +1 -1
- package/docs/github-api.md +1 -1
- package/docs/gitlab-api.md +1 -1
- package/docs/gmail.md +1 -1
- package/docs/gong.md +1 -1
- package/docs/google-ads.md +3 -3
- package/docs/google-analytics.md +2 -2
- package/docs/google-docs.md +1 -1
- package/docs/google-drive.md +1 -1
- package/docs/google-maps-platform.md +2 -2
- package/docs/google-search-console.md +1 -1
- package/docs/google-sheets.md +1 -1
- package/docs/google-slides.md +2 -2
- package/docs/granola.md +2 -2
- package/docs/hex.md +2 -2
- package/docs/heygen.md +2 -2
- package/docs/inngest.md +1 -1
- package/docs/klipy.md +1 -1
- package/docs/lightspeed.md +2 -2
- package/docs/linear.md +1 -1
- package/docs/linkedin.md +2 -2
- package/docs/mailgun.md +1 -1
- package/docs/microsoft-excel.md +2 -2
- package/docs/microsoft-fabric.md +1 -1
- package/docs/microsoft-outlook.md +1 -1
- package/docs/microsoft-powerpoint.md +1 -1
- package/docs/microsoft-teams.md +1 -1
- package/docs/microsoft-word.md +1 -1
- package/docs/notion.md +1 -1
- package/docs/oura.md +3 -3
- package/docs/paddle.md +3 -3
- package/docs/pipedrive.md +2 -2
- package/docs/prestashop.md +1 -1
- package/docs/replicate.md +1 -1
- package/docs/resend.md +1 -1
- package/docs/salesforce.md +1 -1
- package/docs/sanity.md +3 -3
- package/docs/semrush.md +2 -2
- package/docs/sentry.md +1 -1
- package/docs/slack.md +1 -1
- package/docs/snowflake.md +5 -5
- package/docs/storyblok.md +1 -1
- package/docs/stripe.md +2 -2
- package/docs/tally.md +1 -1
- package/docs/telegram.md +2 -2
- package/docs/tiktok.md +3 -3
- package/docs/twilio.md +3 -3
- package/docs/twitch.md +2 -2
- package/docs/wave.md +3 -3
- package/docs/wix.md +13 -13
- package/docs/wordpress-com.md +1 -1
- package/docs/wordpress-self-hosted.md +1 -1
- package/docs/workday.md +2 -2
- package/docs/x-twitter.md +3 -3
- package/docs/xero.md +3 -3
- package/docs/zoho-books.md +2 -2
- package/docs/zoho-crm.md +2 -2
- package/package.json +4 -3
- package/patterns/ai-connections/PATTERN.md +16 -1
- package/patterns/ai-connections/example/ai-model.js +12 -3
- package/patterns/ai-connections/example/perplexity-answer.js +15 -4
- package/patterns/api-key-connection/PATTERN.md +9 -1
- package/patterns/api-key-connection/example/connections.js +5 -2
- package/patterns/aws-storage-queries/PATTERN.md +13 -3
- package/patterns/aws-storage-queries/example/s3-transfer.js +14 -3
- package/patterns/event-delivery/PATTERN.md +1 -1
- package/patterns/firebase-messaging/PATTERN.md +10 -0
- package/patterns/firebase-messaging/example/server/notifications.js +8 -0
- package/patterns/google-ads-search/PATTERN.md +18 -7
- package/patterns/google-ads-search/example/ads-setup.js +13 -5
- package/patterns/oauth-connection/PATTERN.md +1 -1
- package/patterns/paddle-catalogue/PATTERN.md +11 -2
- package/patterns/paddle-catalogue/example/create-products.js +5 -2
- package/patterns/public-image/PATTERN.md +10 -1
- package/patterns/public-image/example/logo-url.js +4 -1
- package/patterns/redshift-queries/PATTERN.md +1 -1
- package/patterns/source-scanning/PATTERN.md +13 -2
- package/patterns/source-scanning/example/source-scanner.js +9 -0
- package/src/server/ai.js +19 -5
- package/src/server/apify.js +2 -2
- package/src/server/asana.js +6 -6
- package/src/server/ashby.js +2 -1
- package/src/server/attention.js +12 -11
- package/src/server/awsTransport.js +6 -0
- package/src/server/bigquery.js +5 -4
- package/src/server/brevo.js +2 -1
- package/src/server/clay.js +2 -1
- package/src/server/clickhouse.js +2 -1
- package/src/server/confidence.js +6 -1
- package/src/server/connectorValues.js +33 -0
- package/src/server/databricks.js +7 -7
- package/src/server/dbt-semantic-layer.js +5 -5
- package/src/server/elevenlabs.js +2 -2
- package/src/server/firebase-cloud-messaging.js +2 -2
- package/src/server/gemini-enterprise.js +5 -5
- package/src/server/google-ads-search.js +68 -48
- package/src/server/google-ads.js +15 -17
- package/src/server/google-drive.js +7 -7
- package/src/server/google-maps-platform.js +4 -2
- package/src/server/google-search-console.js +4 -3
- package/src/server/google.js +8 -4
- package/src/server/googleAdsApi.js +14 -0
- package/src/server/granola.js +2 -2
- package/src/server/hubspot.js +2 -1
- package/src/server/incident-io.js +3 -2
- package/src/server/lexware.js +5 -4
- package/src/server/lightspeed.js +7 -5
- package/src/server/linkedin.js +2 -2
- package/src/server/mcpTransport.js +8 -4
- package/src/server/microsoft-fabric.js +3 -3
- package/src/server/microsoft-onedrive.js +2 -2
- package/src/server/microsoft-onenote.js +4 -3
- package/src/server/microsoft-outlook.js +6 -6
- package/src/server/microsoft-sharepoint.js +4 -2
- package/src/server/microsoft-teams.js +2 -1
- package/src/server/microsoft.js +20 -13
- package/src/server/notion.js +2 -1
- package/src/server/pipedrive.js +2 -1
- package/src/server/salesforce.js +12 -12
- package/src/server/semrush.js +9 -8
- package/src/server/snowflake.js +20 -20
- package/src/server/storyblok.js +2 -1
- package/src/server/tally.js +2 -1
- package/src/server/telegram.js +2 -6
- package/src/server/tiktok.js +3 -3
- package/src/server/twilio.js +3 -2
- package/src/server/twitch.js +5 -3
- package/src/server/types.js +13 -0
- package/src/server/wix.js +20 -878
- package/src/server/wixBookings.js +449 -0
- package/src/server/wixCms.js +132 -0
- package/src/server/wixStores.js +296 -0
- package/src/server/wixValues.js +35 -0
- package/src/server/wiz.js +18 -4
- package/src/server/woocommerce.js +1 -1
- package/src/server/wordpress-com.js +2 -1
- package/src/server/workday.js +9 -9
- package/src/server/x-twitter.js +2 -1
- package/src/server/xero.js +40 -40
- package/src/server/zoho-books.js +6 -6
- package/src/server/zoho-crm.js +5 -5
- package/src/shared/confidence.js +6 -1
- package/src/shared/dbt-semantic-layer.js +1 -1
- package/src/shared/figma.js +1 -1
- package/src/shared/google.js +8 -4
- package/src/shared/googleScope.js +4 -0
- package/src/shared/microsoft-fabric.js +4 -5
- package/src/shared/microsoft.js +8 -3
- package/src/shared/salesforce.js +1 -1
- package/src/shared/tokens.js +7 -3
- package/src/shared/uuid.js +13 -0
- package/src/shared/wix.js +4 -3
- package/src/shared/zoho-books.js +3 -4
- package/src/shared/zoho-crm.js +1 -1
- package/src/shared/{zoho-regions.js → zoho.js} +4 -1
- package/test/ai.test.js +1 -1
- package/test/brevo.test.js +1 -1
- package/test/chargebee.test.js +1 -1
- package/test/google-ads.test.js +8 -4
- package/test/linkedin.test.js +1 -1
- package/test/resend.test.js +2 -2
- package/test/salesforce.test.js +1 -1
- package/test/snowflake.test.js +7 -7
- package/test/tiktok.test.js +1 -1
- package/test/wiz.test.js +1 -1
|
@@ -38,6 +38,12 @@ the ordinary runtime for each request, preserving authorization and the
|
|
|
38
38
|
connection's configured sandbox/live environment. The sandbox setting used for
|
|
39
39
|
local schema validation sends no request and does not override runtime routing.
|
|
40
40
|
|
|
41
|
+
Each collaborator the composition receives (the connection `runtime`) is a
|
|
42
|
+
required parameter typed in a JSDoc `@param` and called directly, without a
|
|
43
|
+
`typeof` probe, a "requires x" guard or a no-op default. A server feature that
|
|
44
|
+
uses it follows
|
|
45
|
+
[typed features](https://github.com/mobily-enterprises/jskit-ai/blob/main/packages/agent-docs/site/guide/framework/typed-features.md).
|
|
46
|
+
|
|
41
47
|
## Product decisions
|
|
42
48
|
|
|
43
49
|
For assistant use, authorize the complete cloned request with
|
|
@@ -58,12 +64,15 @@ termination can prevent the returned report from being saved, so an application
|
|
|
58
64
|
needing crash recovery must record each completed step durably. This small
|
|
59
65
|
recipe does not promise crash recovery, rollback or exactly-once execution.
|
|
60
66
|
|
|
67
|
+
Do not probe a collaborator, default one to `null`, `{}` or a no-op, fill a slot
|
|
68
|
+
with a setter after construction, or keep a module-level registry.
|
|
69
|
+
|
|
61
70
|
## Variation points
|
|
62
71
|
|
|
63
|
-
CLI users can call this same JavaScript recipe with their own runtime.
|
|
72
|
+
CLI users can call this same JavaScript recipe with their own runtime. An editor
|
|
64
73
|
configures the project; it does not execute catalogue writes on configuration
|
|
65
74
|
save. Laravel applications implement this composition with their own framework
|
|
66
|
-
and Paddle client, without importing JavaScript or depending on
|
|
75
|
+
and Paddle client, without importing JavaScript or depending on an editor.
|
|
67
76
|
|
|
68
77
|
## Example files
|
|
69
78
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { paddleProvider } from "@jskit-ai/connectors-catalog/server/paddle";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Application-owned composition: pass an application-mode connection runtime.
|
|
5
|
+
* An assistant host must approve the complete batch before calling this recipe.
|
|
6
|
+
* @param {{ runtime: import("@jskit-ai/connectors-core/server/types").ConnectorsCore, context: object, integrationId: string, items: object[], signal?: AbortSignal }} request
|
|
7
|
+
*/
|
|
5
8
|
async function createProductsWithPrices({ runtime, context, integrationId, items, signal }) {
|
|
6
9
|
if (!Array.isArray(items) || !items.length || items.length > 100) {
|
|
7
10
|
throw new TypeError("Supply between 1 and 100 products with prices.");
|
|
@@ -29,12 +29,18 @@ existing access policy applies before exposing its configured resources.
|
|
|
29
29
|
|
|
30
30
|
## Invariants
|
|
31
31
|
|
|
32
|
-
- CLI and
|
|
32
|
+
- The CLI and a hosted editor edit the same reference-only `integrations.json`.
|
|
33
33
|
- Only a `pk_` key enters an image URL; a private key fails validation.
|
|
34
34
|
- URL creation is local and does not establish a verified account connection.
|
|
35
35
|
- Browser load/error events report delivery. A monogram is not proof of a brand logo.
|
|
36
36
|
- Domain restrictions concern the app that loads the image, including previews
|
|
37
37
|
and custom domains. The editor VM may have a different origin.
|
|
38
|
+
- Each collaborator the composition receives (`resolveReference`) is a required
|
|
39
|
+
parameter typed in a JSDoc `@param` and called directly, without a `typeof`
|
|
40
|
+
probe, a "requires x" guard or a no-op default. It is typed with
|
|
41
|
+
`ResolveReference` from `@jskit-ai/connectors-core/server/types`. A server
|
|
42
|
+
feature that uses it follows
|
|
43
|
+
[typed features](https://github.com/mobily-enterprises/jskit-ai/blob/main/packages/agent-docs/site/guide/framework/typed-features.md).
|
|
38
44
|
|
|
39
45
|
## Framework APIs
|
|
40
46
|
|
|
@@ -82,3 +88,6 @@ These checks need neither live credentials nor generated sample applications.
|
|
|
82
88
|
Do not call server `connectApiKey` to manufacture a Connected receipt for this
|
|
83
89
|
image flow. Do not send a secret key, an environment dump or a caller-selected
|
|
84
90
|
service origin to the browser. Do not treat key naming as provider quota isolation.
|
|
91
|
+
|
|
92
|
+
Do not probe a collaborator, default one to `null`, `{}` or a no-op, fill a slot
|
|
93
|
+
with a setter after construction, or keep a module-level registry.
|
|
@@ -2,7 +2,10 @@ import { parseIntegrationConfiguration } from "@jskit-ai/connectors-core/shared/
|
|
|
2
2
|
import { logoDevDefinition } from "@jskit-ai/connectors-catalog/shared";
|
|
3
3
|
import { createLogoDevImageUrl } from "@jskit-ai/connectors-catalog/client/logo-dev";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The application authorizes access before passing its configuration and resolver.
|
|
7
|
+
* @param {{ configurationText: string, integrationId: string, resolveReference: import("@jskit-ai/connectors-core/server/types").ResolveReference, image: object }} request
|
|
8
|
+
*/
|
|
6
9
|
async function logoUrlFromConfiguration({ configurationText, integrationId, resolveReference, image }) {
|
|
7
10
|
const configuration = parseIntegrationConfiguration(configurationText, { providers: [logoDevDefinition], allowUnknownProviders: true });
|
|
8
11
|
const integration = configuration.integrations[integrationId];
|
|
@@ -24,7 +24,7 @@ SQL can modify data; read-only behavior requires appropriate database grants.
|
|
|
24
24
|
|
|
25
25
|
## Product decisions
|
|
26
26
|
|
|
27
|
-
Select serverless or provisioned in the same JSON
|
|
27
|
+
Select serverless or provisioned in the same JSON the editor edits. Serverless
|
|
28
28
|
requires a workgroup name; provisioned requires a cluster identifier and may use
|
|
29
29
|
an explicit database user. Both require a database and region. Store AWS secrets
|
|
30
30
|
outside the file. Temporary credentials additionally need `sessionTokenRef` and
|
|
@@ -18,7 +18,7 @@ The example wires that library to application-owned authority and snapshot looku
|
|
|
18
18
|
## Do not use when
|
|
19
19
|
|
|
20
20
|
This is not an HTTP application-data connector, user login, a managed gateway,
|
|
21
|
-
or an implementation of
|
|
21
|
+
or an implementation of a hosted editor's coding agents. It does not generate an app.
|
|
22
22
|
Auth0 and native Windows execution are not implemented in the current runner.
|
|
23
23
|
|
|
24
24
|
## Product decisions
|
|
@@ -39,7 +39,15 @@ and whether a blocking verdict prevents a later operation.
|
|
|
39
39
|
- Missing/invalid reports, cancellation and failed scans never become clean scans.
|
|
40
40
|
- Provider credentials are isolated from coding-agent sessions and credentials.
|
|
41
41
|
- The host supplies its own authorized provider credentials; editor edition does
|
|
42
|
-
not select a shared
|
|
42
|
+
not select a shared hosted provider account.
|
|
43
|
+
- Each collaborator the composition receives (`authorize`, `resolveReference`
|
|
44
|
+
and `resolveScanTarget`) is a required parameter typed in a JSDoc `@param` and
|
|
45
|
+
called directly, without a `typeof` probe, a "requires x" guard or a no-op
|
|
46
|
+
default. `authorize` and `resolveReference` are typed with `ConnectorAuthorize`
|
|
47
|
+
and `ResolveReference` from `@jskit-ai/connectors-core/server/types`, and
|
|
48
|
+
`resolveScanTarget` with `WizScanTargetResolver` from
|
|
49
|
+
`@jskit-ai/connectors-catalog/server/types`. A server feature that uses it follows
|
|
50
|
+
[typed features](https://github.com/mobily-enterprises/jskit-ai/blob/main/packages/agent-docs/site/guide/framework/typed-features.md).
|
|
43
51
|
|
|
44
52
|
## Framework APIs
|
|
45
53
|
|
|
@@ -81,3 +89,6 @@ and findings require separately authorised provider acceptance.
|
|
|
81
89
|
Do not infer workspace grants from a shared slot, return raw provider stderr,
|
|
82
90
|
replay scans automatically, pass arbitrary CLI flags, use a mutable working tree
|
|
83
91
|
as if it were a fixed revision, or substitute a generic API reader for source scans.
|
|
92
|
+
|
|
93
|
+
Do not probe a collaborator, default one to `null`, `{}` or a no-op, fill a slot
|
|
94
|
+
with a setter after construction, or keep a module-level registry.
|
|
@@ -3,6 +3,15 @@ import { parseIntegrationConfiguration } from "@jskit-ai/connectors-core/shared/
|
|
|
3
3
|
import { wizDefinition } from "@jskit-ai/connectors-catalog/shared";
|
|
4
4
|
import { createWizScanner } from "@jskit-ai/connectors-catalog/server/wiz";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @param {object} dependencies
|
|
8
|
+
* @param {string} [dependencies.configFile]
|
|
9
|
+
* @param {string} dependencies.installedWizCliPath
|
|
10
|
+
* @param {import("@jskit-ai/connectors-core/server/types").ConnectorAuthorize} dependencies.authorize The application's access policy.
|
|
11
|
+
* @param {import("@jskit-ai/connectors-core/server/types").ResolveReference} dependencies.resolveReference
|
|
12
|
+
* @param {import("@jskit-ai/connectors-catalog/server/types").WizScanTargetResolver} dependencies.resolveScanTarget
|
|
13
|
+
* @param {Record<string, string>} [dependencies.environment]
|
|
14
|
+
*/
|
|
6
15
|
export async function projectSourceScanner({ configFile = "integrations.json", installedWizCliPath,
|
|
7
16
|
authorize, resolveReference, resolveScanTarget, environment }) {
|
|
8
17
|
const configuration = parseIntegrationConfiguration(await readFile(configFile, "utf8"), {
|
package/src/server/ai.js
CHANGED
|
@@ -2,9 +2,17 @@ import { ConnectorError, createEnvironmentReferenceResolver } from "@jskit-ai/co
|
|
|
2
2
|
import { validateIntegrationConfiguration } from "@jskit-ai/connectors-core/shared/configuration";
|
|
3
3
|
import { aiCatalogue, aiDefinition, getAiModel } from "../shared/ai.js";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/**
|
|
6
|
+
* A server-only credential boundary. The consuming application's SDK owns all
|
|
7
|
+
* inference, transport, streaming, tool execution and error/fallback policy.
|
|
8
|
+
* @param {object} options
|
|
9
|
+
* @param {Record<string, any>} options.configuration The integration configuration.
|
|
10
|
+
* @param {import("@jskit-ai/connectors-core/server/types").ConnectorAuthorize} options.authorize
|
|
11
|
+
* @param {import("@jskit-ai/connectors-core/server/types").ResolveReference} [options.resolveReference]
|
|
12
|
+
* Reads `env:NAME` references from the environment unless the application resolves its own.
|
|
13
|
+
*/
|
|
14
|
+
function createAiConnectionResolver({ configuration, authorize, resolveReference = createEnvironmentReferenceResolver(process.env) }) {
|
|
15
|
+
// readable-check: allow method-probe - input validation: the application passes its access policy
|
|
8
16
|
if (typeof authorize !== "function") throw new TypeError("AI connections require application authorization.");
|
|
9
17
|
const config = validateIntegrationConfiguration(configuration, { providers: [aiDefinition], allowUnknownProviders: true });
|
|
10
18
|
|
|
@@ -37,9 +45,15 @@ function createAiConnectionResolver({ configuration, authorize, resolveReference
|
|
|
37
45
|
}
|
|
38
46
|
// Never expose this result through a browser API or include it in logs.
|
|
39
47
|
// Missing baseURL means use the named provider SDK's own default endpoint.
|
|
40
|
-
return Object.freeze({
|
|
48
|
+
return Object.freeze({
|
|
49
|
+
providerId: provider.id,
|
|
50
|
+
model: model.id,
|
|
51
|
+
sdkPackage: model.sdkPackage,
|
|
41
52
|
modelLimits: Object.freeze({ ...model.limit }),
|
|
42
|
-
...(provider.baseURL ? { baseURL: provider.baseURL } : {}),
|
|
53
|
+
...(provider.baseURL ? { baseURL: provider.baseURL } : {}),
|
|
54
|
+
apiKey,
|
|
55
|
+
access: model.access
|
|
56
|
+
});
|
|
43
57
|
}
|
|
44
58
|
return Object.freeze({ resolve });
|
|
45
59
|
}
|
package/src/server/apify.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
1
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
2
2
|
import { apifyDefinition } from "../shared/tokens.js";
|
|
3
3
|
import { jsonOperation, validatedOperation } from "./jsonOperation.js";
|
|
4
4
|
|
|
@@ -13,7 +13,7 @@ const apifyProvider = Object.freeze({
|
|
|
13
13
|
const response = await fetchImpl(url, { ...options, credentials: "omit", redirect: "error" });
|
|
14
14
|
if (!response.ok) {
|
|
15
15
|
await response.body?.cancel();
|
|
16
|
-
throw
|
|
16
|
+
throw providerStatusError(response.status);
|
|
17
17
|
}
|
|
18
18
|
const reader = response.body?.getReader();
|
|
19
19
|
const chunks = [];
|
package/src/server/asana.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
2
2
|
import { asanaDefinition } from "../shared/tokens.js";
|
|
3
3
|
import { jsonOperation, validatedOperation } from "./jsonOperation.js";
|
|
4
|
+
import { hasDataList } from "./connectorValues.js";
|
|
4
5
|
|
|
5
6
|
const gid = { type: "string", minLength: 1, maxLength: 100, pattern: "^[0-9]+$" };
|
|
6
7
|
const requiredGid = { ...gid, required: true };
|
|
@@ -17,7 +18,6 @@ const project = { ...common, archived: { type: "boolean" }, owner: { ...gid, nul
|
|
|
17
18
|
const taskFields = "gid,name,notes,completed,assignee.gid,assignee.name,due_on,start_on,projects.gid,parent.gid";
|
|
18
19
|
const projectFields = "gid,name,notes,archived,owner.gid,privacy_setting,due_on,start_on,workspace.gid";
|
|
19
20
|
const record = result => typeof result?.data?.gid === "string";
|
|
20
|
-
const list = result => Array.isArray(result?.data);
|
|
21
21
|
function read(endpoint, input = {}, fields) {
|
|
22
22
|
const url = new URL(`https://app.asana.com/api/1.0/${endpoint}`);
|
|
23
23
|
for (const [key, value] of Object.entries(input)) url.searchParams.set(key, String(value));
|
|
@@ -40,21 +40,21 @@ const asanaProvider = Object.freeze({
|
|
|
40
40
|
apiKey: { headers: (key) => ({ Authorization: `Bearer ${key}` }) },
|
|
41
41
|
checkOperation: "workspaces.list",
|
|
42
42
|
operations: {
|
|
43
|
-
"workspaces.list": jsonOperation("https://app.asana.com/api/1.0/workspaces", page,
|
|
43
|
+
"workspaces.list": jsonOperation("https://app.asana.com/api/1.0/workspaces", page, hasDataList),
|
|
44
44
|
"projects.list": validatedOperation({ workspace: requiredGid, ...page, archived: { type: "boolean" } },
|
|
45
|
-
input => read("projects", input, projectFields),
|
|
45
|
+
input => read("projects", input, projectFields), hasDataList),
|
|
46
46
|
"projects.get": validatedOperation({ gid: requiredGid }, ({ gid }) => read(`projects/${gid}`, {}, projectFields), record),
|
|
47
47
|
"projects.create": write("POST", "projects", { ...project, name: { ...common.name, required: true },
|
|
48
48
|
workspace: requiredGid, privacy_setting: { ...project.privacy_setting, required: true } }, projectFields),
|
|
49
49
|
"projects.update": write("PUT", "projects", project, projectFields),
|
|
50
50
|
"tasks.list": validatedOperation({ project: requiredGid, ...page, completed_since: { type: "string", maxLength: 100 } },
|
|
51
|
-
({ project, ...input }) => read(`projects/${project}/tasks`, input, taskFields),
|
|
51
|
+
({ project, ...input }) => read(`projects/${project}/tasks`, input, taskFields), hasDataList),
|
|
52
52
|
"tasks.get": validatedOperation({ gid: requiredGid }, ({ gid }) => read(`tasks/${gid}`, {}, taskFields), record),
|
|
53
53
|
"tasks.create": write("POST", "tasks", { ...task, name: { ...common.name, required: true }, workspace: requiredGid,
|
|
54
54
|
projects: { type: "array", items: gid, validator: values => values.length <= 100 || "Choose at most 100 projects." } }, taskFields),
|
|
55
55
|
"tasks.update": write("PUT", "tasks", task, taskFields),
|
|
56
|
-
"users.list": validatedOperation({ workspace: requiredGid, ...page }, ({ workspace, ...input }) => read(`workspaces/${workspace}/users`, input),
|
|
57
|
-
"teams.list": validatedOperation({ workspace: requiredGid, ...page }, ({ workspace, ...input }) => read(`workspaces/${workspace}/teams`, input),
|
|
56
|
+
"users.list": validatedOperation({ workspace: requiredGid, ...page }, ({ workspace, ...input }) => read(`workspaces/${workspace}/users`, input), hasDataList),
|
|
57
|
+
"teams.list": validatedOperation({ workspace: requiredGid, ...page }, ({ workspace, ...input }) => read(`workspaces/${workspace}/teams`, input), hasDataList),
|
|
58
58
|
"projectMembers.add": validatedOperation({ project: requiredGid, member: requiredGid,
|
|
59
59
|
access_level: { type: "string", required: true, enum: ["admin", "editor", "commenter"] } },
|
|
60
60
|
({ project, member, access_level }) => ({ method: "POST", url: "https://app.asana.com/api/1.0/memberships",
|
package/src/server/ashby.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
2
2
|
import { ashbyDefinition } from "../shared/tokens.js";
|
|
3
3
|
import { jsonOperation } from "./jsonOperation.js";
|
|
4
|
+
import { UUID_SOURCE } from "../shared/uuid.js";
|
|
4
5
|
|
|
5
6
|
const text = { type: "string", minLength: 1, maxLength: 256 };
|
|
6
7
|
const uuid = { type: "string", required: true,
|
|
7
|
-
pattern:
|
|
8
|
+
pattern: `^${UUID_SOURCE}$` };
|
|
8
9
|
const page = { limit: { type: "integer", min: 1, max: 100, defaultTo: 25 },
|
|
9
10
|
cursor: { type: "string", minLength: 1, maxLength: 8192 }, syncToken: { type: "string", minLength: 1, maxLength: 8192 } };
|
|
10
11
|
const candidate = { name: text, email: { ...text, pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" }, phoneNumber: text };
|
package/src/server/attention.js
CHANGED
|
@@ -2,6 +2,8 @@ import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
|
2
2
|
import { createSchema } from "json-rest-schema";
|
|
3
3
|
import { attentionDefinition } from "../shared/tokens.js";
|
|
4
4
|
import { jsonOperation, validatedOperation, withQuery } from "./jsonOperation.js";
|
|
5
|
+
import { hasDataList } from "./connectorValues.js";
|
|
6
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
7
|
|
|
6
8
|
const text = { type: "string", minLength: 1, maxLength: 2000 };
|
|
7
9
|
const id = { type: "string", minLength: 1, maxLength: 100, pattern: "^[A-Za-z0-9_-]+$" };
|
|
@@ -10,9 +12,8 @@ const ids = { type: "array", items: id, validator: value => value.length <= 100
|
|
|
10
12
|
const bool = { type: "boolean" };
|
|
11
13
|
const page = { page: { type: "integer", min: 1, defaultTo: 1 }, size: { type: "integer", min: 1, max: 200, defaultTo: 50 } };
|
|
12
14
|
const dateTime = { ...text, validator: value => /^\d{4}-\d{2}-\d{2}T/.test(value) && Number.isFinite(Date.parse(value)) || "Use an ISO timestamp." };
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const conversation = result => result?.type === "conversations" && typeof result.id === "string" && result.attributes !== null && typeof result.attributes === "object" && !Array.isArray(result.attributes);
|
|
15
|
+
const record = result => isRecord(result?.data) && typeof (result.data.uuid ?? result.data.id) === "string";
|
|
16
|
+
const conversation = result => result?.type === "conversations" && typeof result.id === "string" && isRecord(result.attributes);
|
|
16
17
|
function operation(method, endpoint, fields, validateResult, prepare = value => value) {
|
|
17
18
|
return validatedOperation(fields, values => {
|
|
18
19
|
const { id: resource, ...body } = prepare(values);
|
|
@@ -49,19 +50,19 @@ const attentionProvider = Object.freeze({
|
|
|
49
50
|
"analysis.ask": operation("POST", "ask_attention/v2", { conversations_ids: { ...ids, required: true },
|
|
50
51
|
deal_id: { type: "string", required: true, maxLength: 100 }, prompt: { ...text, required: true, maxLength: 20000 }
|
|
51
52
|
}, result => Array.isArray(result) && result.every(item => typeof item?.output === "string" && typeof item.error === "string" && typeof item.conversation_id === "string")),
|
|
52
|
-
"scorecards.list": operation("GET", "scorecards", page,
|
|
53
|
+
"scorecards.list": operation("GET", "scorecards", page, hasDataList),
|
|
53
54
|
"scorecards.get": operation("GET", "scorecards/{id}", { id: requiredId }, record),
|
|
54
|
-
"scorecardItems.list": operation("GET", "scorecards/{id}/items", { id: requiredId, ...page },
|
|
55
|
+
"scorecardItems.list": operation("GET", "scorecards/{id}/items", { id: requiredId, ...page }, hasDataList),
|
|
55
56
|
"scorecards.summary": operation("POST", "scorecards/summary", { fromDateTime: { ...dateTime, required: true }, toDateTime: { ...dateTime, required: true },
|
|
56
57
|
scorecardUUID: requiredId, scorecardsItemsUUIDs: { ...ids, required: true }, teamUUIDs: { ...ids, required: true }, userUUIDs: { ...ids, required: true }
|
|
57
|
-
},
|
|
58
|
+
}, hasDataList, 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
59
|
"scorecardResults.create": operation("POST", "createScorecardResult", { scorecard_uuid: requiredId, conversation_uuid: requiredId,
|
|
59
60
|
summary: { ...text, required: true, maxLength: 20000 }, items: { type: "array", required: true,
|
|
60
61
|
items: { type: "object", schema: createSchema({ scorecard_item_uuid: requiredId, numeric_result: { type: "integer" }, description: { ...text, required: true } }) },
|
|
61
62
|
validator: value => value.length > 0 && value.length <= 100 || "Supply 1–100 scoring items." }
|
|
62
63
|
}, result => result?.success === true),
|
|
63
|
-
"users.list": operation("GET", "organizations/users", { teamUUID: id },
|
|
64
|
-
"roles.list": operation("GET", "organizations/roles", {},
|
|
64
|
+
"users.list": operation("GET", "organizations/users", { teamUUID: id }, hasDataList),
|
|
65
|
+
"roles.list": operation("GET", "organizations/roles", {}, hasDataList),
|
|
65
66
|
"users.create": operation("POST", "organizations/users", { email: { ...text, required: true, pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" },
|
|
66
67
|
first_name: { ...text, required: true }, last_name: { ...text, required: true }, roleUUID: requiredId,
|
|
67
68
|
seat_type: { type: "string", required: true, enum: ["listener", "recording"] },
|
|
@@ -71,8 +72,8 @@ const attentionProvider = Object.freeze({
|
|
|
71
72
|
roleUUID: id, seat_type: { type: "string", enum: ["listener", "recording"] }, teamUUIDsToRemove: ids,
|
|
72
73
|
teamsToAdd: { type: "array", items: team, validator: value => value.length <= 100 || "Use at most 100 teams." }
|
|
73
74
|
}, record),
|
|
74
|
-
"users.delete": operation("DELETE", "organizations/users/{id}", { id: requiredId }, result => result === null ||
|
|
75
|
-
"teams.list": operation("GET", "organizations/teams", {},
|
|
75
|
+
"users.delete": operation("DELETE", "organizations/users/{id}", { id: requiredId }, result => result === null || isRecord(result) && Object.keys(result).length === 0),
|
|
76
|
+
"teams.list": operation("GET", "organizations/teams", {}, hasDataList),
|
|
76
77
|
"teams.create": operation("POST", "organizations/teams", { name: { ...text, required: true }, parentTeamUUID: id }, record),
|
|
77
78
|
"teams.update": operation("PATCH", "organizations/teams/{id}", { id: requiredId, name: text, parentTeamUUID: id }, record),
|
|
78
79
|
"conversations.list": jsonOperation("https://api.attention.tech/v2/conversations/list", {
|
|
@@ -82,7 +83,7 @@ const attentionProvider = Object.freeze({
|
|
|
82
83
|
"filter[hide_internal]": { type: "boolean" },
|
|
83
84
|
detailedTranscript: { type: "boolean", defaultTo: false }
|
|
84
85
|
}, (result) => Array.isArray(result?.data) && (result.meta === undefined ||
|
|
85
|
-
|
|
86
|
+
isRecord(result.meta) &&
|
|
86
87
|
["totalRecords", "pageCount", "pageNumber", "pageSize"].every((field) => result.meta[field] === undefined ||
|
|
87
88
|
Number.isInteger(result.meta[field]) && result.meta[field] >= 0)))
|
|
88
89
|
}
|
|
@@ -18,6 +18,12 @@ function awsOperation(service, command, fields, makeInput, validateResult, scope
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function awsExchange(service, Client, execute) {
|
|
21
|
+
/**
|
|
22
|
+
* Sends one AWS request, signed with the connection's access key bindings.
|
|
23
|
+
* @param {string} endpoint
|
|
24
|
+
* @param {object} options
|
|
25
|
+
* @param {import("@jskit-ai/connectors-core/server/types").ConnectorExchangeTransport} transport
|
|
26
|
+
*/
|
|
21
27
|
return async (endpoint, options, { settings, apiKey, resolveReference, fetchImpl }) => {
|
|
22
28
|
let credentials;
|
|
23
29
|
try {
|
package/src/server/bigquery.js
CHANGED
|
@@ -3,6 +3,7 @@ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
|
3
3
|
import { jsonOperation, validatedOperation } from "./jsonOperation.js";
|
|
4
4
|
import { bigqueryDefinition } from "../shared/google.js";
|
|
5
5
|
import { googleRead, googleProvider, pageToken } from "./google.js";
|
|
6
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
6
7
|
const origin = "https://bigquery.googleapis.com";
|
|
7
8
|
const queryScopes = ["https://www.googleapis.com/auth/bigquery", "https://www.googleapis.com/auth/cloud-platform"];
|
|
8
9
|
const resultFields = {
|
|
@@ -13,9 +14,9 @@ const resultFields = {
|
|
|
13
14
|
const resultsSchema = createSchema({ ...resultFields,
|
|
14
15
|
jobId: { type: "string", required: true, minLength: 1, maxLength: 1024, pattern: "^[a-zA-Z0-9_-]+$" }, pageToken: { ...pageToken, noTrim: true }
|
|
15
16
|
});
|
|
16
|
-
const validQueryResult = (value) => Boolean(
|
|
17
|
+
const validQueryResult = (value) => Boolean(isRecord(value) && !value.error &&
|
|
17
18
|
typeof value.jobComplete === "boolean" && (value.rows === undefined || Array.isArray(value.rows) && value.rows.every((row) =>
|
|
18
|
-
Array.isArray(row?.f) && row.f.every((cell) =>
|
|
19
|
+
Array.isArray(row?.f) && row.f.every((cell) => isRecord(cell) && Object.hasOwn(cell, "v")))) &&
|
|
19
20
|
(value.schema === undefined || Array.isArray(value.schema?.fields)) &&
|
|
20
21
|
(value.pageToken === undefined || typeof value.pageToken === "string") &&
|
|
21
22
|
(value.totalRows === undefined || (typeof value.totalRows === "string" && /^\d+$/.test(value.totalRows))) &&
|
|
@@ -41,7 +42,7 @@ const metadataPage = { maxResults: resultFields.maxResults, pageToken: { ...page
|
|
|
41
42
|
const jobFields = { jobId: resultsSchema.getFieldDefinitions().jobId, location: resultFields.location };
|
|
42
43
|
const validJob = (job) => Boolean(typeof job?.jobReference?.jobId === "string" && job.jobReference.jobId.length > 0 && typeof job.jobReference.projectId === "string" &&
|
|
43
44
|
["PENDING", "RUNNING", "DONE"].includes(job.status?.state));
|
|
44
|
-
const validPage = (result, field, reference) => Boolean(
|
|
45
|
+
const validPage = (result, field, reference) => Boolean(isRecord(result) && !result.error &&
|
|
45
46
|
(result[field] === undefined || Array.isArray(result[field]) && result[field].every((item) => typeof item?.[reference]?.projectId === "string")) &&
|
|
46
47
|
(result.nextPageToken === undefined || typeof result.nextPageToken === "string"));
|
|
47
48
|
|
|
@@ -86,7 +87,7 @@ const bigqueryProvider = googleProvider(bigqueryDefinition, origin, "projects.li
|
|
|
86
87
|
"projects.list": googleRead(["bigquery.readonly", "bigquery", "cloud-platform.read-only", "cloud-platform"], {
|
|
87
88
|
maxResults: { type: "integer", min: 1, max: 50, defaultTo: 50 }, pageToken
|
|
88
89
|
}, (query) => ({ url: `${origin}/bigquery/v2/projects`, query }),
|
|
89
|
-
(result) => Boolean(
|
|
90
|
+
(result) => Boolean(isRecord(result) && !result.error
|
|
90
91
|
&& Number.isInteger(result.totalItems) && result.totalItems >= 0
|
|
91
92
|
&& (Array.isArray(result.projects) || (result.totalItems === 0 && result.projects === undefined))))
|
|
92
93
|
});
|
package/src/server/brevo.js
CHANGED
|
@@ -2,6 +2,7 @@ import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
|
2
2
|
import { createSchema } from "json-rest-schema";
|
|
3
3
|
import { brevoDefinition } from "../shared/tokens.js";
|
|
4
4
|
import { jsonOperation, validatedOperation, withQuery } from "./jsonOperation.js";
|
|
5
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
6
|
|
|
6
7
|
const text = { type: "string", minLength: 1, maxLength: 1000 };
|
|
7
8
|
const email = { ...text, maxLength: 320, pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" };
|
|
@@ -13,7 +14,7 @@ const mailbox = { type: "object", schema: createSchema({ email: { ...email, requ
|
|
|
13
14
|
const ids = { type: "array", items: { type: "integer", min: 1 }, validator: value => value.length <= 100 || "Use at most 100 list IDs." };
|
|
14
15
|
const emails = { type: "array", items: email, validator: value => value.length > 0 && value.length <= 150 || "Supply 1–150 email addresses." };
|
|
15
16
|
const domain = { type: "string", minLength: 3, maxLength: 253, pattern: "^(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\\.)+[A-Za-z]{2,}$", required: true };
|
|
16
|
-
const empty = result => result === null ||
|
|
17
|
+
const empty = result => result === null || isRecord(result) && Object.keys(result).length === 0;
|
|
17
18
|
const record = result => Number.isInteger(result?.id);
|
|
18
19
|
function operation(method, endpoint, fields, validateResult, prepare = value => value) {
|
|
19
20
|
return validatedOperation(fields, values => {
|
package/src/server/clay.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createSchema } from "json-rest-schema";
|
|
|
2
2
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
3
|
import { clayDefinition } from "../shared/tokens.js";
|
|
4
4
|
import { jsonOperation } from "./jsonOperation.js";
|
|
5
|
+
import { hasDataList } from "./connectorValues.js";
|
|
5
6
|
|
|
6
7
|
const endpoint = "https://api.clay.com/public/v0";
|
|
7
8
|
const pageSchema = createSchema({
|
|
@@ -57,7 +58,7 @@ const clayProvider = Object.freeze({
|
|
|
57
58
|
group_by: array(text, 5), order_by: array(object, 3)
|
|
58
59
|
}) },
|
|
59
60
|
cursor: text, limit: { type: "integer", min: 1, max: 100, defaultTo: 50 }
|
|
60
|
-
},
|
|
61
|
+
}, hasDataList, "POST"),
|
|
61
62
|
"searches.create": jsonOperation(`${endpoint}/search/query-mode`, {
|
|
62
63
|
query: { type: "string", required: true, minLength: 1, maxLength: 16000,
|
|
63
64
|
validator: (value) => value.trim().length > 0 || "Enter a Clay search query." }
|
package/src/server/clickhouse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
2
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
3
|
import { clickhouseDefinition } from "../shared/clickhouse.js";
|
|
4
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
5
|
|
|
5
6
|
const name = { type: "string", required: true, minLength: 1, maxLength: 256,
|
|
6
7
|
validator: (value) => !/[\p{Cc}]/u.test(value) || "Use a name without control characters." };
|
|
@@ -31,7 +32,7 @@ function queryOperation(query, fields, validateData, parameters = values => valu
|
|
|
31
32
|
return Boolean(result && !result.exception && !result.error && Array.isArray(result.meta) &&
|
|
32
33
|
result.meta.every((column) => typeof column?.name === "string" && typeof column.type === "string") &&
|
|
33
34
|
Array.isArray(result.data) && result.data.length <= 100 && Number.isInteger(result.rows) && result.rows === result.data.length &&
|
|
34
|
-
result.data.every(
|
|
35
|
+
result.data.every(isRecord) && (!validateData || validateData(result.data)));
|
|
35
36
|
}
|
|
36
37
|
};
|
|
37
38
|
}
|
package/src/server/confidence.js
CHANGED
|
@@ -10,7 +10,12 @@ const oauth = Object.freeze({ issuer: origin, authorization_endpoint: `${origin}
|
|
|
10
10
|
|
|
11
11
|
function provider(definition, path) {
|
|
12
12
|
return Object.freeze({
|
|
13
|
-
...definition,
|
|
13
|
+
...definition,
|
|
14
|
+
oauth,
|
|
15
|
+
oauthResource: `${origin}/mcp`,
|
|
16
|
+
apiOrigins: [origin],
|
|
17
|
+
checkOperation: "tools.list",
|
|
18
|
+
exchange: exchangeMcp,
|
|
14
19
|
operations: Object.fromEntries(Object.entries(mcpOperations(() => `${origin}/mcp/${path}`)).map(([name, operation]) => [name, { ...operation, scopes: ["openid"] }]))
|
|
15
20
|
});
|
|
16
21
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// The value checks several providers share when they validate a provider's response or settings.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A safe integer that is zero or more, such as a count or a version number.
|
|
5
|
+
* @param {unknown} value
|
|
6
|
+
*/
|
|
7
|
+
const isCount = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A non-empty string.
|
|
11
|
+
* @param {unknown} value
|
|
12
|
+
*/
|
|
13
|
+
const isText = (value) => typeof value === "string" && value.length > 0;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A response record that carries a string `id`.
|
|
17
|
+
* @param {any} value
|
|
18
|
+
*/
|
|
19
|
+
const hasStringId = (value) => typeof value?.id === "string";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A response whose `data` is a list.
|
|
23
|
+
* @param {any} result
|
|
24
|
+
*/
|
|
25
|
+
const hasDataList = (result) => Array.isArray(result?.data);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The origin of the account URL a connection's settings hold.
|
|
29
|
+
* @param {{ accountUrl: string }} settings
|
|
30
|
+
*/
|
|
31
|
+
const accountOrigin = (settings) => new URL(settings.accountUrl).origin;
|
|
32
|
+
|
|
33
|
+
export { isCount, isText, hasStringId, hasDataList, accountOrigin };
|
package/src/server/databricks.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
1
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
2
2
|
import { databricksDefinition } from "../shared/databricks.js";
|
|
3
3
|
import { jsonOperation } from "./jsonOperation.js";
|
|
4
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
5
|
|
|
5
6
|
const origin = (settings) => new URL(settings.workspaceUrl).origin;
|
|
6
|
-
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7
7
|
const identifier = (value) => Number.isSafeInteger(value) && value > 0;
|
|
8
8
|
const pageToken = { type: "string", minLength: 1, maxLength: 8192 };
|
|
9
9
|
const pagination = (value) => ["next_page_token", "prev_page_token"].every((key) => value[key] === undefined ||
|
|
10
10
|
typeof value[key] === "string" && value[key].length > 0 && value[key].length <= 8192);
|
|
11
|
-
const job = (value) =>
|
|
12
|
-
(value.settings === undefined ||
|
|
11
|
+
const job = (value) => isRecord(value) && identifier(value.job_id) &&
|
|
12
|
+
(value.settings === undefined || isRecord(value.settings)) && (value.has_more === undefined || typeof value.has_more === "boolean");
|
|
13
13
|
const jobsList = jsonOperation((settings) => `${origin(settings)}/api/2.2/jobs/list`, {
|
|
14
14
|
limit: { type: "integer", min: 1, max: 100, defaultTo: 20 },
|
|
15
15
|
expand_tasks: { type: "boolean", defaultTo: false }, name: { type: "string", minLength: 1, maxLength: 4096 }, page_token: pageToken
|
|
16
|
-
}, (value) =>
|
|
16
|
+
}, (value) => isRecord(value) && pagination(value) && (value.jobs === undefined || Array.isArray(value.jobs) && value.jobs.every(job)));
|
|
17
17
|
const jobsGet = jsonOperation((settings) => `${origin(settings)}/api/2.2/jobs/get`, {
|
|
18
18
|
job_id: { type: "integer", required: true, min: 1, max: Number.MAX_SAFE_INTEGER },
|
|
19
19
|
include_trigger_state: { type: "boolean", defaultTo: false }, page_token: pageToken
|
|
@@ -41,10 +41,10 @@ const databricksProvider = Object.freeze({
|
|
|
41
41
|
const url = new URL(address);
|
|
42
42
|
// An empty jobs list is {}, but JSON null or an unparseable body is not a verified list.
|
|
43
43
|
const response = await fetchImpl(address, { ...options, credentials: "omit" });
|
|
44
|
-
if (!response.ok) throw
|
|
44
|
+
if (!response.ok) throw providerStatusError(response.status);
|
|
45
45
|
let result;
|
|
46
46
|
try { result = await response.json(); } catch { /* checked below */ }
|
|
47
|
-
if (response.status !== 200 || !
|
|
47
|
+
if (response.status !== 200 || !isRecord(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
50
|
throw new ConnectorError("connector_response_invalid", "Databricks returned an unexpected job response.", { statusCode: 502, messageCode: "connectors.databricks.returnedUnexpectedJobResponse" });
|
|
@@ -2,10 +2,10 @@ import { createSchema } from "json-rest-schema";
|
|
|
2
2
|
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
3
3
|
import { dbtSemanticLayerDefinition } from "../shared/dbt-semantic-layer.js";
|
|
4
4
|
import { graphqlOperation } from "./graphqlOperation.js";
|
|
5
|
+
import { isText } from "./connectorValues.js";
|
|
5
6
|
|
|
6
7
|
const origin = (settings) => `https://${settings.host}`;
|
|
7
8
|
const endpoint = (settings) => `${origin(settings)}/api/graphql`;
|
|
8
|
-
const text = (value) => typeof value === "string" && value.length > 0;
|
|
9
9
|
const description = (value) => value === null || typeof value === "string";
|
|
10
10
|
const paging = {
|
|
11
11
|
pageNum: { type: "integer", min: 1, max: 2_147_483_647, defaultTo: 1 },
|
|
@@ -14,7 +14,7 @@ const paging = {
|
|
|
14
14
|
};
|
|
15
15
|
const pageFields = "pageNum pageSize totalItems totalPages items { name description type }";
|
|
16
16
|
const validPage = (page, typed = true) => Array.isArray(page?.items) && page.items.length <= 100 &&
|
|
17
|
-
page.items.every((item) =>
|
|
17
|
+
page.items.every((item) => isText(item?.name) && description(item.description) && (!typed || isText(item.type))) &&
|
|
18
18
|
Number.isSafeInteger(page.pageNum) && page.pageNum >= 1 && Number.isSafeInteger(page.pageSize) && page.pageSize >= 1 && page.pageSize <= 100 &&
|
|
19
19
|
Number.isSafeInteger(page.totalItems) && page.totalItems >= page.items.length && Number.isSafeInteger(page.totalPages) && page.totalPages >= 0;
|
|
20
20
|
|
|
@@ -43,15 +43,15 @@ const dbtSemanticLayerProvider = Object.freeze({
|
|
|
43
43
|
where: { type: "array", validator: (value) => value.length <= 20 || "Use at most 20 filters.",
|
|
44
44
|
items: { type: "object", schema: createSchema({ sql: { type: "string", required: true, minLength: 1, maxLength: 4000 } }) } },
|
|
45
45
|
limit: { type: "integer", min: 1, max: 10000, defaultTo: 100 }
|
|
46
|
-
}, (data) =>
|
|
46
|
+
}, (data) => isText(data?.createQuery?.queryId)),
|
|
47
47
|
"queries.get": environmentOperation("query ConnectorDbtQueryResult($environmentId: BigInt!, $queryId: String!, $pageNum: Int!) { query(environmentId: $environmentId, queryId: $queryId, pageNum: $pageNum) { status error totalPages jsonResult(orient: TABLE, encoded: false) } }", {
|
|
48
48
|
queryId: { type: "string", required: true, minLength: 1, maxLength: 512 },
|
|
49
49
|
pageNum: { type: "integer", min: 1, max: 2_147_483_647, defaultTo: 1 }
|
|
50
|
-
}, (data) =>
|
|
50
|
+
}, (data) => isText(data?.query?.status) && description(data.query.error) &&
|
|
51
51
|
(data.query.totalPages === null || (Number.isSafeInteger(data.query.totalPages) && data.query.totalPages >= 0)) &&
|
|
52
52
|
(data.query.jsonResult === null || (typeof data.query.jsonResult === "object" && Array.isArray(data.query.jsonResult?.data) && Array.isArray(data.query.jsonResult?.schema?.fields)))),
|
|
53
53
|
"environment.read": environmentOperation("query ConnectorDbtEnvironment($environmentId: BigInt!) { environmentInfo(environmentId: $environmentId) { dialect } }", {},
|
|
54
|
-
(data) =>
|
|
54
|
+
(data) => isText(data?.environmentInfo?.dialect)),
|
|
55
55
|
"metrics.list": environmentOperation(`query ConnectorDbtMetrics($environmentId: BigInt!, $pageNum: Int!, $pageSize: Int!, $search: String) { metricsPaginated(environmentId: $environmentId, pageNum: $pageNum, pageSize: $pageSize, search: $search) { ${pageFields} } }`, paging,
|
|
56
56
|
(data) => validPage(data?.metricsPaginated)),
|
|
57
57
|
"dimensions.list": environmentOperation(`query ConnectorDbtDimensions($environmentId: BigInt!, $metrics: [MetricInput!]!, $pageNum: Int!, $pageSize: Int!, $search: String) { dimensionsPaginated(environmentId: $environmentId, metrics: $metrics, pageNum: $pageNum, pageSize: $pageSize, search: $search) { ${pageFields} } }`, {
|
package/src/server/elevenlabs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
2
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
3
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
4
4
|
import { elevenlabsDefinition } from "../shared/tokens.js";
|
|
5
5
|
import { jsonOperation } from "./jsonOperation.js";
|
|
6
6
|
|
|
@@ -36,7 +36,7 @@ const elevenlabsProvider = Object.freeze({
|
|
|
36
36
|
if (!new URL(url).pathname.startsWith("/v1/text-to-speech/")) return request(url, options);
|
|
37
37
|
const response = await fetchImpl(url, { ...options, body: JSON.stringify(options.body),
|
|
38
38
|
headers: { ...options.headers, "Content-Type": "application/json", Accept: "audio/*" }, credentials: "omit", redirect: "error" });
|
|
39
|
-
if (!response.ok) { await response.body?.cancel(); throw
|
|
39
|
+
if (!response.ok) { await response.body?.cancel(); throw providerStatusError(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
42
|
await response.body?.cancel(); throw new ConnectorError("connector_response_invalid", "ElevenLabs did not return audio.", { statusCode: 502, messageCode: "connectors.elevenlabs.didNotReturnAudio" });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { importPKCS8, SignJWT } from "jose";
|
|
2
2
|
import { createSchema } from "json-rest-schema";
|
|
3
3
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
4
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
4
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
5
5
|
import { firebaseCloudMessagingDefinition, firebaseMessagingScope, firebaseProjectId } from "../shared/firebase-cloud-messaging.js";
|
|
6
6
|
import { jsonOperation } from "./jsonOperation.js";
|
|
7
7
|
|
|
@@ -99,7 +99,7 @@ const firebaseCloudMessagingProvider = Object.freeze({
|
|
|
99
99
|
if (response.status === 400) {
|
|
100
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
|
-
throw
|
|
102
|
+
throw providerStatusError(response.status);
|
|
103
103
|
}
|
|
104
104
|
if (!receipt(result) || !result.name.startsWith(`projects/${settings.projectId}/messages/`)) {
|
|
105
105
|
throw new ConnectorError("connector_response_invalid", "Firebase returned an unexpected message receipt.", { statusCode: 502, messageCode: "connectors.firebaseCloudMessaging.returnedUnexpectedMessageReceipt" });
|