@zackbart/connecta 0.28.0 → 0.29.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.
- package/CHANGELOG.md +961 -5
- package/README.md +62 -39
- package/bin/connecta.mjs +110 -139
- package/bin/meta-tool-names.d.mts +2 -0
- package/bin/meta-tool-names.mjs +9 -0
- package/bin/version.mjs +3 -0
- package/dist/access-tokens.js +53 -63
- package/dist/activity-request.js +8 -0
- package/dist/activity.d.ts +76 -42
- package/dist/activity.js +110 -42
- package/dist/auth/clerk-sdk/client.d.ts +65 -0
- package/dist/auth/clerk-sdk/client.js +5 -0
- package/dist/auth/clerk-sdk/generated.js +1845 -0
- package/dist/auth/clerk-transport.js +19 -0
- package/dist/auth/clerk.d.ts +17 -7
- package/dist/auth/clerk.js +189 -107
- package/dist/auth/cloudflare-access.js +25 -26
- package/dist/auth/downstream-client-metadata.d.ts +29 -0
- package/dist/auth/downstream-client-metadata.js +133 -0
- package/dist/auth/downstream-oauth.js +1632 -1509
- package/dist/auth/downstream-revocation.js +106 -0
- package/dist/auth/oauth-token-response.js +231 -0
- package/dist/auth/oauth-v2-migration.js +116 -0
- package/dist/auth/static-oauth.js +526 -0
- package/dist/auth-elicitation.js +283 -0
- package/dist/branding.d.ts +8 -16
- package/dist/branding.js +26 -59
- package/dist/byte-read-response.js +39 -0
- package/dist/call-admission.js +12 -16
- package/dist/catalog-cache.js +739 -0
- package/dist/catalog-drift.d.ts +50 -15
- package/dist/catalog-drift.js +365 -128
- package/dist/catalog-fingerprint.js +2 -4
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-limits.js +2 -2
- package/dist/catalog-service.js +368 -224
- package/dist/catalog.js +43 -84
- package/dist/concurrency.js +3 -4
- package/dist/config-defaults.d.ts +39 -0
- package/dist/config-defaults.js +46 -0
- package/dist/config-schema.d.ts +181 -0
- package/dist/config-schema.js +435 -0
- package/dist/config-value-sources.js +102 -0
- package/dist/config.d.ts +631 -0
- package/dist/config.js +503 -0
- package/dist/connector-access.d.ts +1 -0
- package/dist/connector-access.js +13 -15
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +15 -6
- package/dist/connector-scope.js +1 -1
- package/dist/connectors/api-connector.d.ts +220 -0
- package/dist/connectors/api-connector.js +214 -0
- package/dist/connectors/api.d.ts +8 -71
- package/dist/connectors/api.js +13 -99
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +108 -25
- package/dist/connectors/negotiation-cache.js +46 -0
- package/dist/connectors/option-shapes.js +27 -0
- package/dist/connectors/remote-mcp.d.ts +30 -4
- package/dist/connectors/remote-mcp.js +1780 -351
- package/dist/connectors/resource-uri.js +335 -0
- package/dist/credential-contract.d.ts +10 -0
- package/dist/credential-rules.js +1 -3
- package/dist/credential-url.js +108 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +27 -18
- package/dist/d1.d.ts +130 -0
- package/dist/d1.js +290 -0
- package/dist/describe-config.d.ts +165 -0
- package/dist/describe-config.js +315 -0
- package/dist/described.js +106 -0
- package/dist/downstream-input-context.js +119 -0
- package/dist/downstream-input.js +323 -0
- package/dist/errors.d.ts +97 -20
- package/dist/errors.js +158 -80
- package/dist/execute.js +453 -305
- package/dist/executor-admission.d.ts +1 -0
- package/dist/executor-admission.js +11 -15
- package/dist/executor-contract.d.ts +20 -0
- package/dist/executor-contract.js +48 -0
- package/dist/executor-result.js +24 -9
- package/dist/executors/quickjs-child.js +3 -4
- package/dist/executors/quickjs-runtime.js +147 -59
- package/dist/executors/quickjs.js +29 -35
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +188 -0
- package/dist/guest-types.d.ts +176 -0
- package/dist/guest-types.js +1 -0
- package/dist/html-security.js +24 -0
- package/dist/identity.js +1 -3
- package/dist/inbound-credential.js +52 -0
- package/dist/index.d.ts +25 -257
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +230 -73
- package/dist/json-schema.d.ts +1 -1
- package/dist/mcp-client-context.js +35 -0
- package/dist/mcp-result.js +11 -7
- package/dist/meta-output.js +76 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +285 -209
- package/dist/module-contracts.d.ts +8 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -2
- package/dist/oauth-callback-outcome.js +1 -3
- package/dist/oauth-handoff.js +117 -0
- package/dist/oauth-partition.js +17 -0
- package/dist/oauth-sealing.js +20 -0
- package/dist/operator-record.d.ts +137 -0
- package/dist/operator-record.js +420 -0
- package/dist/operator-ui/assets.js +31 -0
- package/dist/operator-ui/contract.d.ts +72 -0
- package/dist/operator-ui/contract.js +1 -0
- package/dist/operator-ui/fix-prompts.d.ts +18 -0
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/model.d.ts +12 -87
- package/dist/operator-ui/model.js +2 -9
- package/dist/operator-ui/setup-commands.js +1 -3
- package/dist/operator-ui/view.d.ts +323 -0
- package/dist/operator-ui/view.js +62 -189
- package/dist/page-styles.js +2 -3
- package/dist/program-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +55 -18
- package/dist/provider.d.ts +142 -0
- package/dist/provider.js +187 -0
- package/dist/providers/_shared/google/result-size.js +57 -0
- package/dist/providers/_shared/google/service-account.d.ts +58 -0
- package/dist/providers/_shared/google/service-account.js +500 -0
- package/dist/providers/_shared/google/workspace.d.ts +276 -0
- package/dist/providers/_shared/google/workspace.js +518 -0
- package/dist/providers/basecamp/index.d.ts +43 -0
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/breeze/index.d.ts +34 -0
- package/dist/providers/breeze/index.js +1446 -0
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/ccb/index.d.ts +103 -0
- package/dist/providers/ccb/index.js +1725 -0
- package/dist/providers/ccb/skill.generated.js +9 -0
- package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
- package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
- package/dist/providers/cloudflare/skill.generated.js +10 -0
- package/dist/providers/docs/index.d.ts +54 -0
- package/dist/providers/docs/index.js +1237 -0
- package/dist/providers/docs/skill.generated.js +9 -0
- package/dist/providers/drive/index.d.ts +53 -0
- package/dist/providers/drive/index.js +1523 -0
- package/dist/providers/drive/skill.generated.js +9 -0
- package/dist/providers/forms/index.d.ts +54 -0
- package/dist/providers/forms/index.js +1217 -0
- package/dist/providers/forms/skill.generated.js +11 -0
- package/dist/providers/github/auth.d.ts +32 -0
- package/dist/providers/github/auth.js +361 -0
- package/dist/providers/github/hosted.js +218 -0
- package/dist/providers/github/index.d.ts +13 -0
- package/dist/providers/github/index.js +140 -0
- package/dist/providers/github/key.js +100 -0
- package/dist/providers/github/rest.js +321 -0
- package/dist/providers/github/scope.d.ts +29 -0
- package/dist/providers/github/scope.js +124 -0
- package/dist/providers/github/skill.generated.js +8 -0
- package/dist/providers/gmail/index.d.ts +51 -0
- package/dist/providers/gmail/index.js +1436 -0
- package/dist/providers/gmail/skill.generated.js +9 -0
- package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
- package/dist/providers/linear/index.js +352 -0
- package/dist/providers/linear/skill.generated.js +8 -0
- package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
- package/dist/providers/mixpanel/index.js +416 -0
- package/dist/providers/mixpanel/skill.generated.js +9 -0
- package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
- package/dist/providers/{notion.js → notion/index.js} +226 -239
- package/dist/providers/notion/skill.generated.js +10 -0
- package/dist/providers/overflow/index.d.ts +60 -0
- package/dist/providers/overflow/index.js +1535 -0
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/planning-center/index.d.ts +59 -0
- package/dist/providers/planning-center/index.js +2199 -0
- package/dist/providers/planning-center/skill.generated.js +10 -0
- package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
- package/dist/providers/revenuecat/index.js +557 -0
- package/dist/providers/revenuecat/skill.generated.js +11 -0
- package/dist/providers/sheets/index.d.ts +54 -0
- package/dist/providers/sheets/index.js +1237 -0
- package/dist/providers/sheets/skill.generated.js +10 -0
- package/dist/providers/slides/index.d.ts +53 -0
- package/dist/providers/slides/index.js +2645 -0
- package/dist/providers/slides/skill.generated.js +9 -0
- package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
- package/dist/providers/stripe/index.js +266 -0
- package/dist/providers/stripe/skill.generated.js +11 -0
- package/dist/providers/tithely/index.d.ts +49 -0
- package/dist/providers/tithely/index.js +1003 -0
- package/dist/providers/tithely/skill.generated.js +8 -0
- package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
- package/dist/providers/vercel/index.js +1038 -0
- package/dist/providers/vercel/mcp-ownership.js +212 -0
- package/dist/providers/vercel/skill.generated.js +11 -0
- package/dist/registry.d.ts +108 -171
- package/dist/registry.js +513 -925
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +100 -0
- package/dist/result-shapes.js +17 -49
- package/dist/retired-toolkits.js +1 -1
- package/dist/routes/access-tokens.js +3 -6
- package/dist/routes/activity-context.js +21 -0
- package/dist/routes/activity-disclosure.js +40 -0
- package/dist/routes/activity.js +59 -21
- package/dist/routes/actor-labels.js +3 -50
- package/dist/routes/connect.js +221 -0
- package/dist/routes/credentials.js +15 -27
- package/dist/routes/mcp.js +356 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +136 -57
- package/dist/routes/operator.js +3 -3
- package/dist/routes/shared.d.ts +25 -64
- package/dist/routes/shared.js +160 -80
- package/dist/routes/skills.js +30 -0
- package/dist/routes/ui-config.js +225 -0
- package/dist/routes/ui-data.js +40 -35
- package/dist/routes/ui.js +66 -56
- package/dist/runtime/admission.js +4 -2
- package/dist/runtime/call-admission.js +3 -1
- package/dist/runtime/connector-scope.js +1 -4
- package/dist/runtime/run.js +10 -39
- package/dist/runtime/services.js +12 -117
- package/dist/runtime/shared-read.js +1 -3
- package/dist/runtime/storage.js +8 -1
- package/dist/sent-secrets.d.ts +52 -0
- package/dist/sent-secrets.js +474 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +406 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +166 -0
- package/dist/storage/keys.js +428 -0
- package/dist/storage/memory.js +7 -0
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +534 -0
- package/dist/timeout.js +4 -2
- package/dist/tool-name.js +17 -0
- package/dist/tool-safety.d.ts +9 -35
- package/dist/tool-safety.js +20 -40
- package/dist/types.d.ts +320 -54
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +7 -19
- package/dist/ui.js +76 -90
- package/dist/url-safety.js +3 -6
- package/dist/usage-guide.js +137 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +309 -47
- package/dist/version.d.ts +1 -7
- package/dist/version.js +3 -7
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +434 -0
- package/documentation/architecture.md +665 -230
- package/documentation/auth.md +962 -458
- package/documentation/code-mode.md +501 -312
- package/documentation/meta-tools.md +449 -200
- package/documentation/operator-tests.md +93 -0
- package/documentation/provider-migration-0.29.md +60 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +286 -255
- package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
- package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
- package/examples/worker/src/connecta.config.ts +151 -0
- package/examples/worker/src/index.ts +14 -197
- package/examples/worker/wrangler.jsonc +15 -35
- package/package.json +125 -35
- package/templates/node/.dockerignore +1 -2
- package/templates/node/.env.example +20 -21
- package/templates/node/AGENTS.md +21 -17
- package/templates/node/Dockerfile +3 -5
- package/templates/node/README.md +133 -58
- package/templates/node/docker-compose.yml +11 -12
- package/templates/node/package.json +4 -2
- package/templates/node/src/connecta.config.ts +119 -0
- package/templates/node/src/index.ts +9 -154
- package/templates/node/src/provision-token.ts +17 -0
- package/AGENTS.md +0 -170
- package/dist/artifacts/connector.d.ts +0 -58
- package/dist/artifacts/connector.js +0 -730
- package/dist/artifacts/document.js +0 -86
- package/dist/artifacts/guide.js +0 -140
- package/dist/artifacts/html-scan.js +0 -329
- package/dist/artifacts/json.js +0 -78
- package/dist/artifacts/kv-store.d.ts +0 -20
- package/dist/artifacts/kv-store.js +0 -140
- package/dist/artifacts/markdown.js +0 -478
- package/dist/artifacts/module.d.ts +0 -37
- package/dist/artifacts/module.js +0 -66
- package/dist/artifacts/operations.d.ts +0 -197
- package/dist/artifacts/operations.js +0 -1002
- package/dist/artifacts/refresh.d.ts +0 -55
- package/dist/artifacts/refresh.js +0 -236
- package/dist/artifacts/routes.js +0 -206
- package/dist/artifacts/types.d.ts +0 -214
- package/dist/artifacts/types.js +0 -22
- package/dist/artifacts/validate.d.ts +0 -62
- package/dist/artifacts/validate.js +0 -651
- package/dist/artifacts.d.ts +0 -6
- package/dist/artifacts.js +0 -9
- package/dist/auth/bearer.d.ts +0 -11
- package/dist/auth/bearer.js +0 -50
- package/dist/providers/linear.js +0 -217
- package/dist/providers/mixpanel.js +0 -247
- package/dist/providers/revenuecat.js +0 -317
- package/dist/providers/stripe.js +0 -253
- package/dist/providers/vercel.js +0 -1253
- package/dist/storage/file.d.ts +0 -15
- package/dist/storage/file.js +0 -455
- package/ethos.md +0 -93
- package/examples/worker/src/cloudflare-kv.ts +0 -43
- package/examples/worker/src/d1-activity-row.ts +0 -155
- package/examples/worker/src/d1-activity.ts +0 -147
- package/examples/worker/src/d1-storage.ts +0 -138
- package/examples/worker/src/r2-artifact-blobs.ts +0 -29
- package/templates/node/src/file-activity.ts +0 -182
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// `Connecta.describeConfig()`: a secret-free snapshot of what a deployment
|
|
2
|
+
// runs with, built once at construction for the operator UI and
|
|
3
|
+
// `connecta doctor --config`.
|
|
4
|
+
//
|
|
5
|
+
// It is an allowlist serializer. Every value is copied field by field from a
|
|
6
|
+
// known type, and nothing is spread: a config object, a module, a connector,
|
|
7
|
+
// or a connector's own `describe()` result can carry a secret beside the
|
|
8
|
+
// fields named here, and spreading it would publish that secret the day
|
|
9
|
+
// someone adds one. Functions become "custom"/"default" or a boolean and are
|
|
10
|
+
// never called or stringified; every URL, absolute or root-relative, passes
|
|
11
|
+
// through one sanitizer in src/described.ts and keeps origin and path at most;
|
|
12
|
+
// headers keep their names. test/describe-config.test.ts plants a sentinel secret in every
|
|
13
|
+
// secret-bearing position and asserts none survives.
|
|
14
|
+
import { resolveBranding } from "./branding.js";
|
|
15
|
+
import { CONFIG_DEFAULTS } from "./config-defaults.js";
|
|
16
|
+
import { describedEndpoint, describedHref, describedOrigin, describedTools, describedUrl } from "./described.js";
|
|
17
|
+
import { connectorGuideRequired, connectorGuideSummary } from "./skills.js";
|
|
18
|
+
import { CONNECTA_VERSION } from "./version.js";
|
|
19
|
+
const str = (value) => (typeof value === "string" ? value : undefined);
|
|
20
|
+
const num = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
21
|
+
/** Copy only the named keys whose values are strings or numbers. */
|
|
22
|
+
function pick(value, keys, read) {
|
|
23
|
+
const out = {};
|
|
24
|
+
if (typeof value !== "object" || value === null)
|
|
25
|
+
return out;
|
|
26
|
+
for (const key of keys) {
|
|
27
|
+
const item = read(value[key]);
|
|
28
|
+
if (item !== undefined)
|
|
29
|
+
out[key] = item;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function ownValue(value, key) {
|
|
34
|
+
return typeof value === "object" && value !== null && Object.hasOwn(value, key)
|
|
35
|
+
? value[key]
|
|
36
|
+
: undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Each resolved value, with "config" where the deployment set it explicitly. */
|
|
39
|
+
function limitGroup(resolved, raw, keys) {
|
|
40
|
+
const out = {};
|
|
41
|
+
for (const key of keys) {
|
|
42
|
+
const value = resolved[key];
|
|
43
|
+
out[key] = {
|
|
44
|
+
value: typeof value === "number" || typeof value === "boolean" ? value : null,
|
|
45
|
+
source: ownValue(raw, key) !== undefined ? "config" : "default",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** An endpoint from a string or an `{ origin, path }` pair, re-parsed so only origin and path survive. */
|
|
51
|
+
function endpointOf(value) {
|
|
52
|
+
if (typeof value === "object" && value !== null && !(value instanceof URL)) {
|
|
53
|
+
const { origin, path } = value;
|
|
54
|
+
return typeof origin === "string"
|
|
55
|
+
? describedEndpoint(`${origin}${typeof path === "string" ? path : ""}`)
|
|
56
|
+
: undefined;
|
|
57
|
+
}
|
|
58
|
+
return describedEndpoint(value);
|
|
59
|
+
}
|
|
60
|
+
const AUTH_MODES = new Set(["none", "headers", "credential", "oauth", "request"]);
|
|
61
|
+
const SOURCE_KINDS = new Set(["remote-mcp", "api", "custom"]);
|
|
62
|
+
/** Re-validate a connector's own description: a custom describe() may return anything. */
|
|
63
|
+
function copyAuth(value) {
|
|
64
|
+
if (typeof value !== "object" || value === null)
|
|
65
|
+
return undefined;
|
|
66
|
+
const auth = value;
|
|
67
|
+
if (typeof auth.mode !== "string" || !AUTH_MODES.has(auth.mode))
|
|
68
|
+
return undefined;
|
|
69
|
+
const names = (item) => Array.isArray(item) ? item.filter((name) => typeof name === "string") : undefined;
|
|
70
|
+
const authorizationEndpoint = endpointOf(auth.authorizationEndpoint);
|
|
71
|
+
const tokenEndpoint = endpointOf(auth.tokenEndpoint);
|
|
72
|
+
const clientMetadataUrl = describedUrl(auth.clientMetadataUrl);
|
|
73
|
+
const headerNames = names(auth.headerNames);
|
|
74
|
+
const apiOrigins = names(auth.apiOrigins)?.flatMap((origin) => describedOrigin(origin) ?? []);
|
|
75
|
+
const authorizationParamNames = names(auth.authorizationParamNames);
|
|
76
|
+
const tokenRequestHeaderNames = names(auth.tokenRequestHeaderNames);
|
|
77
|
+
return {
|
|
78
|
+
mode: auth.mode,
|
|
79
|
+
...(headerNames ? { headerNames } : {}),
|
|
80
|
+
...(str(auth.header) !== undefined ? { header: str(auth.header) } : {}),
|
|
81
|
+
...(auth.scheme === null || typeof auth.scheme === "string" ? { scheme: auth.scheme } : {}),
|
|
82
|
+
...(str(auth.scope) !== undefined ? { scope: str(auth.scope) } : {}),
|
|
83
|
+
...(clientMetadataUrl ? { clientMetadataUrl } : {}),
|
|
84
|
+
...(authorizationEndpoint ? { authorizationEndpoint } : {}),
|
|
85
|
+
...(tokenEndpoint ? { tokenEndpoint } : {}),
|
|
86
|
+
...(apiOrigins ? { apiOrigins } : {}),
|
|
87
|
+
...(str(auth.tokenEndpointAuthMethod) !== undefined
|
|
88
|
+
? { tokenEndpointAuthMethod: str(auth.tokenEndpointAuthMethod) }
|
|
89
|
+
: {}),
|
|
90
|
+
...(typeof auth.confidentialClient === "boolean" ? { confidentialClient: auth.confidentialClient } : {}),
|
|
91
|
+
...(typeof auth.pkce === "boolean" ? { pkce: auth.pkce } : {}),
|
|
92
|
+
...(authorizationParamNames ? { authorizationParamNames } : {}),
|
|
93
|
+
...(tokenRequestHeaderNames ? { tokenRequestHeaderNames } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function ownDescription(connector) {
|
|
97
|
+
let described;
|
|
98
|
+
try {
|
|
99
|
+
described = typeof connector.describe === "function" ? connector.describe() : undefined;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
described = undefined;
|
|
103
|
+
}
|
|
104
|
+
const value = (typeof described === "object" && described !== null ? described : {});
|
|
105
|
+
const source = (typeof value.source === "object" && value.source !== null ? value.source : {});
|
|
106
|
+
const kind = typeof source.kind === "string" && SOURCE_KINDS.has(source.kind)
|
|
107
|
+
? source.kind
|
|
108
|
+
: "custom";
|
|
109
|
+
const endpoint = endpointOf(value.endpoint);
|
|
110
|
+
const auth = copyAuth(value.auth);
|
|
111
|
+
const transportValue = value.transport;
|
|
112
|
+
const transport = typeof transportValue === "object" && transportValue !== null
|
|
113
|
+
? (() => {
|
|
114
|
+
const { versionNegotiation, redirects, requireHttps } = transportValue;
|
|
115
|
+
return {
|
|
116
|
+
...(versionNegotiation === "auto" || versionNegotiation === "legacy" ? { versionNegotiation } : {}),
|
|
117
|
+
...(redirects === "none" || redirects === "same-origin" ? { redirects } : {}),
|
|
118
|
+
...(typeof requireHttps === "boolean" ? { requireHttps } : {}),
|
|
119
|
+
};
|
|
120
|
+
})()
|
|
121
|
+
: undefined;
|
|
122
|
+
return {
|
|
123
|
+
source: { kind, ...(str(source.provider) !== undefined ? { provider: str(source.provider) } : {}) },
|
|
124
|
+
...(endpoint ? { endpoint } : {}),
|
|
125
|
+
...(auth ? { auth } : {}),
|
|
126
|
+
...(transport ? { transport } : {}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function describeConnector(connector, deploymentCap, registry) {
|
|
130
|
+
const own = ownDescription(connector);
|
|
131
|
+
const credential = connector.credential;
|
|
132
|
+
const admission = connector.callAdmission;
|
|
133
|
+
const guideSummary = connectorGuideSummary(connector);
|
|
134
|
+
const guided = connector.usageGuide !== undefined;
|
|
135
|
+
return {
|
|
136
|
+
id: connector.id,
|
|
137
|
+
...(str(connector.title) !== undefined ? { title: connector.title } : {}),
|
|
138
|
+
...(str(connector.description) !== undefined ? { description: connector.description } : {}),
|
|
139
|
+
authScope: connector.authScope === "personal" ? "personal" : "shared",
|
|
140
|
+
...own,
|
|
141
|
+
...(credential && typeof credential === "object"
|
|
142
|
+
? {
|
|
143
|
+
credential: {
|
|
144
|
+
label: String(credential.label),
|
|
145
|
+
...(str(credential.description) !== undefined ? { description: credential.description } : {}),
|
|
146
|
+
...(Array.isArray(credential.fields)
|
|
147
|
+
? {
|
|
148
|
+
fields: credential.fields.map((field) => ({
|
|
149
|
+
name: String(field.name),
|
|
150
|
+
label: String(field.label),
|
|
151
|
+
...(str(field.description) !== undefined ? { description: field.description } : {}),
|
|
152
|
+
})),
|
|
153
|
+
}
|
|
154
|
+
: {}),
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
: {}),
|
|
158
|
+
maxResultBytes: num(connector.maxResultBytes) !== undefined
|
|
159
|
+
? { value: connector.maxResultBytes, source: "connector" }
|
|
160
|
+
: { value: deploymentCap, source: "deployment" },
|
|
161
|
+
...(admission && Array.isArray(admission.rules)
|
|
162
|
+
? {
|
|
163
|
+
callAdmission: {
|
|
164
|
+
...(num(admission.maxPartitions) !== undefined ? { maxPartitions: admission.maxPartitions } : {}),
|
|
165
|
+
rules: admission.rules.map((rule) => ({
|
|
166
|
+
...pick(rule, ["maxConcurrency", "maxQueueSize", "queueTimeoutMs", "retryAfterMs"], num),
|
|
167
|
+
...(rule.budget && num(rule.budget.maxCalls) !== undefined && num(rule.budget.windowMs) !== undefined
|
|
168
|
+
? { budget: { maxCalls: rule.budget.maxCalls, windowMs: rule.budget.windowMs } }
|
|
169
|
+
: {}),
|
|
170
|
+
partitioned: typeof rule.partitionKey === "function",
|
|
171
|
+
})),
|
|
172
|
+
},
|
|
173
|
+
}
|
|
174
|
+
: {}),
|
|
175
|
+
...(guided
|
|
176
|
+
? {
|
|
177
|
+
usageGuide: {
|
|
178
|
+
...(guideSummary !== undefined ? { summary: guideSummary } : {}),
|
|
179
|
+
required: connectorGuideRequired(connector),
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
: {}),
|
|
183
|
+
...(connector.staticTools ? { tools: describedTools(registry.describeStaticTools(connector.id) ?? []) } : {}),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const IDENTITY_KEYS = [
|
|
187
|
+
"connectorAccess",
|
|
188
|
+
"activityAccess",
|
|
189
|
+
"credentialAdministration",
|
|
190
|
+
"accessTokenManagement",
|
|
191
|
+
"personalConnection",
|
|
192
|
+
];
|
|
193
|
+
const STORE_KINDS = new Set(["memory", "d1", "sqlite"]);
|
|
194
|
+
const storeKind = (described) => typeof described?.kind === "string" && STORE_KINDS.has(described.kind)
|
|
195
|
+
? described.kind
|
|
196
|
+
: "custom";
|
|
197
|
+
/** Build the snapshot. Pure: it reads configuration and calls only describe hooks. */
|
|
198
|
+
export function describeConfig(input) {
|
|
199
|
+
const { raw, config } = input;
|
|
200
|
+
const brand = resolveBranding(config.ui?.branding);
|
|
201
|
+
const accessTokens = config.accessTokens?.describe?.();
|
|
202
|
+
const storage = config.storage;
|
|
203
|
+
const activityStore = config.activity?.store.describe?.();
|
|
204
|
+
const D = CONFIG_DEFAULTS;
|
|
205
|
+
const websiteUrl = describedEndpoint(config.serverInfo.websiteUrl);
|
|
206
|
+
const publicUrl = describedEndpoint(config.publicUrl);
|
|
207
|
+
const productUrl = describedUrl(brand.productUrl);
|
|
208
|
+
const ownerUrl = describedUrl(brand.ownerUrl);
|
|
209
|
+
const codeLimits = limitGroup(config.admission.code, ownValue(ownValue(raw, "admission"), "code"), Object.keys(D.admission.code));
|
|
210
|
+
const description = {
|
|
211
|
+
schemaVersion: 1,
|
|
212
|
+
connectaVersion: CONNECTA_VERSION,
|
|
213
|
+
server: {
|
|
214
|
+
name: config.serverInfo.name,
|
|
215
|
+
version: config.serverInfo.version,
|
|
216
|
+
...(config.serverInfo.title !== undefined ? { title: config.serverInfo.title } : {}),
|
|
217
|
+
...(websiteUrl ? { websiteUrl } : {}),
|
|
218
|
+
icons: config.serverInfo.icons?.length ?? 0,
|
|
219
|
+
},
|
|
220
|
+
urls: {
|
|
221
|
+
...(publicUrl ? { publicUrl } : {}),
|
|
222
|
+
mcpPath: "/mcp",
|
|
223
|
+
allowedOrigins: config.allowedOrigins === undefined
|
|
224
|
+
? "default"
|
|
225
|
+
: config.allowedOrigins === "*"
|
|
226
|
+
? "*"
|
|
227
|
+
: config.allowedOrigins.flatMap((origin) => describedOrigin(origin) ?? []),
|
|
228
|
+
},
|
|
229
|
+
executor: {
|
|
230
|
+
...(input.executorName !== undefined ? { name: input.executorName } : {}),
|
|
231
|
+
admission: input.executorAdmits ? "executor" : codeLimits,
|
|
232
|
+
},
|
|
233
|
+
limits: {
|
|
234
|
+
discovery: limitGroup(config.discovery, ownValue(raw, "discovery"), Object.keys(D.discovery)),
|
|
235
|
+
calls: limitGroup(config.calls, ownValue(raw, "calls"), [
|
|
236
|
+
"defaultTimeoutMs",
|
|
237
|
+
"maxResultBytes",
|
|
238
|
+
]),
|
|
239
|
+
results: limitGroup(config.results, ownValue(raw, "results"), Object.keys(D.results)),
|
|
240
|
+
execute: limitGroup(config.execute, ownValue(raw, "execute"), Object.keys(D.execute)),
|
|
241
|
+
requests: limitGroup(config.admission.requests, ownValue(ownValue(raw, "admission"), "requests"), Object.keys(D.admission.requests)),
|
|
242
|
+
},
|
|
243
|
+
trust: config.trust,
|
|
244
|
+
classification: structuredClone(config.classification ?? {}),
|
|
245
|
+
auth: config.auth.map((provider) => ({
|
|
246
|
+
kind: String(provider.kind),
|
|
247
|
+
interactive: provider.interactiveOperator === true,
|
|
248
|
+
...(str(provider.uiAuth?.kind) !== undefined ? { ui: provider.uiAuth.kind } : {}),
|
|
249
|
+
})),
|
|
250
|
+
identity: Object.fromEntries(IDENTITY_KEYS.map((key) => [key, typeof config.identity[key] === "function" ? "custom" : "default"])),
|
|
251
|
+
pools: Object.entries(config.pools ?? {}).map(([name, pool]) => ({
|
|
252
|
+
trust: pool.trust,
|
|
253
|
+
name,
|
|
254
|
+
path: `/mcp/${name}`,
|
|
255
|
+
tools: pool.tools.map(String),
|
|
256
|
+
hasGrant: typeof pool.grant === "function",
|
|
257
|
+
})),
|
|
258
|
+
modules: {
|
|
259
|
+
ui: { enabled: config.ui !== undefined },
|
|
260
|
+
vault: config.vault
|
|
261
|
+
? {
|
|
262
|
+
enabled: true,
|
|
263
|
+
sealsOAuth: typeof config.vault.seal === "function" && typeof config.vault.open === "function",
|
|
264
|
+
}
|
|
265
|
+
: { enabled: false },
|
|
266
|
+
activity: config.activity
|
|
267
|
+
? {
|
|
268
|
+
enabled: true,
|
|
269
|
+
readable: typeof config.activity.store.list === "function",
|
|
270
|
+
...(str(config.activity.deploymentId) !== undefined ? { deploymentId: config.activity.deploymentId } : {}),
|
|
271
|
+
store: {
|
|
272
|
+
kind: storeKind(activityStore),
|
|
273
|
+
...(num(activityStore?.retentionDays) !== undefined
|
|
274
|
+
? { retentionDays: activityStore.retentionDays }
|
|
275
|
+
: {}),
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
: { enabled: false },
|
|
279
|
+
accessTokens: config.accessTokens
|
|
280
|
+
? {
|
|
281
|
+
enabled: true,
|
|
282
|
+
...(num(accessTokens?.maxActive) !== undefined ? { maxActive: accessTokens.maxActive } : {}),
|
|
283
|
+
}
|
|
284
|
+
: { enabled: false },
|
|
285
|
+
},
|
|
286
|
+
storage: {
|
|
287
|
+
configured: raw.storage !== undefined,
|
|
288
|
+
kind: storeKind(storage.describe?.()),
|
|
289
|
+
},
|
|
290
|
+
branding: {
|
|
291
|
+
productName: brand.productName,
|
|
292
|
+
...(productUrl !== undefined ? { productUrl } : {}),
|
|
293
|
+
...(brand.ownerName !== undefined ? { ownerName: brand.ownerName } : {}),
|
|
294
|
+
...(ownerUrl !== undefined ? { ownerUrl } : {}),
|
|
295
|
+
description: brand.description,
|
|
296
|
+
faviconHref: describedHref(brand.faviconHref) ?? "/favicon.svg",
|
|
297
|
+
themeColor: brand.themeColor,
|
|
298
|
+
theme: {
|
|
299
|
+
...pick(brand.theme, ["accent", "radius", "fontFamily", "monoFamily"], str),
|
|
300
|
+
colorScheme: brand.theme.colorScheme,
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
deploymentInfo: Object.keys(config.deploymentInfo ?? {}),
|
|
304
|
+
connectors: config.connectors.map((connector) => describeConnector(connector, config.calls.maxResultBytes, input.registry)),
|
|
305
|
+
};
|
|
306
|
+
return deepFreeze(description);
|
|
307
|
+
}
|
|
308
|
+
function deepFreeze(value) {
|
|
309
|
+
if (typeof value === "object" && value !== null && !Object.isFrozen(value)) {
|
|
310
|
+
Object.freeze(value);
|
|
311
|
+
for (const item of Object.values(value))
|
|
312
|
+
deepFreeze(item);
|
|
313
|
+
}
|
|
314
|
+
return value;
|
|
315
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Small, allowlisting builders shared by every `Connector.describe()` and by
|
|
2
|
+
// `describeConfig()` itself. Each one copies named fields of known types and
|
|
3
|
+
// nothing else, so a value that carries a secret — a URL's userinfo or query,
|
|
4
|
+
// a header's value, a function's source — has no path into a description.
|
|
5
|
+
/**
|
|
6
|
+
* Origin and path of an absolute http(s) URL; undefined for anything else.
|
|
7
|
+
*
|
|
8
|
+
* The scheme is allowlisted, not just parsed: `blob:` and other schemes that
|
|
9
|
+
* wrap a URL report the inner URL's origin while their `pathname` is the
|
|
10
|
+
* whole inner URL, userinfo included, and `data:` or `javascript:` carry
|
|
11
|
+
* their payload in the path. Every field a description reports is a web
|
|
12
|
+
* URL, so any other scheme is omitted rather than reduced.
|
|
13
|
+
*/
|
|
14
|
+
export function describedEndpoint(value) {
|
|
15
|
+
if (typeof value !== "string" && !(value instanceof URL))
|
|
16
|
+
return undefined;
|
|
17
|
+
try {
|
|
18
|
+
const url = new URL(value);
|
|
19
|
+
if ((url.protocol !== "https:" && url.protocol !== "http:") || url.origin === "null")
|
|
20
|
+
return undefined;
|
|
21
|
+
return { origin: url.origin, path: url.pathname };
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* An absolute URL as `origin + path`: userinfo, query, and fragment dropped.
|
|
29
|
+
* Every URL a description emits as a string goes through this or
|
|
30
|
+
* {@link describedHref}, so none carries a token a deployment put in it.
|
|
31
|
+
*/
|
|
32
|
+
export function describedUrl(value) {
|
|
33
|
+
const endpoint = describedEndpoint(value);
|
|
34
|
+
return endpoint ? `${endpoint.origin}${endpoint.path}` : undefined;
|
|
35
|
+
}
|
|
36
|
+
/** An origin alone, from any absolute URL. */
|
|
37
|
+
export function describedOrigin(value) {
|
|
38
|
+
return describedEndpoint(value)?.origin;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* An href that may be root-relative (`/favicon.svg?v=…`): an absolute one as
|
|
42
|
+
* {@link describedUrl}, a relative one as its path alone.
|
|
43
|
+
*/
|
|
44
|
+
export function describedHref(value) {
|
|
45
|
+
if (typeof value !== "string")
|
|
46
|
+
return undefined;
|
|
47
|
+
const absolute = describedUrl(value);
|
|
48
|
+
if (absolute !== undefined)
|
|
49
|
+
return absolute;
|
|
50
|
+
if (!value.startsWith("/") || value.startsWith("//"))
|
|
51
|
+
return undefined;
|
|
52
|
+
try {
|
|
53
|
+
return new URL(value, "https://connecta.invalid").pathname;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const ANNOTATION_KEYS = {
|
|
60
|
+
title: "string",
|
|
61
|
+
readOnlyHint: "boolean",
|
|
62
|
+
destructiveHint: "boolean",
|
|
63
|
+
idempotentHint: "boolean",
|
|
64
|
+
openWorldHint: "boolean",
|
|
65
|
+
};
|
|
66
|
+
function describedAnnotations(value) {
|
|
67
|
+
if (typeof value !== "object" || value === null)
|
|
68
|
+
return undefined;
|
|
69
|
+
const out = {};
|
|
70
|
+
for (const [key, type] of Object.entries(ANNOTATION_KEYS)) {
|
|
71
|
+
const item = value[key];
|
|
72
|
+
if (typeof item === type)
|
|
73
|
+
out[key] = item;
|
|
74
|
+
}
|
|
75
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A JSON Schema as plain data. Schemas are declared in source and describe
|
|
79
|
+
* arguments, never hold them; the round trip drops anything that is not JSON.
|
|
80
|
+
*/
|
|
81
|
+
function describedSchema(value) {
|
|
82
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
83
|
+
return undefined;
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(JSON.stringify(value));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Static tool definitions as `describeConfig()` reports them. */
|
|
92
|
+
export function describedTools(tools) {
|
|
93
|
+
return tools.map((tool) => {
|
|
94
|
+
const annotations = describedAnnotations(tool.annotations);
|
|
95
|
+
const inputSchema = describedSchema(tool.inputSchema);
|
|
96
|
+
const outputSchema = describedSchema(tool.outputSchema);
|
|
97
|
+
return {
|
|
98
|
+
name: String(tool.name),
|
|
99
|
+
...(typeof tool.description === "string" ? { description: tool.description } : {}),
|
|
100
|
+
...(annotations ? { annotations } : {}),
|
|
101
|
+
...(inputSchema ? { inputSchema } : {}),
|
|
102
|
+
...(outputSchema ? { outputSchema } : {}),
|
|
103
|
+
...(tool.classification ? { classification: tool.classification } : {}),
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ConnectorCallError } from "./errors.js";
|
|
2
|
+
import { percentDecoded } from "./credential-url.js";
|
|
3
|
+
export function isDownstreamInputResult(value) {
|
|
4
|
+
return value !== null && typeof value === "object" && "resultType" in value && value.resultType === "input_required";
|
|
5
|
+
}
|
|
6
|
+
const continuations = new WeakMap();
|
|
7
|
+
const capabilities = new WeakMap();
|
|
8
|
+
export function bindDownstreamCapabilities(scope, declared) {
|
|
9
|
+
capabilities.set(scope, declared);
|
|
10
|
+
}
|
|
11
|
+
/** Only the relayable declarations travel to the downstream SDK, never grants. */
|
|
12
|
+
export function downstreamInputCapabilities(scope) {
|
|
13
|
+
return capabilities.get(scope) ?? {};
|
|
14
|
+
}
|
|
15
|
+
export function bindDownstreamContinuation(scope, target) {
|
|
16
|
+
continuations.set(scope, target);
|
|
17
|
+
}
|
|
18
|
+
export function clearDownstreamContinuation(scope) {
|
|
19
|
+
continuations.delete(scope);
|
|
20
|
+
}
|
|
21
|
+
export function downstreamWriteContinuation(scope) {
|
|
22
|
+
return continuations.get(scope)?.write === true;
|
|
23
|
+
}
|
|
24
|
+
/** Check raw output before redaction, paging, caching, or shape observation. */
|
|
25
|
+
export function assertNoPrivateStateEchoes(value, states) {
|
|
26
|
+
const refused = () => new ConnectorCallError("input_required_invalid", "The downstream response exposes private continuation state.");
|
|
27
|
+
try {
|
|
28
|
+
// Literal matching avoids constructing a regex from a 64 KiB opaque value.
|
|
29
|
+
// There is no credential length floor, and percent hex is case insensitive.
|
|
30
|
+
const percent = (text) => text.replace(/%[0-9a-f]{2}/gi, (hex) => hex.toUpperCase());
|
|
31
|
+
const forms = new Set();
|
|
32
|
+
for (const state of states)
|
|
33
|
+
if (state)
|
|
34
|
+
for (const framed of [state, `Bearer ${state}`, `token ${state}`]) {
|
|
35
|
+
forms.add(percent(framed));
|
|
36
|
+
try {
|
|
37
|
+
forms.add(percent(encodeURIComponent(framed)));
|
|
38
|
+
forms.add(percent(encodeURI(framed)));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* Lone surrogates still match literal and JSON-escaped views. */
|
|
42
|
+
}
|
|
43
|
+
forms.add(percent(new URLSearchParams({ value: framed }).toString().slice(6)));
|
|
44
|
+
let binary = "";
|
|
45
|
+
for (const byte of new TextEncoder().encode(framed))
|
|
46
|
+
binary += String.fromCharCode(byte);
|
|
47
|
+
const encoded = btoa(binary);
|
|
48
|
+
forms.add(encoded);
|
|
49
|
+
forms.add(encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""));
|
|
50
|
+
}
|
|
51
|
+
if (!forms.size)
|
|
52
|
+
return;
|
|
53
|
+
const escapes = {
|
|
54
|
+
'"': '"',
|
|
55
|
+
"\\": "\\",
|
|
56
|
+
"/": "/",
|
|
57
|
+
b: "\b",
|
|
58
|
+
f: "\f",
|
|
59
|
+
n: "\n",
|
|
60
|
+
r: "\r",
|
|
61
|
+
t: "\t",
|
|
62
|
+
};
|
|
63
|
+
const matches = (text) => {
|
|
64
|
+
for (let pass = 0; pass < 3; pass++) {
|
|
65
|
+
const unescaped = percentDecoded(text);
|
|
66
|
+
const views = [percent(text), percent(unescaped)];
|
|
67
|
+
if ([...forms].some((form) => views.some((view) => view.includes(form))))
|
|
68
|
+
return true;
|
|
69
|
+
const decoded = unescaped.replace(/\\(?:u[0-9a-fA-F]{4}|["\\/bfnrt])/g, (escape) => escape[1] === "u" ? String.fromCharCode(parseInt(escape.slice(2), 16)) : escapes[escape[1]]);
|
|
70
|
+
if (decoded === text)
|
|
71
|
+
break;
|
|
72
|
+
text = decoded;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
};
|
|
76
|
+
const seen = new WeakSet();
|
|
77
|
+
const visit = (item) => {
|
|
78
|
+
if (typeof item === "string")
|
|
79
|
+
return matches(item);
|
|
80
|
+
if (item === null || typeof item === "boolean" || typeof item === "number")
|
|
81
|
+
return matches(JSON.stringify(item));
|
|
82
|
+
if (!item || typeof item !== "object" || seen.has(item))
|
|
83
|
+
return false;
|
|
84
|
+
seen.add(item);
|
|
85
|
+
if (item instanceof Error && matches(item.message))
|
|
86
|
+
return true;
|
|
87
|
+
const content = Object.getOwnPropertyDescriptor(item, "content")?.value;
|
|
88
|
+
if (Array.isArray(content)) {
|
|
89
|
+
const texts = content
|
|
90
|
+
.filter((block) => block?.type === "text" && typeof block.text === "string")
|
|
91
|
+
.map((block) => block.text);
|
|
92
|
+
if (matches(texts.join("")) || matches(texts.join("\n")))
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
return Object.entries(Object.getOwnPropertyDescriptors(item)).some(([key, descriptor]) => matches(key) || ("value" in descriptor && visit(descriptor.value)));
|
|
96
|
+
};
|
|
97
|
+
if (visit(value))
|
|
98
|
+
throw refused();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// A privacy-check failure must never expose a matcher diagnostic or state.
|
|
102
|
+
throw refused();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function assertDownstreamOutputSafe(scope, value) {
|
|
106
|
+
const continuation = continuations.get(scope);
|
|
107
|
+
if (continuation)
|
|
108
|
+
assertNoPrivateStateEchoes(value, continuation.privateStates);
|
|
109
|
+
}
|
|
110
|
+
/** The host binds one verified target before invocation constructs its context. */
|
|
111
|
+
export function downstreamContinuation(scope, connector, address) {
|
|
112
|
+
const continuation = continuations.get(scope);
|
|
113
|
+
if (!continuation)
|
|
114
|
+
return undefined;
|
|
115
|
+
if (continuation.connector !== connector || continuation.address !== address) {
|
|
116
|
+
throw new ConnectorCallError("input_required_invalid", "The resolved target no longer matches this continuation.");
|
|
117
|
+
}
|
|
118
|
+
return continuation.input;
|
|
119
|
+
}
|