@zackbart/connecta 0.15.1 → 0.16.1
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 +40 -20
- package/CHANGELOG.md +653 -0
- package/README.md +25 -5
- 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 +41 -5
- package/dist/catalog.d.ts +11 -1
- package/dist/catalog.js +20 -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 +30 -3
- package/dist/errors.js +1 -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 +0 -1
- package/dist/providers/cloudflare.js +261 -299
- package/dist/providers/linear.d.ts +26 -5
- package/dist/providers/linear.js +32 -61
- package/dist/providers/mixpanel.d.ts +35 -4
- package/dist/providers/mixpanel.js +53 -79
- 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 -60
- 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 +92 -10
- package/documentation/code-first-exploration.md +2 -2
- package/documentation/code-mode.md +14 -14
- package/documentation/connector-guides.md +172 -5
- package/documentation/connectors.md +89 -0
- package/documentation/linear.md +20 -2
- package/documentation/mcp-ui-design.md +2 -2
- package/documentation/meta-tools.md +7 -1
- package/documentation/mixpanel.md +42 -9
- package/documentation/notion.md +23 -2
- package/documentation/operations.md +325 -5
- package/documentation/operator-ui.md +120 -5
- package/documentation/provider-audit.md +160 -0
- package/documentation/provider-conventions.md +667 -0
- package/documentation/request-admission.md +167 -5
- package/documentation/rich-output-design.md +1 -2
- package/documentation/stripe.md +15 -0
- package/documentation/upgrading.md +454 -0
- package/ethos.md +14 -9
- package/examples/worker/README.md +102 -12
- package/examples/worker/src/index.ts +46 -1
- package/examples/worker/wrangler.jsonc +14 -1
- package/package.json +10 -6
- package/templates/node/.dockerignore +10 -0
- package/templates/node/.env.example +40 -3
- package/templates/node/AGENTS.md +16 -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 -3999
- package/src/providers/linear.ts +0 -302
- package/src/providers/mixpanel.ts +0 -229
- package/src/providers/notion.ts +0 -1879
- package/src/providers/stripe.ts +0 -307
- 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
package/src/routes/ui.ts
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import { CONNECTA_FAVICON_ICO } from "../favicon.js";
|
|
2
|
-
import {
|
|
3
|
-
buildUiData,
|
|
4
|
-
CONNECTA_FAVICON_SVG,
|
|
5
|
-
credentialManagementCapability,
|
|
6
|
-
operatorPageForPath,
|
|
7
|
-
renderUiHtml,
|
|
8
|
-
} from "../ui.js";
|
|
9
|
-
import {
|
|
10
|
-
authorize,
|
|
11
|
-
privateJson,
|
|
12
|
-
type RouteContext,
|
|
13
|
-
} from "./shared.js";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Headers that make an operator-supplied favicon body inert on this origin.
|
|
17
|
-
* The SVG route is the sharp one: `image/svg+xml` is an *active* content type,
|
|
18
|
-
* so a `<script>` inside a branding SVG would run on the deployment origin the
|
|
19
|
-
* moment anyone navigated straight to `/favicon.svg` — strictly more powerful
|
|
20
|
-
* than the `favicon.href` vector the branding gates close, because the payload
|
|
21
|
-
* is same-origin. Neutralizing the response rather than inspecting the body
|
|
22
|
-
* keeps every valid static SVG (the built-in mark included) byte-identical:
|
|
23
|
-
*
|
|
24
|
-
* - `sandbox` (no tokens ⇒ every restriction) drops the document into an opaque
|
|
25
|
-
* origin with scripting off, so even a script that ran would have nothing to
|
|
26
|
-
* reach.
|
|
27
|
-
* - `default-src 'none'` denies script, network, and framing outright.
|
|
28
|
-
* - `style-src 'unsafe-inline'` is the single allowance: the default mark styles
|
|
29
|
-
* itself inline to follow the OS colour scheme, and CSS cannot script.
|
|
30
|
-
* - `nosniff` keeps the declared type authoritative in both directions — an SVG
|
|
31
|
-
* can never be re-read as HTML, and `.ico` bytes can never be re-read as SVG.
|
|
32
|
-
*
|
|
33
|
-
* `.ico` bodies are deliberately in scope: they are inert bytes rather than
|
|
34
|
-
* active content, so they are still served verbatim, but they carry the same
|
|
35
|
-
* headers so the invariant is "every favicon route is neutralized" rather than
|
|
36
|
-
* "whichever route got attention".
|
|
37
|
-
*/
|
|
38
|
-
const INERT_ICON_HEADERS = {
|
|
39
|
-
"Content-Security-Policy":
|
|
40
|
-
"default-src 'none'; style-src 'unsafe-inline'; sandbox",
|
|
41
|
-
"X-Content-Type-Options": "nosniff",
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/** Per-request base64 nonce for an operator shell's scripts (Node 20+ and Workers). */
|
|
45
|
-
function uiScriptNonce(): string {
|
|
46
|
-
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
47
|
-
let binary = "";
|
|
48
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
49
|
-
return btoa(binary);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function routeUi(
|
|
53
|
-
context: RouteContext,
|
|
54
|
-
): Promise<Response | null> {
|
|
55
|
-
const { request, url, path, baseUrl, opts, defer } = context;
|
|
56
|
-
if (request.method === "GET" && path === "/favicon.svg") {
|
|
57
|
-
return new Response(opts.branding?.favicon?.svg ?? CONNECTA_FAVICON_SVG, {
|
|
58
|
-
headers: {
|
|
59
|
-
"Content-Type": "image/svg+xml",
|
|
60
|
-
"Cache-Control": "public, max-age=86400",
|
|
61
|
-
...INERT_ICON_HEADERS,
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
if (request.method === "GET" && path === "/favicon.ico") {
|
|
66
|
-
return new Response(opts.branding?.favicon?.ico ?? CONNECTA_FAVICON_ICO, {
|
|
67
|
-
headers: {
|
|
68
|
-
"Content-Type": "image/x-icon",
|
|
69
|
-
"Cache-Control": "public, max-age=86400",
|
|
70
|
-
...INERT_ICON_HEADERS,
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
if (path === "/ui") {
|
|
75
|
-
if (request.method !== "GET") {
|
|
76
|
-
return privateJson({ error: "method not allowed" }, { status: 405 });
|
|
77
|
-
}
|
|
78
|
-
const target = new URL(`/${url.search}`, baseUrl);
|
|
79
|
-
return new Response(null, {
|
|
80
|
-
status: 308,
|
|
81
|
-
headers: { Location: target.toString() },
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const operatorPage = operatorPageForPath(path);
|
|
86
|
-
if (operatorPage) {
|
|
87
|
-
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
88
|
-
return privateJson({ error: "method not allowed" }, { status: 405 });
|
|
89
|
-
}
|
|
90
|
-
// Open shell — carries no operator data; everything comes from the
|
|
91
|
-
// authenticated /ui/* APIs after the browser establishes a session.
|
|
92
|
-
const uiAuth = opts.auth.find((provider) => provider.uiAuth)?.uiAuth;
|
|
93
|
-
const mcpUrl = new URL("/mcp", baseUrl).toString();
|
|
94
|
-
// Nonce the page's inline script (and the Clerk loader). 'strict-dynamic'
|
|
95
|
-
// lets scripts the nonced Clerk loader injects at runtime execute; the
|
|
96
|
-
// https:/'unsafe-inline' fallbacks are ignored by CSP3 browsers that
|
|
97
|
-
// honour the nonce and only cover legacy ones. No default-src, so Clerk's
|
|
98
|
-
// style/font/network needs and the page's inline <style> stay unrestricted
|
|
99
|
-
// — only script execution, the XSS sink, is gated.
|
|
100
|
-
const nonce = uiScriptNonce();
|
|
101
|
-
return new Response(
|
|
102
|
-
request.method === "HEAD"
|
|
103
|
-
? null
|
|
104
|
-
: renderUiHtml(uiAuth, mcpUrl, opts.branding, nonce, operatorPage),
|
|
105
|
-
{
|
|
106
|
-
status: 200,
|
|
107
|
-
headers: {
|
|
108
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
109
|
-
"Content-Security-Policy":
|
|
110
|
-
`script-src 'nonce-${nonce}' 'strict-dynamic' https: 'unsafe-inline'; ` +
|
|
111
|
-
"object-src 'none'; base-uri 'none'; frame-ancestors 'none'",
|
|
112
|
-
"X-Content-Type-Options": "nosniff",
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
if (path !== "/ui/data") return null;
|
|
118
|
-
|
|
119
|
-
const authz = await authorize(request, baseUrl, opts.auth);
|
|
120
|
-
if (!authz.ok) return authz.response;
|
|
121
|
-
const eligibleClerkOperator = authz.uiAdminEligible === true;
|
|
122
|
-
const credentialManagement = credentialManagementCapability({
|
|
123
|
-
eligibleClerkOperator,
|
|
124
|
-
hasCredentialSlots: opts.registry
|
|
125
|
-
.listConnectors()
|
|
126
|
-
.some((connector) => Boolean(connector.credential)),
|
|
127
|
-
hasCredentialVault: Boolean(opts.credentialVault),
|
|
128
|
-
});
|
|
129
|
-
// As with connector credentials, a Bearer-authenticated observer learns
|
|
130
|
-
// only that Clerk is required—not whether this deployment has opted into
|
|
131
|
-
// token issuance. Configuration topology is operator data.
|
|
132
|
-
const accessTokenManagement = !eligibleClerkOperator
|
|
133
|
-
? "requires_clerk" as const
|
|
134
|
-
: opts.accessTokens
|
|
135
|
-
? "available" as const
|
|
136
|
-
: "not_configured" as const;
|
|
137
|
-
const data = await buildUiData(
|
|
138
|
-
opts.registry,
|
|
139
|
-
baseUrl,
|
|
140
|
-
opts.serverInfo,
|
|
141
|
-
// The static headless bearer may read connector health, but only a
|
|
142
|
-
// Clerk-authenticated operator receives credential metadata.
|
|
143
|
-
eligibleClerkOperator ? opts.credentialVault : undefined,
|
|
144
|
-
Boolean(opts.activity?.list),
|
|
145
|
-
credentialManagement,
|
|
146
|
-
defer,
|
|
147
|
-
eligibleClerkOperator,
|
|
148
|
-
opts.discoveryConcurrency,
|
|
149
|
-
accessTokenManagement,
|
|
150
|
-
);
|
|
151
|
-
return privateJson(data);
|
|
152
|
-
}
|
package/src/server.ts
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import { isAdmittingExecutor } from "./executor-admission.js";
|
|
2
|
-
import { routeAccessTokens } from "./routes/access-tokens.js";
|
|
3
|
-
import { routeActivity } from "./routes/activity.js";
|
|
4
|
-
import { routeCredentials } from "./routes/credentials.js";
|
|
5
|
-
import { createMcpRoute, MCP_CORS_HEADERS } from "./routes/mcp.js";
|
|
6
|
-
import {
|
|
7
|
-
routeOAuthCallback,
|
|
8
|
-
routeOAuthManagement,
|
|
9
|
-
} from "./routes/oauth.js";
|
|
10
|
-
import {
|
|
11
|
-
withSecurityHeaders,
|
|
12
|
-
type RouteContext,
|
|
13
|
-
type RuntimeExecutionContext,
|
|
14
|
-
type ServerOptions,
|
|
15
|
-
} from "./routes/shared.js";
|
|
16
|
-
import { routeUi } from "./routes/ui.js";
|
|
17
|
-
|
|
18
|
-
export type { ServerOptions } from "./routes/shared.js";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Build the Web-standard fetch handler.
|
|
22
|
-
*
|
|
23
|
-
* Route ordering is the contract: private mutation routes precede wildcard
|
|
24
|
-
* OPTIONS, every built-in precedes connector-owned routes, and the security
|
|
25
|
-
* wrapper is applied to every response.
|
|
26
|
-
*/
|
|
27
|
-
export function createFetchHandler(
|
|
28
|
-
opts: ServerOptions,
|
|
29
|
-
): (
|
|
30
|
-
request: Request,
|
|
31
|
-
runtimeContext?: RuntimeExecutionContext,
|
|
32
|
-
) => Promise<Response> {
|
|
33
|
-
const { auth, publicUrl, registry } = opts;
|
|
34
|
-
const routeMcp = createMcpRoute(opts);
|
|
35
|
-
|
|
36
|
-
return async function fetch(
|
|
37
|
-
request: Request,
|
|
38
|
-
runtimeContext?: RuntimeExecutionContext,
|
|
39
|
-
): Promise<Response> {
|
|
40
|
-
const url = new URL(request.url);
|
|
41
|
-
const baseUrl = publicUrl ?? url.origin;
|
|
42
|
-
const path = url.pathname;
|
|
43
|
-
const defer = runtimeContext
|
|
44
|
-
? runtimeContext.waitUntil.bind(runtimeContext)
|
|
45
|
-
: undefined;
|
|
46
|
-
|
|
47
|
-
// Container and orchestrator probes reach /health over plain HTTP on
|
|
48
|
-
// loopback, where no proxy has set X-Forwarded-Proto. Redirecting them to
|
|
49
|
-
// the public origin would make an internal liveness check depend on
|
|
50
|
-
// external DNS, TLS, and the tunnel in front of connecta — so /health is
|
|
51
|
-
// exempt. It is unauthenticated, returns no user data, and sets no
|
|
52
|
-
// cookies, so forcing HTTPS on it protects nothing.
|
|
53
|
-
if (
|
|
54
|
-
publicUrl &&
|
|
55
|
-
path !== "/health" &&
|
|
56
|
-
new URL(publicUrl).protocol === "https:" &&
|
|
57
|
-
url.protocol === "http:"
|
|
58
|
-
) {
|
|
59
|
-
// Assign the path and query onto the configured URL instead of resolving
|
|
60
|
-
// attacker-controlled text against it. A pathname beginning with `//`
|
|
61
|
-
// (including a backslash form normalized by URL parsing) is an authority
|
|
62
|
-
// when passed to `new URL(value, base)` and would otherwise replace the
|
|
63
|
-
// deployment host. `/ui` is canonicalized while upgrading it so an old
|
|
64
|
-
// bookmark reaches the new Connections entry point in one permanent
|
|
65
|
-
// redirect.
|
|
66
|
-
const target = new URL(publicUrl);
|
|
67
|
-
target.pathname = path === "/ui" ? "/" : url.pathname;
|
|
68
|
-
target.search = url.search;
|
|
69
|
-
target.hash = "";
|
|
70
|
-
return withSecurityHeaders(
|
|
71
|
-
new Response(null, {
|
|
72
|
-
status: 308,
|
|
73
|
-
headers: { Location: target.toString() },
|
|
74
|
-
}),
|
|
75
|
-
url,
|
|
76
|
-
path,
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const context: RouteContext = {
|
|
81
|
-
request,
|
|
82
|
-
url,
|
|
83
|
-
path,
|
|
84
|
-
baseUrl,
|
|
85
|
-
opts,
|
|
86
|
-
defer,
|
|
87
|
-
runtimeContext,
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const route = async (): Promise<Response> => {
|
|
91
|
-
// Private mutations own OPTIONS so they never inherit wildcard CORS.
|
|
92
|
-
const accessTokens = await routeAccessTokens(context);
|
|
93
|
-
if (accessTokens) return accessTokens;
|
|
94
|
-
|
|
95
|
-
const credentials = await routeCredentials(context);
|
|
96
|
-
if (credentials) return credentials;
|
|
97
|
-
|
|
98
|
-
const oauthManagement = await routeOAuthManagement(context);
|
|
99
|
-
if (oauthManagement) return oauthManagement;
|
|
100
|
-
|
|
101
|
-
if (request.method === "OPTIONS") {
|
|
102
|
-
for (const provider of auth) {
|
|
103
|
-
if (provider.handleMetadata) {
|
|
104
|
-
const response = await provider.handleMetadata(request, baseUrl);
|
|
105
|
-
if (response) return response;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return new Response(null, {
|
|
109
|
-
status: 204,
|
|
110
|
-
headers: MCP_CORS_HEADERS,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (path.startsWith("/.well-known/")) {
|
|
115
|
-
for (const provider of auth) {
|
|
116
|
-
if (provider.handleMetadata) {
|
|
117
|
-
const response = await provider.handleMetadata(request, baseUrl);
|
|
118
|
-
if (response) return response;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return new Response("Not Found", { status: 404 });
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (path === "/health") {
|
|
125
|
-
// The executor is required, so code admission always has a shape to
|
|
126
|
-
// report: either the executor's own pool or the fallback controller
|
|
127
|
-
// wrapped around it at construction.
|
|
128
|
-
const codeAdmission = isAdmittingExecutor(opts.executor)
|
|
129
|
-
? opts.executor.admissionSnapshot?.()
|
|
130
|
-
: undefined;
|
|
131
|
-
return Response.json({
|
|
132
|
-
status: "ok",
|
|
133
|
-
connectors: registry.listConnectors().length,
|
|
134
|
-
server: opts.serverInfo,
|
|
135
|
-
admission: {
|
|
136
|
-
policy: "global-fifo",
|
|
137
|
-
requests: opts.requestAdmission.snapshot(),
|
|
138
|
-
code: codeAdmission ?? { managedByExecutor: true },
|
|
139
|
-
downstreamCalls: {
|
|
140
|
-
policy: "connector-partitioned-per-runtime",
|
|
141
|
-
connectors: registry.callAdmissionSnapshot(),
|
|
142
|
-
},
|
|
143
|
-
reservedRoutes: [
|
|
144
|
-
"/health",
|
|
145
|
-
"/",
|
|
146
|
-
"/credentials",
|
|
147
|
-
"/tokens",
|
|
148
|
-
"/activity",
|
|
149
|
-
"/ui",
|
|
150
|
-
"/ui/*",
|
|
151
|
-
],
|
|
152
|
-
},
|
|
153
|
-
...(opts.deploymentInfo ? { deployment: opts.deploymentInfo } : {}),
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const oauthCallback = await routeOAuthCallback(context);
|
|
158
|
-
if (oauthCallback) return oauthCallback;
|
|
159
|
-
|
|
160
|
-
const ui = await routeUi(context);
|
|
161
|
-
if (ui) return ui;
|
|
162
|
-
|
|
163
|
-
const activity = await routeActivity(context);
|
|
164
|
-
if (activity) return activity;
|
|
165
|
-
|
|
166
|
-
const mcp = await routeMcp(context);
|
|
167
|
-
if (mcp) return mcp;
|
|
168
|
-
|
|
169
|
-
// Connector-owned public routes, dispatched last: a connector can add a
|
|
170
|
-
// route but never shadow one of connecta's own. A throw here is the
|
|
171
|
-
// connector's bug, not a missing route, so it surfaces as 500 rather
|
|
172
|
-
// than falling through to 404.
|
|
173
|
-
for (const connector of registry.listConnectors()) {
|
|
174
|
-
if (!connector.handleRequest) continue;
|
|
175
|
-
try {
|
|
176
|
-
const response = await connector.handleRequest(
|
|
177
|
-
request,
|
|
178
|
-
registry.contextFor(connector.id, baseUrl),
|
|
179
|
-
);
|
|
180
|
-
if (response) return response;
|
|
181
|
-
} catch (error) {
|
|
182
|
-
opts.logger.error(
|
|
183
|
-
`[connecta] connector "${connector.id}" handleRequest failed`,
|
|
184
|
-
error,
|
|
185
|
-
);
|
|
186
|
-
return new Response("Internal Server Error", { status: 500 });
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return new Response("Not Found", { status: 404 });
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
return withSecurityHeaders(await route(), url, path);
|
|
194
|
-
};
|
|
195
|
-
}
|
package/src/skills.ts
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import type { Connector } from "./types.js";
|
|
2
|
-
|
|
3
|
-
export const CONNECTA_INSTRUCTIONS =
|
|
4
|
-
'Connecta exposes seven meta-tools. For one read at an unknown address, search_tools with 2–4 distinctive action/object terms and includeSchemas="compact", then one call_tool — a lone cold call is cheaper direct than a program. For read-only reduction, multiple or dependent calls, loops, joins, or branches, do not call top-level search_tools: make one execute_code call whose program searches, selects, calls, and reduces; never return discovery for another call. connecta.ui(html) is a guest function inside execute_code, never a connector address or search_tools result; pass one HTML string for display-only, or bind named read-only refresh/drill-down calls in its optional reads argument, and return the same initial summary data the HTML renders. Unannotated, write-capable, or destructive tools stay top level: search_tools, then call_destructive_tool; authorize_connector follows auth_required; get_result follows truncation. If this routing is unfamiliar, fetch skills({ name: "usage" }).';
|
|
5
|
-
|
|
6
|
-
const USAGE_SKILL_BASE = `# Connecta usage
|
|
7
|
-
|
|
8
|
-
## The surface
|
|
9
|
-
|
|
10
|
-
Seven tools: \`execute_code\`, \`search_tools\`, \`call_tool\`, \`call_destructive_tool\`, \`authorize_connector\`, \`get_result\`, \`skills\`. Broad discovery and multi-call work live in a program, not in top-level tools.
|
|
11
|
-
|
|
12
|
-
## Choose the smallest execution tool
|
|
13
|
-
|
|
14
|
-
Use exact addresses from discovery; never invent one. Search 2–4 distinctive action/object terms, not the whole request.
|
|
15
|
-
|
|
16
|
-
- One read at an unknown address: \`search_tools({ query, includeSchemas: "compact" })\`, then \`call_tool\` once — one cold call is cheaper direct than a program.
|
|
17
|
-
- Anything wider — two or more calls, dependent steps, loops, joins, branching, a whole-catalog browse, or a result to reduce: one \`execute_code\` run.
|
|
18
|
-
- Any unannotated, write-capable, or destructive call: \`call_destructive_tool\`, one at a time, after reviewing its schema and consequences.
|
|
19
|
-
- Truncated result: retry with \`fields\`, else page it with \`get_result\`.
|
|
20
|
-
- \`auth_required\`: \`authorize_connector\`, hand its recovery text to the operator, retry the call.
|
|
21
|
-
|
|
22
|
-
## Inside a program
|
|
23
|
-
|
|
24
|
-
One async arrow function. The only capabilities are one global per connector (\`<connectorId>.<toolName>(args)\`), the \`connecta\` functions, and \`console.log\`.
|
|
25
|
-
|
|
26
|
-
- \`connecta.search({})\` loads all catalogs; pass \`connector: "<id>"\` when obvious to load one. \`safety: "readOnly"\` keeps executable calls. Neither grants authority. Matches carry \`address\` and annotations.
|
|
27
|
-
- Exact schemas: \`connecta.describe({ address: "connector.tool" })\` for one, \`{ addresses: [...] }\` for many; \`format: "json"\` only for exact constraints.
|
|
28
|
-
- Two to ten independent calls: \`connecta.batch([...])\`. Each outcome is \`{ address, ok: true, data }\` or \`{ address, ok: false, error, errorDetails: { code, retryable } }\` — how a program tells a policy refusal from a transient failure.
|
|
29
|
-
- Search inside the run, not before it; return only the reduction the answer needs, never raw payloads.
|
|
30
|
-
- Only tools annotated \`readOnlyHint: true\` are reachable; the gate, credentials, and admission are enforced below the sandbox — nothing a program does widens its reach.
|
|
31
|
-
|
|
32
|
-
## Rendering a view
|
|
33
|
-
|
|
34
|
-
\`connecta.ui(html)\` renders a display-only view on success for the client, never for the model. Fetch first, check the shape in code. On a surprise — empty array, missing key — return a trimmed first record instead of rendering: the wrong view becomes the sample you needed. Otherwise render from the variables you return; the model reads the return value, not the view.
|
|
35
|
-
|
|
36
|
-
`;
|
|
37
|
-
|
|
38
|
-
/** Deployment-scoped guide routing appended to the shared usage guide. */
|
|
39
|
-
const CONNECTOR_GUIDES_SECTION = `
|
|
40
|
-
## Per-connector guides
|
|
41
|
-
|
|
42
|
-
When a connector here ships a deployment-scoped usage guide, \`skills({})\` and discovery return the exact \`guide\` name plus a bounded \`guideSummary\` saying what it covers. Fetch only a listed or carried name with \`skills({ name: <guide> })\`; never infer one from a connector id. \`guideRequired: true\` is a hard stop: fetch before calling. \`guideRequiredReasons\` says why — \`connector_required\` and \`approval_required\` stand however you expand the schema; \`schema_truncated\` clears once describe returns the exact one. Otherwise fetch when the summary names a connector-specific sequence, unit, pagination rule, alias, or generic API convention relevant to the task. A read-only call whose compact schema is complete and unambiguous may proceed without fetching an otherwise irrelevant guide. Connector guides do not replace the shared Connecta usage guide and never apply to another deployment implicitly.
|
|
43
|
-
`;
|
|
44
|
-
|
|
45
|
-
/** Shared Connecta routing guidance, byte-identical across deployments. */
|
|
46
|
-
export const USAGE_SKILL = USAGE_SKILL_BASE + CONNECTOR_GUIDES_SECTION;
|
|
47
|
-
|
|
48
|
-
/** The always-loaded MCP `instructions` string. */
|
|
49
|
-
export function instructionsFor(): string {
|
|
50
|
-
return CONNECTA_INSTRUCTIONS;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** True when at least one of `connectors` carries a usage guide. */
|
|
54
|
-
export function hasConnectorGuides(connectors: readonly Connector[]): boolean {
|
|
55
|
-
return connectors.some(
|
|
56
|
-
(connector) => connectorGuide(connector) !== undefined,
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* The built-in usage guide is byte-identical across deployments, so an agent
|
|
62
|
-
* that has read it once in a task never needs an equivalent deployment-local
|
|
63
|
-
* copy. Guide-free deployments still pay no fixed tool-description cost: the
|
|
64
|
-
* conditional notes in meta-tools.ts remain absent.
|
|
65
|
-
*/
|
|
66
|
-
function usageSkill(_connectors: readonly Connector[]): string {
|
|
67
|
-
return USAGE_SKILL;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const AVAILABLE_SKILLS = [
|
|
71
|
-
{
|
|
72
|
-
name: "usage",
|
|
73
|
-
description:
|
|
74
|
-
"How to route work between one execute_code program and Connecta's explicit call, authorization, and result tools.",
|
|
75
|
-
content: usageSkill,
|
|
76
|
-
},
|
|
77
|
-
] as const;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Namespace for operator-authored per-connector guides. Built-in skill names
|
|
81
|
-
* are bare identifiers and never contain ":", so `connector:<id>` cannot
|
|
82
|
-
* collide with one — not even when a connector's id is literally "usage".
|
|
83
|
-
* The prefixed form is the ONLY way to reach a connector guide: a bare
|
|
84
|
-
* connector id is never resolved, so nothing shadows anything silently.
|
|
85
|
-
*/
|
|
86
|
-
const CONNECTOR_SKILL_PREFIX = "connector:";
|
|
87
|
-
|
|
88
|
-
/** The skill name that fetches `connector`'s guide. */
|
|
89
|
-
export function connectorSkillName(connectorId: string): string {
|
|
90
|
-
return `${CONNECTOR_SKILL_PREFIX}${connectorId}`;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** The connector's guide, or undefined when it declares none (or a blank one). */
|
|
94
|
-
export function connectorGuide(connector: Connector): string | undefined {
|
|
95
|
-
const guide = connector.usageGuide;
|
|
96
|
-
const content = typeof guide === "string" ? guide : guide?.content;
|
|
97
|
-
return content && content.trim() !== "" ? content : undefined;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const SUMMARY_LENGTH = 120;
|
|
101
|
-
|
|
102
|
-
/** A `---`/`***`/`___` rule, which also opens and closes YAML frontmatter. */
|
|
103
|
-
const RULE_RE = /^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/;
|
|
104
|
-
|
|
105
|
-
/** A fenced code block's delimiter. */
|
|
106
|
-
const FENCE_RE = /^\s*(?:```|~~~)/;
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Markup that carries no summary text of its own: horizontal rules, HTML
|
|
110
|
-
* comments, and table rows. Skipped so a guide that opens with one is
|
|
111
|
-
* summarized by its first real line instead of by punctuation.
|
|
112
|
-
*/
|
|
113
|
-
const NOT_SUMMARY_RE = /^\s*(?:<!--|\|)|^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/;
|
|
114
|
-
|
|
115
|
-
/** Drop a leading YAML frontmatter block — metadata, not summary text. */
|
|
116
|
-
function withoutFrontmatter(lines: string[]): string[] {
|
|
117
|
-
let start = 0;
|
|
118
|
-
while (start < lines.length && (lines[start] ?? "").trim() === "") start++;
|
|
119
|
-
const openingRule = lines[start];
|
|
120
|
-
if (openingRule === undefined || !RULE_RE.test(openingRule)) return lines;
|
|
121
|
-
const close = lines.findIndex((line, i) => i > start && RULE_RE.test(line));
|
|
122
|
-
return close === -1 ? lines : lines.slice(close + 1);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* One line describing a guide, for the cheap list view: the guide's first
|
|
127
|
-
* meaningful line (heading marks and list bullets stripped), falling back to
|
|
128
|
-
* the connector's own description when the guide opens with nothing but
|
|
129
|
-
* markup.
|
|
130
|
-
*/
|
|
131
|
-
function boundedSummary(summary: string): string | undefined {
|
|
132
|
-
const line = summary.replace(/\s+/g, " ").trim();
|
|
133
|
-
if (line === "") return undefined;
|
|
134
|
-
return line.length <= SUMMARY_LENGTH
|
|
135
|
-
? line
|
|
136
|
-
: `${line.slice(0, SUMMARY_LENGTH - 1).trimEnd()}…`;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function summarizeGuide(connector: Connector, guide: string): string {
|
|
140
|
-
let inFence = false;
|
|
141
|
-
let headingFallback: string | undefined;
|
|
142
|
-
for (const raw of withoutFrontmatter(guide.split("\n"))) {
|
|
143
|
-
if (FENCE_RE.test(raw)) {
|
|
144
|
-
inFence = !inFence;
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
if (inFence) continue;
|
|
148
|
-
if (raw.trim() === "" || NOT_SUMMARY_RE.test(raw)) continue;
|
|
149
|
-
const heading = /^\s*#{1,6}/.test(raw);
|
|
150
|
-
const line = raw
|
|
151
|
-
// `\s*` (not `\s+`) so a bare `#` strips to nothing and is skipped, and
|
|
152
|
-
// an unspaced `#Heading` is still read as a heading.
|
|
153
|
-
.replace(/^\s*#{1,6}\s*/, "")
|
|
154
|
-
.replace(/^\s*[-*+]\s+/, "")
|
|
155
|
-
.replace(/\s+/g, " ")
|
|
156
|
-
.trim();
|
|
157
|
-
if (line === "") continue;
|
|
158
|
-
if (heading) {
|
|
159
|
-
headingFallback ??= boundedSummary(line);
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
return boundedSummary(line) ?? line;
|
|
163
|
-
}
|
|
164
|
-
if (headingFallback) return headingFallback;
|
|
165
|
-
const fallback = connector.description ?? `Usage guide for "${connector.id}".`;
|
|
166
|
-
return boundedSummary(fallback) ?? `Usage guide for "${connector.id}".`;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** Bounded, decision-useful discovery summary for a connector guide. */
|
|
170
|
-
export function connectorGuideSummary(
|
|
171
|
-
connector: Connector,
|
|
172
|
-
): string | undefined {
|
|
173
|
-
const guide = connectorGuide(connector);
|
|
174
|
-
if (!guide) return undefined;
|
|
175
|
-
const configured =
|
|
176
|
-
typeof connector.usageGuide === "object"
|
|
177
|
-
? boundedSummary(connector.usageGuide.summary ?? "")
|
|
178
|
-
: undefined;
|
|
179
|
-
return configured ?? summarizeGuide(connector, guide);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** Whether correct use always depends on conventions outside the tool schema. */
|
|
183
|
-
export function connectorGuideRequired(connector: Connector): boolean {
|
|
184
|
-
return (
|
|
185
|
-
connectorGuide(connector) !== undefined &&
|
|
186
|
-
typeof connector.usageGuide === "object" &&
|
|
187
|
-
connector.usageGuide.required === true
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export interface SkillListing {
|
|
192
|
-
name: string;
|
|
193
|
-
description: string;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Every fetchable skill: the built-in guides plus one entry per connector that
|
|
198
|
-
* carries a usage guide. Derived from the connector list passed in — the single
|
|
199
|
-
* place guide visibility is decided.
|
|
200
|
-
*/
|
|
201
|
-
export function listSkills(connectors: readonly Connector[]): SkillListing[] {
|
|
202
|
-
const listing: SkillListing[] = AVAILABLE_SKILLS.map((skill) => ({
|
|
203
|
-
name: skill.name,
|
|
204
|
-
description: skill.description,
|
|
205
|
-
}));
|
|
206
|
-
for (const connector of connectors) {
|
|
207
|
-
// Undefined here means "no guide" and nothing else: a connector that has
|
|
208
|
-
// one always summarizes to a non-empty line, configured or derived.
|
|
209
|
-
const summary = connectorGuideSummary(connector);
|
|
210
|
-
if (!summary) continue;
|
|
211
|
-
listing.push({
|
|
212
|
-
name: connectorSkillName(connector.id),
|
|
213
|
-
description: summary,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
return listing;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export type SkillLookup =
|
|
220
|
-
{ found: true; content: string } | { found: false; message: string };
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Resolve one skill name. Built-in names match exactly; connector guides are
|
|
224
|
-
* reachable only through the `connector:` prefix. Every miss — unknown name,
|
|
225
|
-
* unknown connector, connector without a guide — is an explicit error, never a
|
|
226
|
-
* silent fallback to the generic guide.
|
|
227
|
-
*/
|
|
228
|
-
export function resolveSkill(
|
|
229
|
-
name: string,
|
|
230
|
-
connectors: readonly Connector[],
|
|
231
|
-
): SkillLookup {
|
|
232
|
-
const builtIn = AVAILABLE_SKILLS.find((skill) => skill.name === name);
|
|
233
|
-
if (builtIn) {
|
|
234
|
-
return { found: true, content: builtIn.content(connectors) };
|
|
235
|
-
}
|
|
236
|
-
const available = () =>
|
|
237
|
-
listSkills(connectors)
|
|
238
|
-
.map((skill) => skill.name)
|
|
239
|
-
.join(", ");
|
|
240
|
-
if (name.startsWith(CONNECTOR_SKILL_PREFIX)) {
|
|
241
|
-
const id = name.slice(CONNECTOR_SKILL_PREFIX.length);
|
|
242
|
-
const connector = connectors.find((c) => c.id === id);
|
|
243
|
-
if (!connector) {
|
|
244
|
-
return {
|
|
245
|
-
found: false,
|
|
246
|
-
message: `Unknown connector "${id}". Available skills: ${available()}.`,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
const guide = connectorGuide(connector);
|
|
250
|
-
if (!guide) {
|
|
251
|
-
return {
|
|
252
|
-
found: false,
|
|
253
|
-
message: `Connector "${id}" has no usage guide. Available skills: ${available()}.`,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
return { found: true, content: guide };
|
|
257
|
-
}
|
|
258
|
-
const bare = connectors.find((c) => c.id === name);
|
|
259
|
-
if (bare) {
|
|
260
|
-
return {
|
|
261
|
-
found: false,
|
|
262
|
-
message: connectorGuide(bare)
|
|
263
|
-
? `Unknown skill "${name}". Connector guides are fetched as "${connectorSkillName(name)}". Available skills: ${available()}.`
|
|
264
|
-
: `Connector "${name}" has no usage guide. Available skills: ${available()}.`,
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
return {
|
|
268
|
-
found: false,
|
|
269
|
-
message: `Unknown skill "${name}". Available skills: ${available()}.`,
|
|
270
|
-
};
|
|
271
|
-
}
|