@zackbart/connecta 0.15.0 → 0.16.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/AGENTS.md +28 -15
- package/CHANGELOG.md +493 -0
- package/README.md +19 -4
- package/bin/connecta.mjs +44 -3
- package/dist/access-tokens.d.ts +0 -1
- package/dist/access-tokens.js +0 -1
- package/dist/activity.d.ts +50 -2
- package/dist/activity.js +34 -1
- package/dist/apps-shell.d.ts +0 -1
- package/dist/apps-shell.js +0 -1
- package/dist/auth/bearer.d.ts +0 -1
- package/dist/auth/bearer.js +0 -1
- package/dist/auth/clerk.d.ts +0 -1
- package/dist/auth/clerk.js +45 -9
- package/dist/auth/downstream-oauth.d.ts +0 -1
- package/dist/auth/downstream-oauth.js +0 -1
- package/dist/call-admission.d.ts +0 -1
- package/dist/call-admission.js +0 -1
- package/dist/catalog-drift.d.ts +99 -0
- package/dist/catalog-drift.js +251 -0
- package/dist/catalog-fingerprint.d.ts +0 -1
- package/dist/catalog-fingerprint.js +0 -1
- package/dist/catalog-limits.d.ts +0 -1
- package/dist/catalog-limits.js +0 -1
- package/dist/catalog-service.d.ts +0 -1
- package/dist/catalog-service.js +0 -1
- package/dist/catalog.d.ts +0 -1
- package/dist/catalog.js +0 -1
- package/dist/concurrency.d.ts +0 -1
- package/dist/concurrency.js +0 -1
- package/dist/connector-scope.d.ts +0 -1
- package/dist/connector-scope.js +0 -1
- package/dist/connectors/api.d.ts +29 -21
- package/dist/connectors/api.js +45 -19
- package/dist/connectors/guarded-fetch.d.ts +106 -0
- package/dist/connectors/guarded-fetch.js +289 -0
- package/dist/connectors/remote-mcp.d.ts +0 -1
- package/dist/connectors/remote-mcp.js +0 -1
- package/dist/credentials.d.ts +0 -1
- package/dist/credentials.js +0 -1
- package/dist/errors.d.ts +0 -1
- package/dist/errors.js +0 -1
- package/dist/execute.d.ts +0 -1
- package/dist/execute.js +0 -1
- package/dist/executor-admission.d.ts +11 -1
- package/dist/executor-admission.js +28 -1
- package/dist/executor-result.d.ts +0 -1
- package/dist/executor-result.js +0 -1
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-child.js +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -1
- package/dist/executors/quickjs-protocol.js +0 -1
- package/dist/executors/quickjs-runtime.d.ts +0 -1
- package/dist/executors/quickjs-runtime.js +0 -1
- package/dist/executors/quickjs.d.ts +0 -1
- package/dist/executors/quickjs.js +2 -1
- package/dist/favicon.d.ts +0 -1
- package/dist/favicon.js +0 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +24 -7
- package/dist/invocation.d.ts +0 -1
- package/dist/invocation.js +0 -1
- package/dist/json-schema.d.ts +0 -1
- package/dist/json-schema.js +0 -1
- package/dist/mcp-result.d.ts +0 -1
- package/dist/mcp-result.js +0 -1
- package/dist/meta-tools.d.ts +0 -1
- package/dist/meta-tools.js +0 -1
- package/dist/node.d.ts +0 -1
- package/dist/node.js +0 -1
- package/dist/operator-ui/generated.d.ts +2 -3
- package/dist/operator-ui/generated.js +3 -4
- package/dist/operator-ui/model.d.ts +10 -1
- package/dist/operator-ui/model.js +0 -1
- package/dist/operator-ui/view.d.ts +167 -0
- package/dist/operator-ui/view.js +270 -0
- package/dist/providers/cloudflare.d.ts +5 -2
- package/dist/providers/cloudflare.js +435 -328
- package/dist/providers/linear.d.ts +26 -5
- package/dist/providers/linear.js +32 -60
- package/dist/providers/mixpanel.d.ts +35 -4
- package/dist/providers/mixpanel.js +53 -78
- package/dist/providers/notion.d.ts +0 -1
- package/dist/providers/notion.js +87 -42
- package/dist/providers/stripe.d.ts +13 -1
- package/dist/providers/stripe.js +31 -59
- package/dist/registry.d.ts +34 -2
- package/dist/registry.js +85 -5
- package/dist/retired-toolkits.d.ts +0 -1
- package/dist/retired-toolkits.js +0 -1
- package/dist/routes/access-tokens.d.ts +0 -1
- package/dist/routes/access-tokens.js +0 -1
- package/dist/routes/activity.d.ts +0 -1
- package/dist/routes/activity.js +0 -1
- package/dist/routes/credentials.d.ts +0 -1
- package/dist/routes/credentials.js +0 -1
- package/dist/routes/mcp.d.ts +0 -1
- package/dist/routes/mcp.js +0 -1
- package/dist/routes/oauth.d.ts +0 -1
- package/dist/routes/oauth.js +0 -1
- package/dist/routes/shared.d.ts +2 -1
- package/dist/routes/shared.js +0 -1
- package/dist/routes/ui.d.ts +0 -1
- package/dist/routes/ui.js +0 -1
- package/dist/server.d.ts +0 -1
- package/dist/server.js +11 -1
- package/dist/skills.d.ts +0 -1
- package/dist/skills.js +0 -1
- package/dist/storage/file.d.ts +0 -1
- package/dist/storage/file.js +0 -1
- package/dist/storage/memory.d.ts +0 -1
- package/dist/storage/memory.js +0 -1
- package/dist/timeout.d.ts +0 -1
- package/dist/timeout.js +0 -1
- package/dist/tool-safety.d.ts +0 -1
- package/dist/tool-safety.js +0 -1
- package/dist/types.d.ts +45 -1
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +0 -1
- package/dist/ui.js +13 -137
- package/dist/validate.d.ts +13 -15
- package/dist/validate.js +24 -11
- package/dist/version.d.ts +1 -2
- package/dist/version.js +1 -2
- package/documentation/architecture.md +186 -5
- package/documentation/auth.md +11 -0
- package/documentation/call-admission.md +142 -6
- package/documentation/cloudflare.md +95 -27
- package/documentation/code-mode.md +12 -12
- package/documentation/connector-guides.md +172 -5
- package/documentation/connectors.md +98 -7
- package/documentation/linear.md +26 -9
- package/documentation/mcp-ui-design.md +2 -2
- package/documentation/mixpanel.md +48 -16
- package/documentation/notion.md +16 -0
- package/documentation/operations.md +283 -5
- package/documentation/operator-ui.md +120 -5
- package/documentation/provider-audit.md +160 -0
- package/documentation/provider-conventions.md +631 -0
- package/documentation/request-admission.md +167 -5
- package/documentation/rich-output-design.md +1 -2
- package/documentation/stripe.md +27 -12
- package/ethos.md +14 -9
- package/examples/worker/README.md +96 -12
- package/examples/worker/src/index.ts +46 -1
- package/examples/worker/wrangler.jsonc +14 -1
- package/package.json +4 -5
- package/templates/node/.dockerignore +10 -0
- package/templates/node/.env.example +40 -3
- package/templates/node/AGENTS.md +11 -1
- package/templates/node/Dockerfile +49 -0
- package/templates/node/README.md +117 -2
- package/templates/node/docker-compose.yml +39 -0
- package/templates/node/package.json +1 -1
- package/templates/node/src/file-activity.ts +182 -0
- package/templates/node/src/index.ts +80 -4
- package/assets/connecta-clay-hero.png +0 -0
- package/dist/access-tokens.d.ts.map +0 -1
- package/dist/access-tokens.js.map +0 -1
- package/dist/activity.d.ts.map +0 -1
- package/dist/activity.js.map +0 -1
- package/dist/apps-shell.d.ts.map +0 -1
- package/dist/apps-shell.js.map +0 -1
- package/dist/auth/bearer.d.ts.map +0 -1
- package/dist/auth/bearer.js.map +0 -1
- package/dist/auth/clerk.d.ts.map +0 -1
- package/dist/auth/clerk.js.map +0 -1
- package/dist/auth/downstream-oauth.d.ts.map +0 -1
- package/dist/auth/downstream-oauth.js.map +0 -1
- package/dist/call-admission.d.ts.map +0 -1
- package/dist/call-admission.js.map +0 -1
- package/dist/catalog-fingerprint.d.ts.map +0 -1
- package/dist/catalog-fingerprint.js.map +0 -1
- package/dist/catalog-limits.d.ts.map +0 -1
- package/dist/catalog-limits.js.map +0 -1
- package/dist/catalog-service.d.ts.map +0 -1
- package/dist/catalog-service.js.map +0 -1
- package/dist/catalog.d.ts.map +0 -1
- package/dist/catalog.js.map +0 -1
- package/dist/concurrency.d.ts.map +0 -1
- package/dist/concurrency.js.map +0 -1
- package/dist/connector-scope.d.ts.map +0 -1
- package/dist/connector-scope.js.map +0 -1
- package/dist/connectors/api.d.ts.map +0 -1
- package/dist/connectors/api.js.map +0 -1
- package/dist/connectors/remote-mcp.d.ts.map +0 -1
- package/dist/connectors/remote-mcp.js.map +0 -1
- package/dist/credentials.d.ts.map +0 -1
- package/dist/credentials.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/execute.d.ts.map +0 -1
- package/dist/execute.js.map +0 -1
- package/dist/executor-admission.d.ts.map +0 -1
- package/dist/executor-admission.js.map +0 -1
- package/dist/executor-result.d.ts.map +0 -1
- package/dist/executor-result.js.map +0 -1
- package/dist/executors/quickjs-child.d.ts.map +0 -1
- package/dist/executors/quickjs-child.js.map +0 -1
- package/dist/executors/quickjs-protocol.d.ts.map +0 -1
- package/dist/executors/quickjs-protocol.js.map +0 -1
- package/dist/executors/quickjs-runtime.d.ts.map +0 -1
- package/dist/executors/quickjs-runtime.js.map +0 -1
- package/dist/executors/quickjs.d.ts.map +0 -1
- package/dist/executors/quickjs.js.map +0 -1
- package/dist/favicon.d.ts.map +0 -1
- package/dist/favicon.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/invocation.d.ts.map +0 -1
- package/dist/invocation.js.map +0 -1
- package/dist/json-schema.d.ts.map +0 -1
- package/dist/json-schema.js.map +0 -1
- package/dist/mcp-result.d.ts.map +0 -1
- package/dist/mcp-result.js.map +0 -1
- package/dist/meta-tools.d.ts.map +0 -1
- package/dist/meta-tools.js.map +0 -1
- package/dist/node.d.ts.map +0 -1
- package/dist/node.js.map +0 -1
- package/dist/operator-ui/generated.d.ts.map +0 -1
- package/dist/operator-ui/generated.js.map +0 -1
- package/dist/operator-ui/model.d.ts.map +0 -1
- package/dist/operator-ui/model.js.map +0 -1
- package/dist/providers/cloudflare.d.ts.map +0 -1
- package/dist/providers/cloudflare.js.map +0 -1
- package/dist/providers/linear.d.ts.map +0 -1
- package/dist/providers/linear.js.map +0 -1
- package/dist/providers/mixpanel.d.ts.map +0 -1
- package/dist/providers/mixpanel.js.map +0 -1
- package/dist/providers/notion.d.ts.map +0 -1
- package/dist/providers/notion.js.map +0 -1
- package/dist/providers/stripe.d.ts.map +0 -1
- package/dist/providers/stripe.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/retired-toolkits.d.ts.map +0 -1
- package/dist/retired-toolkits.js.map +0 -1
- package/dist/routes/access-tokens.d.ts.map +0 -1
- package/dist/routes/access-tokens.js.map +0 -1
- package/dist/routes/activity.d.ts.map +0 -1
- package/dist/routes/activity.js.map +0 -1
- package/dist/routes/credentials.d.ts.map +0 -1
- package/dist/routes/credentials.js.map +0 -1
- package/dist/routes/mcp.d.ts.map +0 -1
- package/dist/routes/mcp.js.map +0 -1
- package/dist/routes/oauth.d.ts.map +0 -1
- package/dist/routes/oauth.js.map +0 -1
- package/dist/routes/shared.d.ts.map +0 -1
- package/dist/routes/shared.js.map +0 -1
- package/dist/routes/ui.d.ts.map +0 -1
- package/dist/routes/ui.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js.map +0 -1
- package/dist/storage/file.d.ts.map +0 -1
- package/dist/storage/file.js.map +0 -1
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js.map +0 -1
- package/dist/timeout.d.ts.map +0 -1
- package/dist/timeout.js.map +0 -1
- package/dist/tool-safety.d.ts.map +0 -1
- package/dist/tool-safety.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/ui.d.ts.map +0 -1
- package/dist/ui.js.map +0 -1
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
- package/examples/node/README.md +0 -52
- package/examples/node/src/index.ts +0 -71
- package/src/access-tokens.ts +0 -289
- package/src/activity.ts +0 -234
- package/src/apps-shell.ts +0 -370
- package/src/auth/bearer.ts +0 -61
- package/src/auth/clerk.ts +0 -553
- package/src/auth/downstream-oauth.ts +0 -575
- package/src/call-admission.ts +0 -521
- package/src/catalog-fingerprint.ts +0 -52
- package/src/catalog-limits.ts +0 -11
- package/src/catalog-service.ts +0 -1153
- package/src/catalog.ts +0 -790
- package/src/concurrency.ts +0 -39
- package/src/connector-scope.ts +0 -66
- package/src/connectors/api.ts +0 -159
- package/src/connectors/remote-mcp.ts +0 -1090
- package/src/credentials.ts +0 -428
- package/src/errors.ts +0 -364
- package/src/execute.ts +0 -1406
- package/src/executor-admission.ts +0 -328
- package/src/executor-result.ts +0 -107
- package/src/executors/quickjs-child.ts +0 -172
- package/src/executors/quickjs-protocol.ts +0 -99
- package/src/executors/quickjs-runtime.ts +0 -470
- package/src/executors/quickjs.ts +0 -776
- package/src/favicon.ts +0 -53
- package/src/index.ts +0 -666
- package/src/invocation.ts +0 -655
- package/src/json-schema.ts +0 -11
- package/src/mcp-result.ts +0 -43
- package/src/meta-tools.ts +0 -1607
- package/src/node.ts +0 -206
- package/src/operator-ui/browser.css +0 -423
- package/src/operator-ui/browser.ts +0 -1281
- package/src/operator-ui/generated.ts +0 -4
- package/src/operator-ui/model.ts +0 -111
- package/src/providers/cloudflare.ts +0 -3803
- package/src/providers/linear.ts +0 -301
- package/src/providers/mixpanel.ts +0 -228
- package/src/providers/notion.ts +0 -1879
- package/src/providers/stripe.ts +0 -306
- package/src/registry.ts +0 -1085
- package/src/retired-toolkits.ts +0 -14
- package/src/routes/access-tokens.ts +0 -115
- package/src/routes/activity.ts +0 -215
- package/src/routes/credentials.ts +0 -254
- package/src/routes/mcp.ts +0 -429
- package/src/routes/oauth.ts +0 -322
- package/src/routes/shared.ts +0 -247
- package/src/routes/ui.ts +0 -152
- package/src/server.ts +0 -195
- package/src/skills.ts +0 -271
- package/src/storage/file.ts +0 -123
- package/src/storage/memory.ts +0 -44
- package/src/timeout.ts +0 -84
- package/src/tool-safety.ts +0 -15
- package/src/types.ts +0 -523
- package/src/ui.ts +0 -744
- package/src/validate.ts +0 -387
- package/src/version.ts +0 -7
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mechanical half of a hand-written connector's HTTP transport.
|
|
3
|
+
*
|
|
4
|
+
* Every `api()` surface that speaks HTTP re-derives the same handful of safety
|
|
5
|
+
* properties: the request lands inside the base URL and nowhere else, the
|
|
6
|
+
* credential rides exactly one origin, an unreachable provider becomes a typed
|
|
7
|
+
* failure instead of a raw `TypeError`, and the response is read with a
|
|
8
|
+
* ceiling on it. Cloudflare and Notion each grew a private copy of that
|
|
9
|
+
* machinery, subtly different in ways neither author intended, and a third
|
|
10
|
+
* author would have grown a third.
|
|
11
|
+
*
|
|
12
|
+
* What this deliberately does not own is *meaning*. It never reads a status
|
|
13
|
+
* code, never decides a 403 is recoverable, and never invents an
|
|
14
|
+
* authentication scheme: `authenticate` supplies the headers, and the caller's
|
|
15
|
+
* mapper reads the response. That split is not fastidiousness — Notion's 403
|
|
16
|
+
* (a capability the integration was never granted, which re-authorizing cannot
|
|
17
|
+
* fix) and Cloudflare's 403 (a token scope) want opposite next moves, and no
|
|
18
|
+
* shared helper can tell them apart without provider knowledge it has no
|
|
19
|
+
* business holding.
|
|
20
|
+
*
|
|
21
|
+
* Web APIs only: this is reachable from the root entry and stays that way.
|
|
22
|
+
*/
|
|
23
|
+
import { ConnectorCallError } from "../errors.js";
|
|
24
|
+
/** Statuses that instruct a client to re-send somewhere else. Never followed. */
|
|
25
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
26
|
+
/** Statuses the HTTP spec defines as carrying no body at all. */
|
|
27
|
+
const BODILESS_STATUSES = new Set([204, 205, 304]);
|
|
28
|
+
const decoder = new TextDecoder();
|
|
29
|
+
const encoder = new TextEncoder();
|
|
30
|
+
/**
|
|
31
|
+
* A base URL is deployment configuration, so a bad one is a structural mistake
|
|
32
|
+
* and throws where it is written rather than on the first call that uses it.
|
|
33
|
+
*/
|
|
34
|
+
function confinementBase(provider, baseUrl) {
|
|
35
|
+
let base;
|
|
36
|
+
try {
|
|
37
|
+
base = new URL(baseUrl);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
throw new Error(`${provider} baseUrl must be an absolute URL: ${baseUrl}`);
|
|
41
|
+
}
|
|
42
|
+
const loopback = base.hostname === "localhost" ||
|
|
43
|
+
base.hostname === "127.0.0.1" ||
|
|
44
|
+
base.hostname === "[::1]";
|
|
45
|
+
if (base.protocol !== "https:" && !(base.protocol === "http:" && loopback)) {
|
|
46
|
+
throw new Error(`${provider} baseUrl must be https (http is allowed only for a loopback proxy or test double): ${baseUrl}`);
|
|
47
|
+
}
|
|
48
|
+
if (base.username || base.password) {
|
|
49
|
+
throw new Error(`${provider} baseUrl must not embed URL credentials; authentication belongs in headers.`);
|
|
50
|
+
}
|
|
51
|
+
if (base.search || base.hash) {
|
|
52
|
+
throw new Error(`${provider} baseUrl must not carry a query or fragment: ${baseUrl}`);
|
|
53
|
+
}
|
|
54
|
+
return base;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a provider-relative path beneath the base and prove it stayed there.
|
|
58
|
+
*
|
|
59
|
+
* The proof is the point. `new URL` normalizes `..` and percent-escapes for
|
|
60
|
+
* us, which is convenient and also exactly how a path escapes its prefix, so
|
|
61
|
+
* the origin and prefix are re-checked *after* normalization rather than
|
|
62
|
+
* before it.
|
|
63
|
+
*/
|
|
64
|
+
function confinedUrl(provider, base, basePath, request) {
|
|
65
|
+
const path = request.path;
|
|
66
|
+
if (!path.startsWith("/")) {
|
|
67
|
+
throw new ConnectorCallError("invalid_args", `A ${provider} request path is provider-relative and must begin with "/".`);
|
|
68
|
+
}
|
|
69
|
+
if (path.includes("?") || path.includes("#")) {
|
|
70
|
+
throw new ConnectorCallError("invalid_args", `A ${provider} request path carries no query or fragment; put query parameters in the query argument.`);
|
|
71
|
+
}
|
|
72
|
+
let url;
|
|
73
|
+
try {
|
|
74
|
+
url = new URL(`${base.origin}${basePath}${path}`);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
throw new ConnectorCallError("invalid_args", `A ${provider} request path did not resolve to a valid URL.`);
|
|
78
|
+
}
|
|
79
|
+
if (url.origin !== base.origin ||
|
|
80
|
+
!`${url.pathname}/`.startsWith(`${basePath}/`)) {
|
|
81
|
+
throw new ConnectorCallError("invalid_args", `A ${provider} request path escaped the connector's base URL.`);
|
|
82
|
+
}
|
|
83
|
+
for (const [key, value] of Object.entries(request.query ?? {})) {
|
|
84
|
+
if (value === undefined)
|
|
85
|
+
continue;
|
|
86
|
+
url.searchParams.set(key, String(value));
|
|
87
|
+
}
|
|
88
|
+
return url;
|
|
89
|
+
}
|
|
90
|
+
function oversized(provider, limit, detail) {
|
|
91
|
+
return new ConnectorCallError("connector_call_failed", `${provider} returned ${detail}, past this connector's ${limit}-byte response ceiling.`, { retryable: false });
|
|
92
|
+
}
|
|
93
|
+
/** The response body as a `ReadableStream`, or null when there is none to read. */
|
|
94
|
+
function readableBody(response) {
|
|
95
|
+
const body = response.body;
|
|
96
|
+
return body && typeof body.getReader === "function"
|
|
97
|
+
? body
|
|
98
|
+
: null;
|
|
99
|
+
}
|
|
100
|
+
/** Read a stream to the ceiling and stop there — never past it. */
|
|
101
|
+
async function drain(provider, stream, limit) {
|
|
102
|
+
const reader = stream.getReader();
|
|
103
|
+
const chunks = [];
|
|
104
|
+
let total = 0;
|
|
105
|
+
try {
|
|
106
|
+
while (true) {
|
|
107
|
+
const { done, value } = await reader.read();
|
|
108
|
+
if (done)
|
|
109
|
+
break;
|
|
110
|
+
if (!value)
|
|
111
|
+
continue;
|
|
112
|
+
total += value.byteLength;
|
|
113
|
+
if (total > limit) {
|
|
114
|
+
await reader.cancel().catch(() => { });
|
|
115
|
+
throw oversized(provider, limit, "more bytes than it may");
|
|
116
|
+
}
|
|
117
|
+
chunks.push(value);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
reader.releaseLock();
|
|
122
|
+
}
|
|
123
|
+
const body = new Uint8Array(total);
|
|
124
|
+
let offset = 0;
|
|
125
|
+
for (const chunk of chunks) {
|
|
126
|
+
body.set(chunk, offset);
|
|
127
|
+
offset += chunk.byteLength;
|
|
128
|
+
}
|
|
129
|
+
return body;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Wrap a `Response` in the bounded read surface.
|
|
133
|
+
*
|
|
134
|
+
* Three paths, for one honest reason. A status the spec says carries no body
|
|
135
|
+
* is answered as empty without touching the response at all. Otherwise, when
|
|
136
|
+
* the runtime hands back a readable body — every real response on Node and on
|
|
137
|
+
* Workers — the ceiling is enforced *while* reading, so an oversized payload
|
|
138
|
+
* is abandoned rather than buffered. When it does not, there is nothing to
|
|
139
|
+
* meter mid-flight: `bytes()` and `text()` read the body whole and check the
|
|
140
|
+
* ceiling against what came back, and `json()` — with no bytes of its own to
|
|
141
|
+
* count — takes the stand-in at its word. That last path is the weaker
|
|
142
|
+
* guarantee and says so, but a real `fetch` Response with a body always
|
|
143
|
+
* streams, so it is not a path a provider takes in production; it is what a
|
|
144
|
+
* hand-built test double gets.
|
|
145
|
+
*/
|
|
146
|
+
function boundedResponse(provider, response, limit) {
|
|
147
|
+
if (BODILESS_STATUSES.has(response.status)) {
|
|
148
|
+
return {
|
|
149
|
+
status: response.status,
|
|
150
|
+
ok: response.ok,
|
|
151
|
+
headers: response.headers,
|
|
152
|
+
bytes: async () => new Uint8Array(),
|
|
153
|
+
text: async () => "",
|
|
154
|
+
json: async () => undefined,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const stream = readableBody(response);
|
|
158
|
+
let read;
|
|
159
|
+
const bytes = () => {
|
|
160
|
+
read ??= stream
|
|
161
|
+
? drain(provider, stream, limit)
|
|
162
|
+
: response.arrayBuffer().then((buffer) => {
|
|
163
|
+
if (buffer.byteLength > limit) {
|
|
164
|
+
throw oversized(provider, limit, `${buffer.byteLength} bytes`);
|
|
165
|
+
}
|
|
166
|
+
return new Uint8Array(buffer);
|
|
167
|
+
});
|
|
168
|
+
return read;
|
|
169
|
+
};
|
|
170
|
+
return {
|
|
171
|
+
status: response.status,
|
|
172
|
+
ok: response.ok,
|
|
173
|
+
headers: response.headers,
|
|
174
|
+
bytes,
|
|
175
|
+
async text() {
|
|
176
|
+
if (stream)
|
|
177
|
+
return decoder.decode(await bytes());
|
|
178
|
+
const body = await response.text();
|
|
179
|
+
const size = encoder.encode(body).length;
|
|
180
|
+
if (size > limit)
|
|
181
|
+
throw oversized(provider, limit, `${size} bytes`);
|
|
182
|
+
return body;
|
|
183
|
+
},
|
|
184
|
+
async json() {
|
|
185
|
+
// No stream means no bytes to count: a stand-in that answers `json()`
|
|
186
|
+
// directly is taken at its word, which is the one accessor on the one
|
|
187
|
+
// path where the ceiling cannot be applied.
|
|
188
|
+
if (!stream)
|
|
189
|
+
return await response.json();
|
|
190
|
+
const body = decoder.decode(await bytes());
|
|
191
|
+
return body.trim() === "" ? undefined : JSON.parse(body);
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Build the guarded transport one hand-written connector sends every request
|
|
197
|
+
* through.
|
|
198
|
+
*
|
|
199
|
+
* ```ts
|
|
200
|
+
* const send = guardedFetch({
|
|
201
|
+
* provider: "Billing",
|
|
202
|
+
* baseUrl: "https://billing.internal.example/v1",
|
|
203
|
+
* maxResponseBytes: 4 * 1024 * 1024,
|
|
204
|
+
* headers: { Accept: "application/json" },
|
|
205
|
+
* authenticate: async (ctx) => {
|
|
206
|
+
* const token = await ctx.credential?.get();
|
|
207
|
+
* if (!token) throw new ConnectorCallError("auth_required", "…");
|
|
208
|
+
* return { Authorization: `Bearer ${token}` };
|
|
209
|
+
* },
|
|
210
|
+
* });
|
|
211
|
+
*
|
|
212
|
+
* const invoice = await send({ method: "GET", path: `/invoices/${id}` }, ctx, (response) => {
|
|
213
|
+
* if (!response.ok) throw billingFailure(response.status);
|
|
214
|
+
* return response.json();
|
|
215
|
+
* });
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
export function guardedFetch(options) {
|
|
219
|
+
const { provider, maxResponseBytes: limit } = options;
|
|
220
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
221
|
+
throw new Error(`${provider} maxResponseBytes must be a whole number of bytes >= 1; received ${String(limit)}.`);
|
|
222
|
+
}
|
|
223
|
+
const base = confinementBase(provider, options.baseUrl);
|
|
224
|
+
// "" for a root base, so joining never doubles the separator.
|
|
225
|
+
const basePath = base.pathname.replace(/\/+$/, "");
|
|
226
|
+
const constantHeaders = { ...options.headers };
|
|
227
|
+
return async function send(request, ctx, map) {
|
|
228
|
+
if (request.body !== undefined && request.rawBody !== undefined) {
|
|
229
|
+
throw new Error(`A ${provider} request cannot carry both a JSON body and a raw body.`);
|
|
230
|
+
}
|
|
231
|
+
const url = confinedUrl(provider, base, basePath, request);
|
|
232
|
+
const headers = { ...constantHeaders };
|
|
233
|
+
for (const [name, value] of Object.entries(request.headers ?? {})) {
|
|
234
|
+
if (value !== undefined)
|
|
235
|
+
headers[name] = value;
|
|
236
|
+
}
|
|
237
|
+
if (request.body !== undefined && !hasHeader(headers, "content-type")) {
|
|
238
|
+
headers["Content-Type"] = "application/json";
|
|
239
|
+
}
|
|
240
|
+
// Authentication is applied last and may not be shadowed. A caller-shaped
|
|
241
|
+
// header that reaches this point wearing the credential's name is a bug
|
|
242
|
+
// worth failing on, not a precedence question worth resolving quietly.
|
|
243
|
+
for (const [name, value] of Object.entries(await options.authenticate(ctx))) {
|
|
244
|
+
const shadow = hasHeader(headers, name);
|
|
245
|
+
if (shadow) {
|
|
246
|
+
throw new ConnectorCallError("invalid_args", `A ${provider} request may not set the ${shadow} header; authentication is connector-owned.`);
|
|
247
|
+
}
|
|
248
|
+
headers[name] = value;
|
|
249
|
+
}
|
|
250
|
+
let response;
|
|
251
|
+
try {
|
|
252
|
+
response = await fetch(url.toString(), {
|
|
253
|
+
method: request.method,
|
|
254
|
+
headers,
|
|
255
|
+
...(request.body !== undefined
|
|
256
|
+
? { body: JSON.stringify(request.body) }
|
|
257
|
+
: request.rawBody !== undefined
|
|
258
|
+
? { body: request.rawBody }
|
|
259
|
+
: {}),
|
|
260
|
+
// Never follow a redirect. A 3xx is an instruction to re-send the
|
|
261
|
+
// connector's credential to whatever origin the Location names, and a
|
|
262
|
+
// confinement a redirect can undo was never a confinement. There is no
|
|
263
|
+
// ambient credential to omit besides that — neither runtime's `fetch`
|
|
264
|
+
// keeps a cookie jar — so the headers `authenticate` returned are the
|
|
265
|
+
// only authority the request carries.
|
|
266
|
+
redirect: "manual",
|
|
267
|
+
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
catch (cause) {
|
|
271
|
+
throw new ConnectorCallError("unavailable", `Could not reach the ${provider} API: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
272
|
+
}
|
|
273
|
+
if (REDIRECT_STATUSES.has(response.status)) {
|
|
274
|
+
await response.body?.cancel().catch(() => { });
|
|
275
|
+
throw new ConnectorCallError("connector_call_failed", `${provider} answered HTTP ${response.status} with a redirect; this connector talks to exactly one origin and never forwards its credential to another.`, { retryable: false });
|
|
276
|
+
}
|
|
277
|
+
const declared = Number(response.headers.get("content-length"));
|
|
278
|
+
if (Number.isFinite(declared) && declared > limit) {
|
|
279
|
+
await response.body?.cancel().catch(() => { });
|
|
280
|
+
throw oversized(provider, limit, `a declared ${declared} bytes`);
|
|
281
|
+
}
|
|
282
|
+
return await map(boundedResponse(provider, response, limit), ctx);
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/** The stored spelling of a header already present, case-insensitively. */
|
|
286
|
+
function hasHeader(headers, name) {
|
|
287
|
+
const wanted = name.toLowerCase();
|
|
288
|
+
return Object.keys(headers).find((key) => key.toLowerCase() === wanted);
|
|
289
|
+
}
|
package/dist/credentials.d.ts
CHANGED
package/dist/credentials.js
CHANGED
package/dist/errors.d.ts
CHANGED
package/dist/errors.js
CHANGED
package/dist/execute.d.ts
CHANGED
package/dist/execute.js
CHANGED
|
@@ -69,10 +69,20 @@ export declare class AdmissionController {
|
|
|
69
69
|
}
|
|
70
70
|
/** Preserve the one-method Workers seam while recognizing richer Node executors. */
|
|
71
71
|
export declare function isAdmittingExecutor(executor: Executor): executor is AdmittingExecutor;
|
|
72
|
+
/**
|
|
73
|
+
* Best-effort name for the configured sandbox, for `/health` and through it
|
|
74
|
+
* `connecta doctor` — which otherwise has to guess, and guessed QuickJS at
|
|
75
|
+
* every deployment including the Workers one ([#368]). The executor seam is
|
|
76
|
+
* structural, so this reads what is already there: an explicit `name`, else
|
|
77
|
+
* the constructor name a class-shaped executor carries for free. The value is
|
|
78
|
+
* a stranger's string bound for a public response body and an operator's
|
|
79
|
+
* terminal, so it is sanitized rather than trusted; nothing identifiable
|
|
80
|
+
* reports nothing, and doctor says so instead of naming a sandbox.
|
|
81
|
+
*/
|
|
82
|
+
export declare function executorName(executor: Executor): string | undefined;
|
|
72
83
|
/**
|
|
73
84
|
* Give a structurally-compatible but otherwise unbounded executor the same
|
|
74
85
|
* admission contract as the built-in Node executor. The wrapper owns only the
|
|
75
86
|
* queue; closing the underlying runtime remains the Connecta lifecycle's job.
|
|
76
87
|
*/
|
|
77
88
|
export declare function withExecutorAdmission(executor: Executor, admission: AdmissionController): AdmittingExecutor;
|
|
78
|
-
//# sourceMappingURL=executor-admission.d.ts.map
|
|
@@ -205,6 +205,34 @@ export function isAdmittingExecutor(executor) {
|
|
|
205
205
|
return ("acquire" in executor &&
|
|
206
206
|
typeof executor.acquire === "function");
|
|
207
207
|
}
|
|
208
|
+
/** Terminal- and JSON-safe upper bound for an executor's self-reported name. */
|
|
209
|
+
const MAX_EXECUTOR_NAME_LENGTH = 40;
|
|
210
|
+
/**
|
|
211
|
+
* Best-effort name for the configured sandbox, for `/health` and through it
|
|
212
|
+
* `connecta doctor` — which otherwise has to guess, and guessed QuickJS at
|
|
213
|
+
* every deployment including the Workers one ([#368]). The executor seam is
|
|
214
|
+
* structural, so this reads what is already there: an explicit `name`, else
|
|
215
|
+
* the constructor name a class-shaped executor carries for free. The value is
|
|
216
|
+
* a stranger's string bound for a public response body and an operator's
|
|
217
|
+
* terminal, so it is sanitized rather than trusted; nothing identifiable
|
|
218
|
+
* reports nothing, and doctor says so instead of naming a sandbox.
|
|
219
|
+
*/
|
|
220
|
+
export function executorName(executor) {
|
|
221
|
+
const declared = executor.name;
|
|
222
|
+
const ctor = executor.constructor;
|
|
223
|
+
const raw = typeof declared === "string" && declared.trim()
|
|
224
|
+
? declared
|
|
225
|
+
: typeof ctor?.name === "string" && ctor.name !== "Object"
|
|
226
|
+
? ctor.name
|
|
227
|
+
: "";
|
|
228
|
+
const cleaned = raw
|
|
229
|
+
.replace(/[^\w .+-]+/g, " ")
|
|
230
|
+
.replace(/\s+/g, " ")
|
|
231
|
+
.trim()
|
|
232
|
+
.slice(0, MAX_EXECUTOR_NAME_LENGTH)
|
|
233
|
+
.trim();
|
|
234
|
+
return cleaned || undefined;
|
|
235
|
+
}
|
|
208
236
|
/**
|
|
209
237
|
* Give a structurally-compatible but otherwise unbounded executor the same
|
|
210
238
|
* admission contract as the built-in Node executor. The wrapper owns only the
|
|
@@ -238,4 +266,3 @@ export function withExecutorAdmission(executor, admission) {
|
|
|
238
266
|
admissionSnapshot: () => admission.snapshot(),
|
|
239
267
|
};
|
|
240
268
|
}
|
|
241
|
-
//# sourceMappingURL=executor-admission.js.map
|
|
@@ -7,4 +7,3 @@ export declare function truncateExecuteText(text: string, max: number): string;
|
|
|
7
7
|
* IPC. The parent repeats the guard for third-party Executor implementations.
|
|
8
8
|
*/
|
|
9
9
|
export declare function prepareExecuteResultForTransport(outcome: ExecuteResult): ExecuteResult;
|
|
10
|
-
//# sourceMappingURL=executor-result.d.ts.map
|
package/dist/executor-result.js
CHANGED
|
@@ -68,4 +68,3 @@ export declare function hostCallLabel(payload: {
|
|
|
68
68
|
}): string;
|
|
69
69
|
export declare function serializedBytes(text: string): number;
|
|
70
70
|
export declare function stringifyBounded(value: unknown, label: string, limit?: number): string;
|
|
71
|
-
//# sourceMappingURL=quickjs-protocol.d.ts.map
|
|
@@ -23,4 +23,3 @@ export interface QuickJsExecutionResult extends ExecuteResult {
|
|
|
23
23
|
* driving QuickJS, so a slow downstream does not consume the short CPU budget.
|
|
24
24
|
*/
|
|
25
25
|
export declare function executeQuickJs(code: string, providers: ExecutorProvider[], options: QuickJsRuntimeOptions): Promise<QuickJsExecutionResult>;
|
|
26
|
-
//# sourceMappingURL=quickjs-runtime.d.ts.map
|
|
@@ -102,6 +102,8 @@ function waitForReady(ready, signal) {
|
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
104
|
class QuickJsChildPool {
|
|
105
|
+
/** What `/health` and `connecta doctor` call this sandbox (#368). */
|
|
106
|
+
name = "QuickJS";
|
|
105
107
|
admission;
|
|
106
108
|
slots;
|
|
107
109
|
available;
|
|
@@ -535,4 +537,3 @@ class QuickJsChildPool {
|
|
|
535
537
|
export function quickJsExecutor(options = {}) {
|
|
536
538
|
return new QuickJsChildPool(options);
|
|
537
539
|
}
|
|
538
|
-
//# sourceMappingURL=quickjs.js.map
|
package/dist/favicon.d.ts
CHANGED
package/dist/favicon.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -192,7 +192,6 @@ export { CONNECTA_VERSION } from "./version.js";
|
|
|
192
192
|
export type { Registry } from "./registry.js";
|
|
193
193
|
export type { RemoteMcpOptions, RemoteMcpAuth, RemoteMcpRedirectPolicy, } from "./connectors/remote-mcp.js";
|
|
194
194
|
export type { ApiOptions, ApiTool } from "./connectors/api.js";
|
|
195
|
-
export type { Connector, ConnectorCallAdmissionInput, ConnectorCallAdmissionPolicy, ConnectorCallAdmissionRule, ConnectorRollingWindowBudget, ConnectaBranding, ConnectorCredentialAccess, ConnectorCredentialConfig, ConnectorCredentialFieldConfig, ConnectorCredentialValues, ConnectorContext, ConnectorUsageGuide, ConnectorStatus, CredentialTestResult, AdmittingExecutor, AdmissionSnapshot, ExecuteResult, Executor, ExecutorLease, ExecutorProvider, InboundAuth, UiAuthConfig, AuthResult, JsonSchema, KVStorage, Logger, ToolDef, ToolAnnotations, } from "./types.js";
|
|
196
|
-
export type { ActivityActor, ActivityCallSource, ActivityOutcome, ActivityPage, ActivityReadActor, ActivityReadEvent, ActivityReader, ActivityReadGate, ActivityReadPage, ActivitySink, ActivityStore, AgentFriction, ToolCallActivityEvent, } from "./activity.js";
|
|
195
|
+
export type { CatalogDriftCounts, CatalogDriftReport, Connector, ConnectorCallAdmissionInput, ConnectorCallAdmissionPolicy, ConnectorCallAdmissionRule, ConnectorRollingWindowBudget, ConnectaBranding, ConnectorCredentialAccess, ConnectorCredentialConfig, ConnectorCredentialFieldConfig, ConnectorCredentialValues, ConnectorContext, ConnectorUsageGuide, ConnectorStatus, CredentialTestResult, AdmittingExecutor, AdmissionSnapshot, ExecuteResult, Executor, ExecutorLease, ExecutorProvider, InboundAuth, UiAuthConfig, AuthResult, JsonSchema, KVStorage, Logger, ToolDef, ToolAnnotations, } from "./types.js";
|
|
196
|
+
export type { ActivityActor, ActivityCallSource, ActivityOutcome, ActivityPage, ActivityReadActor, ActivityReadEvent, ActivityReader, ActivityReadGate, ActivityReadPage, ActivitySink, ActivityStore, AgentFriction, CatalogDriftActivityEvent, ToolCallActivityEvent, } from "./activity.js";
|
|
197
197
|
export { InvalidActivityCursorError } from "./activity.js";
|
|
198
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createFetchHandler } from "./server.js";
|
|
|
5
5
|
import { droppedBrandingUrls, droppedUiAuthUrls } from "./ui.js";
|
|
6
6
|
import { memoryStorage } from "./storage/memory.js";
|
|
7
7
|
import { CONNECTA_VERSION } from "./version.js";
|
|
8
|
-
import { AdmissionController, isAdmittingExecutor, withExecutorAdmission, } from "./executor-admission.js";
|
|
8
|
+
import { AdmissionController, executorName, isAdmittingExecutor, withExecutorAdmission, } from "./executor-admission.js";
|
|
9
9
|
const REQUEST_ADMISSION_DEFAULTS = {
|
|
10
10
|
concurrency: 16,
|
|
11
11
|
maxQueueSize: 32,
|
|
@@ -225,10 +225,26 @@ export function createConnecta(config) {
|
|
|
225
225
|
throw new Error("accessTokens requires a Clerk auth provider: only an eligible Clerk " +
|
|
226
226
|
"operator may create, rename, or revoke deployment access tokens");
|
|
227
227
|
}
|
|
228
|
+
const serverInfo = {
|
|
229
|
+
...config.serverInfo,
|
|
230
|
+
name: config.serverInfo?.name ?? "connecta",
|
|
231
|
+
version: config.serverInfo?.version ?? CONNECTA_VERSION,
|
|
232
|
+
};
|
|
228
233
|
const registry = new Registry(config.connectors, {
|
|
229
234
|
storage,
|
|
230
235
|
logger,
|
|
231
236
|
...(credentialVault !== undefined ? { credentialVault } : {}),
|
|
237
|
+
...(config.activity?.store !== undefined
|
|
238
|
+
? {
|
|
239
|
+
catalogDriftActivity: {
|
|
240
|
+
sink: config.activity.store,
|
|
241
|
+
serverInfo,
|
|
242
|
+
...(config.activity.deploymentId !== undefined
|
|
243
|
+
? { deploymentId: config.activity.deploymentId }
|
|
244
|
+
: {}),
|
|
245
|
+
},
|
|
246
|
+
}
|
|
247
|
+
: {}),
|
|
232
248
|
...(config.discovery?.catalogTtlSeconds !== undefined
|
|
233
249
|
? { toolCacheTtlSeconds: config.discovery.catalogTtlSeconds }
|
|
234
250
|
: {}),
|
|
@@ -248,6 +264,9 @@ export function createConnecta(config) {
|
|
|
248
264
|
const configuredCodeAdmission = admissionController(config.admission?.code, CODE_ADMISSION_DEFAULTS);
|
|
249
265
|
let codeAdmission;
|
|
250
266
|
let executor = config.executor;
|
|
267
|
+
// Read the identity off the configured executor, before any wrapper hides
|
|
268
|
+
// it behind an anonymous object literal.
|
|
269
|
+
const configuredExecutorName = executorName(executor);
|
|
251
270
|
if (!isAdmittingExecutor(executor)) {
|
|
252
271
|
codeAdmission = configuredCodeAdmission;
|
|
253
272
|
executor = withExecutorAdmission(executor, codeAdmission);
|
|
@@ -261,11 +280,7 @@ export function createConnecta(config) {
|
|
|
261
280
|
registry,
|
|
262
281
|
auth: inboundAuth,
|
|
263
282
|
...(config.publicUrl !== undefined ? { publicUrl: config.publicUrl } : {}),
|
|
264
|
-
serverInfo
|
|
265
|
-
...config.serverInfo,
|
|
266
|
-
name: config.serverInfo?.name ?? "connecta",
|
|
267
|
-
version: config.serverInfo?.version ?? CONNECTA_VERSION,
|
|
268
|
-
},
|
|
283
|
+
serverInfo,
|
|
269
284
|
logger,
|
|
270
285
|
...(config.activity?.store !== undefined
|
|
271
286
|
? { activity: config.activity.store }
|
|
@@ -277,6 +292,9 @@ export function createConnecta(config) {
|
|
|
277
292
|
? { activityDeploymentId: config.activity.deploymentId }
|
|
278
293
|
: {}),
|
|
279
294
|
executor,
|
|
295
|
+
...(configuredExecutorName !== undefined
|
|
296
|
+
? { executorName: configuredExecutorName }
|
|
297
|
+
: {}),
|
|
280
298
|
requestAdmission,
|
|
281
299
|
...(config.calls?.defaultTimeoutMs !== undefined
|
|
282
300
|
? { defaultToolTimeoutMs: config.calls.defaultTimeoutMs }
|
|
@@ -328,4 +346,3 @@ export { bearerToken } from "./auth/bearer.js";
|
|
|
328
346
|
export { memoryStorage } from "./storage/memory.js";
|
|
329
347
|
export { CONNECTA_VERSION } from "./version.js";
|
|
330
348
|
export { InvalidActivityCursorError } from "./activity.js";
|
|
331
|
-
//# sourceMappingURL=index.js.map
|
package/dist/invocation.d.ts
CHANGED
package/dist/invocation.js
CHANGED
package/dist/json-schema.d.ts
CHANGED
package/dist/json-schema.js
CHANGED
package/dist/mcp-result.d.ts
CHANGED
package/dist/mcp-result.js
CHANGED
package/dist/meta-tools.d.ts
CHANGED
package/dist/meta-tools.js
CHANGED
package/dist/node.d.ts
CHANGED
package/dist/node.js
CHANGED