@zackbart/connecta 0.28.1 → 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 +788 -5
- package/README.md +62 -42
- 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 +1584 -1580
- 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 +93 -71
- 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 +59 -7
- package/dist/connectors/api-connector.js +106 -19
- package/dist/connectors/api.js +4 -1
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +84 -35
- 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 +1774 -320
- 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 +24 -256
- 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-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.d.ts → basecamp/index.d.ts} +6 -28
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
- package/dist/providers/{breeze.js → breeze/index.js} +184 -140
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
- package/dist/providers/{ccb.js → ccb/index.js} +271 -116
- 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.d.ts → overflow/index.d.ts} +2 -2
- package/dist/providers/{overflow.js → overflow/index.js} +74 -111
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
- package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
- 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.d.ts → tithely/index.d.ts} +2 -2
- package/dist/providers/{tithely.js → tithely/index.js} +60 -91
- 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 +110 -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 +664 -231
- package/documentation/auth.md +936 -546
- 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 +120 -54
- 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/basecamp.js +0 -445
- 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
package/dist/catalog-service.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { carryCatalogFreshness, catalogIsFresh } from "./catalog-freshness.js";
|
|
2
|
+
import { hasControlCharacters } from "./tool-name.js";
|
|
1
3
|
import { Effect, Result } from "effect";
|
|
2
4
|
import { compactDiscoverySchema, compactDescriptionSchema, lexicalCorpusStatistics, lexicalQueryTerms, lexicalSearchQuery, matchesLexicalTerm, rankTools, schemaObjectKeys, summarizeDiscoveryDescription, summarizeDescription, typescriptSignature, } from "./catalog.js";
|
|
3
5
|
import { resolveDiscoveryConcurrency } from "./concurrency.js";
|
|
4
|
-
import { boundedEchoText, classifyCallError,
|
|
5
|
-
import { connectorGuide, connectorGuideRequired, connectorGuideSummary, connectorSkillName
|
|
6
|
+
import { boundedEchoText, classifyCallError, ConnectorCallError, framingError } from "./errors.js";
|
|
7
|
+
import { connectorGuide, connectorGuideRequired, connectorGuideSummary, connectorSkillName } from "./skills.js";
|
|
6
8
|
import { runEdge, withDeadlineEffect } from "./runtime/run.js";
|
|
7
9
|
import { SharedRead } from "./runtime/shared-read.js";
|
|
8
|
-
import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs
|
|
9
|
-
import { isApprovalExempt, isExplicitlyReadOnly, NO_EXEMPTIONS, } from "./tool-safety.js";
|
|
10
|
+
import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs } from "./timeout.js";
|
|
10
11
|
export const DEFAULT_SEARCH_LIMIT = 8;
|
|
11
12
|
export const MAX_SEARCH_LIMIT = 100;
|
|
12
13
|
export const MAX_DESCRIBE_ADDRESSES = 100;
|
|
@@ -20,6 +21,49 @@ const MAX_QUERY_TERM_LENGTH = 64;
|
|
|
20
21
|
*/
|
|
21
22
|
const MAX_IDENTITY_CONNECTORS = 3;
|
|
22
23
|
const MAX_DESCRIBE_SUGGESTIONS = 3;
|
|
24
|
+
// Recognized service names are query vocabulary, never a deployment inventory.
|
|
25
|
+
// Unknown/custom services use the explicit connector scope. Keep this independent
|
|
26
|
+
// of provider implementations so discovery cannot pull them into root imports.
|
|
27
|
+
const SERVICE_NAMES = [
|
|
28
|
+
"GitHub",
|
|
29
|
+
"GitLab",
|
|
30
|
+
"Supabase",
|
|
31
|
+
"PostHog",
|
|
32
|
+
"Linear",
|
|
33
|
+
"Mixpanel",
|
|
34
|
+
"RevenueCat",
|
|
35
|
+
"Stripe",
|
|
36
|
+
"Notion",
|
|
37
|
+
"Slack",
|
|
38
|
+
"Vercel",
|
|
39
|
+
"Cloudflare",
|
|
40
|
+
"Basecamp",
|
|
41
|
+
"Breeze",
|
|
42
|
+
"Tithely",
|
|
43
|
+
"Planning Center",
|
|
44
|
+
"Google Drive",
|
|
45
|
+
"Google Docs",
|
|
46
|
+
"Google Sheets",
|
|
47
|
+
"Google Slides",
|
|
48
|
+
"Gmail",
|
|
49
|
+
];
|
|
50
|
+
function normalizedPhrase(text) {
|
|
51
|
+
return lexicalQueryTerms(text.toLowerCase()).join(" ");
|
|
52
|
+
}
|
|
53
|
+
function containsPhrase(query, phrase) {
|
|
54
|
+
const normalized = normalizedPhrase(phrase);
|
|
55
|
+
return normalized.length > 0 && ` ${normalizedPhrase(query)} `.includes(` ${normalized} `);
|
|
56
|
+
}
|
|
57
|
+
function connectorNames(connector) {
|
|
58
|
+
return [connector.id, ...(connector.title ? [connector.title] : [])];
|
|
59
|
+
}
|
|
60
|
+
function connectorMatchRank(query, connector) {
|
|
61
|
+
if (connectorNames(connector).some((name) => containsPhrase(query, name)))
|
|
62
|
+
return 2;
|
|
63
|
+
return SERVICE_NAMES.some((service) => containsPhrase(query, service) && connectorNames(connector).some((name) => containsPhrase(name, service)))
|
|
64
|
+
? 1
|
|
65
|
+
: 0;
|
|
66
|
+
}
|
|
23
67
|
const encoder = new TextEncoder();
|
|
24
68
|
/** Clip one echoed query term without splitting a non-BMP code point. */
|
|
25
69
|
function boundedQueryTerm(term) {
|
|
@@ -47,10 +91,7 @@ export class DiscoveryPolicyError extends Error {
|
|
|
47
91
|
function discoverySearchLimit(value) {
|
|
48
92
|
if (value === undefined)
|
|
49
93
|
return DEFAULT_SEARCH_LIMIT;
|
|
50
|
-
if (typeof value !== "number" ||
|
|
51
|
-
!Number.isInteger(value) ||
|
|
52
|
-
value < 1 ||
|
|
53
|
-
value > MAX_SEARCH_LIMIT) {
|
|
94
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > MAX_SEARCH_LIMIT) {
|
|
54
95
|
throw new DiscoveryPolicyError("invalid_args", `limit must be a whole number from 1 through ${MAX_SEARCH_LIMIT}. Page through larger catalogs with offset.`);
|
|
55
96
|
}
|
|
56
97
|
return value;
|
|
@@ -60,11 +101,7 @@ function discoveryAddresses(args) {
|
|
|
60
101
|
if (args.address !== undefined && args.addresses !== undefined) {
|
|
61
102
|
throw new DiscoveryPolicyError("invalid_args", "describe takes either address or addresses, not both.");
|
|
62
103
|
}
|
|
63
|
-
const value = args.address !== undefined
|
|
64
|
-
? typeof args.address === "string"
|
|
65
|
-
? [args.address]
|
|
66
|
-
: undefined
|
|
67
|
-
: args.addresses;
|
|
104
|
+
const value = args.address !== undefined ? (typeof args.address === "string" ? [args.address] : undefined) : args.addresses;
|
|
68
105
|
if (!Array.isArray(value)) {
|
|
69
106
|
throw new DiscoveryPolicyError("invalid_args", 'describe takes { address: "<connectorId>.<toolName>" } or { addresses: ["<connectorId>.<toolName>", ...] }.');
|
|
70
107
|
}
|
|
@@ -89,8 +126,7 @@ function editDistance(left, right) {
|
|
|
89
126
|
for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
|
|
90
127
|
const current = [leftIndex + 1];
|
|
91
128
|
for (let rightIndex = 0; rightIndex < right.length; rightIndex += 1) {
|
|
92
|
-
current.push(Math.min(previous[rightIndex + 1] + 1, current[rightIndex] + 1, previous[rightIndex] +
|
|
93
|
-
(left[leftIndex] === right[rightIndex] ? 0 : 1)));
|
|
129
|
+
current.push(Math.min(previous[rightIndex + 1] + 1, current[rightIndex] + 1, previous[rightIndex] + (left[leftIndex] === right[rightIndex] ? 0 : 1)));
|
|
94
130
|
}
|
|
95
131
|
previous = current;
|
|
96
132
|
}
|
|
@@ -99,7 +135,7 @@ function editDistance(left, right) {
|
|
|
99
135
|
/** Nearby names only; descriptions never influence describe-miss recovery. */
|
|
100
136
|
function describeSuggestions(connectorId, attemptedName, tools) {
|
|
101
137
|
const attempted = attemptedName.toLowerCase();
|
|
102
|
-
return tools
|
|
138
|
+
return (tools
|
|
103
139
|
.map((tool, order) => {
|
|
104
140
|
const name = tool.name.toLowerCase();
|
|
105
141
|
return { tool, order, distance: editDistance(attempted, name) };
|
|
@@ -115,7 +151,7 @@ function describeSuggestions(connectorId, attemptedName, tools) {
|
|
|
115
151
|
// A clipped address would no longer be canonical. Omit an implausibly
|
|
116
152
|
// large catalog name instead of letting one suggestion erase the page.
|
|
117
153
|
.filter((address) => boundedEchoText(address) === address)
|
|
118
|
-
.slice(0, MAX_DESCRIBE_SUGGESTIONS);
|
|
154
|
+
.slice(0, MAX_DESCRIBE_SUGGESTIONS));
|
|
119
155
|
}
|
|
120
156
|
/** Serialize once and count the exact bytes the MCP adapter would emit. */
|
|
121
157
|
export function boundedDiscoveryText(value, hint) {
|
|
@@ -132,9 +168,7 @@ export function boundedDiscoveryText(value, hint) {
|
|
|
132
168
|
function discoverySafety(value) {
|
|
133
169
|
if (value === undefined)
|
|
134
170
|
return "all";
|
|
135
|
-
if (value !== "readOnly" &&
|
|
136
|
-
value !== "approvalRequired" &&
|
|
137
|
-
value !== "all") {
|
|
171
|
+
if (value !== "readOnly" && value !== "approvalRequired" && value !== "all") {
|
|
138
172
|
throw new DiscoveryPolicyError("invalid_args", 'safety must be "readOnly", "approvalRequired", or "all".');
|
|
139
173
|
}
|
|
140
174
|
return value;
|
|
@@ -142,9 +176,7 @@ function discoverySafety(value) {
|
|
|
142
176
|
function toolsForSafety(tools, safety) {
|
|
143
177
|
if (safety === "all")
|
|
144
178
|
return tools;
|
|
145
|
-
return tools.filter((tool) => safety === "readOnly"
|
|
146
|
-
? isExplicitlyReadOnly(tool)
|
|
147
|
-
: !isExplicitlyReadOnly(tool));
|
|
179
|
+
return tools.filter((tool) => safety === "readOnly" ? tool.classification === "read" : tool.classification !== "read");
|
|
148
180
|
}
|
|
149
181
|
/**
|
|
150
182
|
* Reasons discovery can determine without reading arguments or guessing at a
|
|
@@ -157,7 +189,7 @@ function guideRequiredReasons(connector, tool, schemaTruncated) {
|
|
|
157
189
|
const reasons = [];
|
|
158
190
|
if (connectorGuideRequired(connector))
|
|
159
191
|
reasons.push("connector_required");
|
|
160
|
-
if (
|
|
192
|
+
if (tool.classification !== "read")
|
|
161
193
|
reasons.push("approval_required");
|
|
162
194
|
if (schemaTruncated)
|
|
163
195
|
reasons.push("schema_truncated");
|
|
@@ -178,11 +210,131 @@ function schemaKeyMetadata(input, output) {
|
|
|
178
210
|
requiredInputKeys: inputKeys.required,
|
|
179
211
|
}
|
|
180
212
|
: {}),
|
|
181
|
-
...(outputKeys && outputKeys.properties.length > 0
|
|
182
|
-
? { outputKeys: outputKeys.properties }
|
|
183
|
-
: {}),
|
|
213
|
+
...(outputKeys && outputKeys.properties.length > 0 ? { outputKeys: outputKeys.properties } : {}),
|
|
184
214
|
};
|
|
185
215
|
}
|
|
216
|
+
const stringListSchema = { type: "array", items: { type: "string" } };
|
|
217
|
+
const guideProperties = {
|
|
218
|
+
guide: { type: "string" },
|
|
219
|
+
guideSummary: { type: "string" },
|
|
220
|
+
guideRequired: { const: true },
|
|
221
|
+
guideRequiredReasons: {
|
|
222
|
+
type: "array",
|
|
223
|
+
items: { enum: ["connector_required", "approval_required", "schema_truncated"] },
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
const catalogFailureProperties = {
|
|
227
|
+
code: { type: "string" },
|
|
228
|
+
message: { type: "string" },
|
|
229
|
+
retryable: { type: "boolean" },
|
|
230
|
+
retryAfterMs: { type: "integer", minimum: 0 },
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* The outputSchema contract for either search adapter. Guest API registration
|
|
234
|
+
* can reuse this value instead of maintaining a second page definition.
|
|
235
|
+
*/
|
|
236
|
+
export const CATALOG_SEARCH_RESULT_SCHEMA = {
|
|
237
|
+
type: "object",
|
|
238
|
+
required: ["catalogErrors", "tools", "total", "offset", "limit", "hasMore"],
|
|
239
|
+
additionalProperties: false,
|
|
240
|
+
properties: {
|
|
241
|
+
catalogErrors: {
|
|
242
|
+
type: "array",
|
|
243
|
+
items: {
|
|
244
|
+
type: "object",
|
|
245
|
+
required: ["connector", "code", "message", "retryable"],
|
|
246
|
+
additionalProperties: false,
|
|
247
|
+
properties: {
|
|
248
|
+
connector: { type: "string" },
|
|
249
|
+
...catalogFailureProperties,
|
|
250
|
+
recovery: { enum: ["oauth", "operator_config", "unavailable"] },
|
|
251
|
+
retry: { type: "string" },
|
|
252
|
+
nextAction: {
|
|
253
|
+
type: "object",
|
|
254
|
+
required: ["tool", "arguments", "operatorHandoff"],
|
|
255
|
+
additionalProperties: false,
|
|
256
|
+
properties: {
|
|
257
|
+
tool: { const: "authorize_connector" },
|
|
258
|
+
arguments: {
|
|
259
|
+
type: "object",
|
|
260
|
+
required: ["connector"],
|
|
261
|
+
additionalProperties: false,
|
|
262
|
+
properties: { connector: { type: "string" } },
|
|
263
|
+
},
|
|
264
|
+
operatorHandoff: { type: "string" },
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
absence: {
|
|
271
|
+
type: "object",
|
|
272
|
+
required: ["service", "message", "configuredConnectors"],
|
|
273
|
+
additionalProperties: false,
|
|
274
|
+
properties: {
|
|
275
|
+
service: { type: "string" },
|
|
276
|
+
message: { type: "string" },
|
|
277
|
+
configuredConnectors: stringListSchema,
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
tools: {
|
|
281
|
+
type: "array",
|
|
282
|
+
items: {
|
|
283
|
+
type: "object",
|
|
284
|
+
required: ["name", "address"],
|
|
285
|
+
additionalProperties: false,
|
|
286
|
+
properties: {
|
|
287
|
+
name: { type: "string" },
|
|
288
|
+
address: { type: "string" },
|
|
289
|
+
connectorTitle: { type: "string" },
|
|
290
|
+
description: { type: "string" },
|
|
291
|
+
inputSchema: {},
|
|
292
|
+
outputSchema: {},
|
|
293
|
+
signature: { type: "string" },
|
|
294
|
+
schemaFormat: { enum: ["json", "text"] },
|
|
295
|
+
outputSchemaSource: { const: "observed" },
|
|
296
|
+
inputSchemaTruncated: { const: true },
|
|
297
|
+
outputSchemaTruncated: { const: true },
|
|
298
|
+
inputKeys: stringListSchema,
|
|
299
|
+
requiredInputKeys: stringListSchema,
|
|
300
|
+
outputKeys: stringListSchema,
|
|
301
|
+
annotations: { type: "object" },
|
|
302
|
+
classification: { enum: ["read", "write"] },
|
|
303
|
+
...guideProperties,
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
total: { type: "integer", minimum: 0 },
|
|
308
|
+
offset: { type: "integer", minimum: 0 },
|
|
309
|
+
limit: { type: "integer", minimum: 1, maximum: MAX_SEARCH_LIMIT },
|
|
310
|
+
hasMore: { type: "boolean" },
|
|
311
|
+
nextOffset: { type: "integer", minimum: 0 },
|
|
312
|
+
matchMode: { const: "partial" },
|
|
313
|
+
queryAnalysis: {
|
|
314
|
+
type: "object",
|
|
315
|
+
required: ["representedTerms", "otherResultTerms", "unmatchedTerms"],
|
|
316
|
+
additionalProperties: false,
|
|
317
|
+
properties: {
|
|
318
|
+
representedTerms: stringListSchema,
|
|
319
|
+
otherResultTerms: stringListSchema,
|
|
320
|
+
unmatchedTerms: stringListSchema,
|
|
321
|
+
truncated: { const: true },
|
|
322
|
+
connectorScope: { type: "string" },
|
|
323
|
+
unknownConnector: { const: true },
|
|
324
|
+
configuredConnectors: stringListSchema,
|
|
325
|
+
unavailableConnectorCount: { type: "integer", minimum: 0 },
|
|
326
|
+
catalogError: {
|
|
327
|
+
type: "object",
|
|
328
|
+
required: ["code", "message", "retryable"],
|
|
329
|
+
additionalProperties: false,
|
|
330
|
+
properties: catalogFailureProperties,
|
|
331
|
+
},
|
|
332
|
+
...guideProperties,
|
|
333
|
+
guidance: { type: "string" },
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
};
|
|
186
338
|
function renderSearchSchema(schema, format) {
|
|
187
339
|
if (format === "json")
|
|
188
340
|
return { schema, truncated: false };
|
|
@@ -198,16 +350,17 @@ export class CatalogService {
|
|
|
198
350
|
registry;
|
|
199
351
|
baseUrl;
|
|
200
352
|
requestScope;
|
|
353
|
+
defer;
|
|
201
354
|
probeTimeoutMs;
|
|
202
355
|
concurrency;
|
|
203
356
|
searchRoute;
|
|
204
|
-
readOptions;
|
|
205
357
|
requestSignal;
|
|
206
|
-
approval;
|
|
207
358
|
// The request-scoped catalog cache: one shared read per connector asked
|
|
208
359
|
// about, started by the first asker and joined by every later one. A
|
|
209
360
|
// success stays for the rest of the request, so a search and the call after
|
|
210
|
-
// it see one catalog
|
|
361
|
+
// it see one catalog. The cache owns a deep copy and each asker receives
|
|
362
|
+
// another, so discovery, invocation, and guest code never hold its objects.
|
|
363
|
+
// A failure, or a read cancelled because every asker
|
|
211
364
|
// left it, is dropped so the next ask reads again. The service lives and
|
|
212
365
|
// dies with one request, so no read here is ever awaited from another.
|
|
213
366
|
//
|
|
@@ -220,19 +373,12 @@ export class CatalogService {
|
|
|
220
373
|
constructor(registry, baseUrl, options = {}) {
|
|
221
374
|
this.registry = registry;
|
|
222
375
|
this.baseUrl = baseUrl;
|
|
223
|
-
this.approval = options.approval ?? NO_EXEMPTIONS;
|
|
224
376
|
this.requestScope = options.requestScope ?? {};
|
|
225
|
-
this.probeTimeoutMs =
|
|
226
|
-
normalizeTimeoutMs(options.probeTimeoutMs) ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
377
|
+
this.probeTimeoutMs = normalizeTimeoutMs(options.probeTimeoutMs) ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
227
378
|
this.concurrency = resolveDiscoveryConcurrency(options.concurrency);
|
|
228
379
|
this.searchRoute = options.searchRoute ?? "search_tools";
|
|
229
380
|
this.requestSignal = options.requestSignal;
|
|
230
|
-
this.
|
|
231
|
-
? {
|
|
232
|
-
defer: options.defer,
|
|
233
|
-
refreshTimeoutMs: this.probeTimeoutMs,
|
|
234
|
-
}
|
|
235
|
-
: undefined;
|
|
381
|
+
this.defer = options.defer;
|
|
236
382
|
}
|
|
237
383
|
/**
|
|
238
384
|
* Send a caller back to discovery through the surface it can actually reach.
|
|
@@ -269,7 +415,13 @@ export class CatalogService {
|
|
|
269
415
|
return Effect.suspend(() => {
|
|
270
416
|
let read = this.catalogs.get(id);
|
|
271
417
|
if (!read) {
|
|
272
|
-
const started = new SharedRead((signal) => this.registry
|
|
418
|
+
const started = new SharedRead((signal) => this.registry
|
|
419
|
+
.getTools(id, this.baseUrl, this.requestScope, {
|
|
420
|
+
signal,
|
|
421
|
+
timeoutMs: this.probeTimeoutMs,
|
|
422
|
+
...(this.defer ? { defer: this.defer } : {}),
|
|
423
|
+
})
|
|
424
|
+
.then((tools) => carryCatalogFreshness(tools, structuredClone(tools))), (succeeded) => {
|
|
273
425
|
// Evicted before the read resumes anyone, so an asker that
|
|
274
426
|
// retries on hearing of the failure starts a fresh read.
|
|
275
427
|
if (!succeeded && this.catalogs.get(id) === started) {
|
|
@@ -279,7 +431,7 @@ export class CatalogService {
|
|
|
279
431
|
this.catalogs.set(id, started);
|
|
280
432
|
read = started;
|
|
281
433
|
}
|
|
282
|
-
return read.join(callOptions.signal);
|
|
434
|
+
return Effect.map(read.join(callOptions.signal), (tools) => carryCatalogFreshness(tools, structuredClone(tools)));
|
|
283
435
|
});
|
|
284
436
|
}
|
|
285
437
|
// Every named catalog under its own probe deadline, `concurrency` at a time,
|
|
@@ -291,22 +443,21 @@ export class CatalogService {
|
|
|
291
443
|
return Effect.forEach(ids, (id) => Effect.result(withDeadlineEffect((signal) => this.catalog(id, { signal }), {
|
|
292
444
|
...(this.requestSignal ? { signal: this.requestSignal } : {}),
|
|
293
445
|
timeoutMs: this.probeTimeoutMs,
|
|
294
|
-
timeoutError: new
|
|
446
|
+
timeoutError: new ConnectorCallError("timeout", `${route} probe of "${id}" timed out after ${this.probeTimeoutMs}ms`),
|
|
295
447
|
})), { concurrency: this.concurrency });
|
|
296
448
|
}
|
|
297
449
|
outputSchema(connectorId, tool) {
|
|
298
450
|
if (tool.outputSchema)
|
|
299
451
|
return { schema: tool.outputSchema };
|
|
300
452
|
const observed = this.registry.observedOutputSchema(connectorId, tool);
|
|
301
|
-
return observed
|
|
302
|
-
? { schema: observed, source: "observed" }
|
|
303
|
-
: {};
|
|
453
|
+
return observed ? { schema: observed, source: "observed" } : {};
|
|
304
454
|
}
|
|
305
455
|
unknownAddressFailure(address, query) {
|
|
306
456
|
return {
|
|
307
457
|
ok: false,
|
|
308
458
|
error: {
|
|
309
459
|
...framingError("unknown_address", `Unknown address "${boundedEchoText(address)}"`),
|
|
460
|
+
configuredConnectors: this.registry.listConnectors().map((connector) => connector.id),
|
|
310
461
|
nextAction: this.searchRecovery({ query: recoveryQuery(query) }, "Find the configured canonical address before retrying."),
|
|
311
462
|
},
|
|
312
463
|
catalogMs: 0,
|
|
@@ -326,8 +477,10 @@ export class CatalogService {
|
|
|
326
477
|
return {
|
|
327
478
|
ok: false,
|
|
328
479
|
error: {
|
|
329
|
-
...framingError("unknown_tool",
|
|
330
|
-
|
|
480
|
+
...framingError("unknown_tool", hasControlCharacters(toolName)
|
|
481
|
+
? `Unknown tool on connector "${connector.id}"`
|
|
482
|
+
: `Unknown tool "${boundedEchoText(toolName)}" on connector "${connector.id}"`),
|
|
483
|
+
nextAction: this.searchRecovery({ query: hasControlCharacters(toolName) ? "" : recoveryQuery(toolName), connector: connector.id }, "Find the connector's current canonical tool address."),
|
|
331
484
|
},
|
|
332
485
|
catalogMs: Date.now() - started,
|
|
333
486
|
connector,
|
|
@@ -357,7 +510,7 @@ export class CatalogService {
|
|
|
357
510
|
}
|
|
358
511
|
return {
|
|
359
512
|
ok: true,
|
|
360
|
-
resolved: { connector, toolName, definition },
|
|
513
|
+
resolved: { connector, toolName, definition, classificationFresh: catalogIsFresh(tools) },
|
|
361
514
|
catalogMs: Date.now() - started,
|
|
362
515
|
};
|
|
363
516
|
},
|
|
@@ -368,8 +521,7 @@ export class CatalogService {
|
|
|
368
521
|
if (args.query !== undefined && typeof args.query !== "string") {
|
|
369
522
|
throw new DiscoveryPolicyError("invalid_args", "query must be a string. Omit it or use an empty string to browse the catalog.");
|
|
370
523
|
}
|
|
371
|
-
if (args.connector !== undefined &&
|
|
372
|
-
boundedEchoText(args.connector) !== args.connector) {
|
|
524
|
+
if (args.connector !== undefined && boundedEchoText(args.connector) !== args.connector) {
|
|
373
525
|
// The scope is echoed back as `queryAnalysis.connectorScope`; a clipped
|
|
374
526
|
// copy could name a different connector, so refuse instead of clamping.
|
|
375
527
|
throw new DiscoveryPolicyError("invalid_args", "connector must be at most 512 UTF-8 bytes.");
|
|
@@ -378,38 +530,44 @@ export class CatalogService {
|
|
|
378
530
|
const retrievalQuery = lexicalSearchQuery(query);
|
|
379
531
|
const safety = discoverySafety(args.safety);
|
|
380
532
|
const limit = discoverySearchLimit(args.limit);
|
|
381
|
-
if (args.offset !== undefined &&
|
|
382
|
-
!Number.isInteger(args.offset) ||
|
|
383
|
-
args.offset < 0)) {
|
|
533
|
+
if (args.offset !== undefined &&
|
|
534
|
+
(typeof args.offset !== "number" || !Number.isInteger(args.offset) || args.offset < 0)) {
|
|
384
535
|
throw new DiscoveryPolicyError("invalid_args", "offset must be a non-negative whole number. Start at 0 or use the previous page's nextOffset.");
|
|
385
536
|
}
|
|
386
537
|
const offset = args.offset ?? 0;
|
|
387
|
-
const scopedConnector = args.connector
|
|
388
|
-
|
|
538
|
+
const scopedConnector = args.connector ? this.registry.getConnector(args.connector) : undefined;
|
|
539
|
+
const visibleConnectors = this.registry.listConnectors();
|
|
540
|
+
const namedService = !args.connector ? SERVICE_NAMES.find((name) => containsPhrase(query, name)) : undefined;
|
|
541
|
+
const absentService = args.connector && !scopedConnector
|
|
542
|
+
? args.connector
|
|
543
|
+
: namedService &&
|
|
544
|
+
!visibleConnectors.some((connector) => connectorNames(connector).some((name) => containsPhrase(name, namedService)))
|
|
545
|
+
? namedService
|
|
546
|
+
: undefined;
|
|
547
|
+
const absence = absentService
|
|
548
|
+
? {
|
|
549
|
+
service: absentService,
|
|
550
|
+
message: `No connector for "${absentService}" is configured for this endpoint.`,
|
|
551
|
+
configuredConnectors: visibleConnectors.map((connector) => connector.id),
|
|
552
|
+
}
|
|
389
553
|
: undefined;
|
|
390
|
-
const connectors = args.connector
|
|
391
|
-
|
|
392
|
-
? [scopedConnector]
|
|
393
|
-
: []
|
|
394
|
-
: this.registry.listConnectors();
|
|
395
|
-
// Named for this service's route: a scoped search echoes a timed-out
|
|
396
|
-
// probe's message back as `queryAnalysis.catalogError`, and a program
|
|
397
|
-
// cannot call search_tools.
|
|
554
|
+
const connectors = absence ? [] : args.connector ? (scopedConnector ? [scopedConnector] : []) : visibleConnectors;
|
|
555
|
+
// Timeout messages name the route the caller used.
|
|
398
556
|
const catalogs = await runEdge(this.discoveryCatalogs(connectors.map((connector) => connector.id), this.searchRoute));
|
|
399
557
|
const searchableCatalogs = catalogs.map((catalog) => Result.map(catalog, (tools) => toolsForSafety(tools, safety)));
|
|
400
558
|
const matches = [];
|
|
401
559
|
let matchMode = "all";
|
|
402
|
-
const statistics = lexicalCorpusStatistics(searchableCatalogs.flatMap((catalog) => Result.isSuccess(catalog) ? [catalog.success] : []), retrievalQuery);
|
|
560
|
+
const statistics = lexicalCorpusStatistics(searchableCatalogs.flatMap((catalog) => (Result.isSuccess(catalog) ? [catalog.success] : [])), retrievalQuery);
|
|
403
561
|
const trimmedQuery = query.trim();
|
|
404
562
|
const isBrowse = trimmedQuery.length === 0;
|
|
405
563
|
const queryTerms = lexicalQueryTerms(retrievalQuery);
|
|
564
|
+
const rawPhrase = lexicalQueryTerms(query).join(" ");
|
|
406
565
|
const queryTermCount = queryTerms.length;
|
|
407
566
|
const unsearchableQuery = !isBrowse && queryTermCount === 0;
|
|
408
567
|
const analysisTerms = unsearchableQuery ? [trimmedQuery] : queryTerms;
|
|
409
568
|
const analyzedTerms = analysisTerms.slice(0, MAX_QUERY_TERMS);
|
|
410
569
|
const displayTerm = (term) => boundedQueryTerm(term).text;
|
|
411
|
-
const queryMetadataTruncated = analysisTerms.length > analyzedTerms.length ||
|
|
412
|
-
analyzedTerms.some((term) => boundedQueryTerm(term).truncated);
|
|
570
|
+
const queryMetadataTruncated = analysisTerms.length > analyzedTerms.length || analyzedTerms.some((term) => boundedQueryTerm(term).truncated);
|
|
413
571
|
const collectMatches = (mode) => {
|
|
414
572
|
const collected = [];
|
|
415
573
|
let orderBase = 0;
|
|
@@ -419,15 +577,43 @@ export class CatalogService {
|
|
|
419
577
|
throw new Error("Catalog result has no corresponding connector");
|
|
420
578
|
}
|
|
421
579
|
if (Result.isSuccess(catalog)) {
|
|
422
|
-
|
|
580
|
+
const connectorRank = args.connector ? 0 : connectorMatchRank(query, connector);
|
|
581
|
+
const exactConnector = connectorRank > 0;
|
|
582
|
+
const connectorTerms = new Set([
|
|
583
|
+
...connectorNames(connector).flatMap(lexicalQueryTerms),
|
|
584
|
+
...(namedService && connectorNames(connector).some((name) => containsPhrase(name, namedService))
|
|
585
|
+
? lexicalQueryTerms(namedService)
|
|
586
|
+
: []),
|
|
587
|
+
]);
|
|
588
|
+
const localStatistics = exactConnector
|
|
589
|
+
? lexicalCorpusStatistics([catalog.success], retrievalQuery)
|
|
590
|
+
: undefined;
|
|
591
|
+
// An exact identity is a browse even when one tool repeats that
|
|
592
|
+
// identity in its name or description. Action queries still keep
|
|
593
|
+
// identity terms that also name an actual capability.
|
|
594
|
+
const identityBrowse = connectorNames(connector).some((name) => normalizedPhrase(query) === normalizedPhrase(name));
|
|
595
|
+
const toolQuery = identityBrowse
|
|
596
|
+
? ""
|
|
597
|
+
: exactConnector
|
|
598
|
+
? queryTerms
|
|
599
|
+
.filter((term) => !connectorTerms.has(term) ||
|
|
600
|
+
(localStatistics?.nameMatches.get(term)?.size ?? 0) > 0 ||
|
|
601
|
+
(localStatistics?.descriptionMatches.get(term)?.size ?? 0) > 0)
|
|
602
|
+
.join(" ")
|
|
603
|
+
: retrievalQuery;
|
|
604
|
+
const toolTermCount = lexicalQueryTerms(toolQuery).length;
|
|
605
|
+
for (const ranked of rankTools(catalog.success, toolQuery, mode, statistics, query)) {
|
|
423
606
|
collected.push({
|
|
424
607
|
connector,
|
|
425
608
|
tool: ranked.tool,
|
|
426
609
|
score: ranked.score,
|
|
427
610
|
order: orderBase + ranked.order,
|
|
428
611
|
exactName: ranked.exactName,
|
|
612
|
+
exactLookup: rawPhrase === lexicalQueryTerms(ranked.tool.name).join(" ") ||
|
|
613
|
+
rawPhrase === lexicalQueryTerms(`${connector.id}.${ranked.tool.name}`).join(" "),
|
|
614
|
+
connectorRank,
|
|
429
615
|
matchedTermCount: ranked.matchedTermCount,
|
|
430
|
-
complete: isBrowse || ranked.matchedTermCount ===
|
|
616
|
+
complete: isBrowse || ranked.matchedTermCount === toolTermCount,
|
|
431
617
|
});
|
|
432
618
|
}
|
|
433
619
|
}
|
|
@@ -438,34 +624,33 @@ export class CatalogService {
|
|
|
438
624
|
// A non-empty query that normalizes to no lexical terms is not a browse.
|
|
439
625
|
// Ranking an empty phrase would otherwise return every tool as an
|
|
440
626
|
// unrelated zero-score match, with no coverage to explain the result.
|
|
441
|
-
const rankedMatches = unsearchableQuery
|
|
442
|
-
? []
|
|
443
|
-
: collectMatches(isBrowse ? "all" : "partial");
|
|
627
|
+
const rankedMatches = unsearchableQuery ? [] : collectMatches(isBrowse ? "all" : "partial");
|
|
444
628
|
const completeMatchCount = rankedMatches.filter((match) => match.complete).length;
|
|
445
629
|
matches.push(...rankedMatches.filter((match) => match.complete ||
|
|
446
630
|
completeMatchCount === 0 ||
|
|
447
631
|
match.exactName ||
|
|
632
|
+
match.connectorRank > 0 ||
|
|
448
633
|
match.matchedTermCount >= 2));
|
|
449
634
|
if (!isBrowse && !unsearchableQuery && completeMatchCount === 0) {
|
|
450
635
|
matchMode = "partial";
|
|
451
636
|
}
|
|
452
|
-
//
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
// complete match, regardless of a rare-term score spike.
|
|
637
|
+
// Exact tool names precede connector identity and lexical scoring. A
|
|
638
|
+
// longer name such as get_issue_status cannot beat an exact get_issue
|
|
639
|
+
// merely because its description repeats rare query terms.
|
|
456
640
|
matches.sort((a, b) => {
|
|
457
641
|
const aFirstTier = a.complete || a.exactName;
|
|
458
642
|
const bFirstTier = b.complete || b.exactName;
|
|
459
|
-
return (Number(
|
|
643
|
+
return (Number(b.exactLookup) - Number(a.exactLookup) ||
|
|
644
|
+
Number(b.exactName) - Number(a.exactName) ||
|
|
645
|
+
b.connectorRank - a.connectorRank ||
|
|
646
|
+
Number(bFirstTier) - Number(aFirstTier) ||
|
|
460
647
|
b.score - a.score ||
|
|
461
648
|
a.order - b.order);
|
|
462
649
|
});
|
|
463
650
|
const pageMatches = matches.slice(offset, offset + limit);
|
|
464
651
|
const withSchemas = args.includeSchemas;
|
|
465
652
|
const entries = pageMatches.map((match) => {
|
|
466
|
-
const output = withSchemas
|
|
467
|
-
? this.outputSchema(match.connector.id, match.tool)
|
|
468
|
-
: {};
|
|
653
|
+
const output = withSchemas ? this.outputSchema(match.connector.id, match.tool) : {};
|
|
469
654
|
const input = match.tool.inputSchema ?? { type: "object" };
|
|
470
655
|
const signature = withSchemas === "typescript"
|
|
471
656
|
? typescriptSignature(input, output.schema, {
|
|
@@ -484,9 +669,7 @@ export class CatalogService {
|
|
|
484
669
|
: schemaFormat && output.schema
|
|
485
670
|
? renderSearchSchema(output.schema, schemaFormat)
|
|
486
671
|
: undefined;
|
|
487
|
-
const schemaKeys = withSchemas && args.includeSchemaKeys
|
|
488
|
-
? schemaKeyMetadata(input, output.schema)
|
|
489
|
-
: undefined;
|
|
672
|
+
const schemaKeys = withSchemas && args.includeSchemaKeys ? schemaKeyMetadata(input, output.schema) : undefined;
|
|
490
673
|
const description = summarizeDiscoveryDescription(match.tool.description, args.fullDescriptions === true);
|
|
491
674
|
const requiredReasons = guideRequiredReasons(match.connector, match.tool, renderedInput?.truncated === true || renderedOutput?.truncated === true);
|
|
492
675
|
const guideSummary = connectorGuideSummary(match.connector);
|
|
@@ -507,42 +690,25 @@ export class CatalogService {
|
|
|
507
690
|
inputSchema: renderedInput?.schema,
|
|
508
691
|
}
|
|
509
692
|
: {}),
|
|
693
|
+
...(withSchemas ? { schemaFormat: withSchemas === "json" ? "json" : "text" } : {}),
|
|
510
694
|
...(signature ? { signature: signature.text } : {}),
|
|
511
|
-
...(renderedInput?.truncated
|
|
512
|
-
? { inputSchemaTruncated: true }
|
|
513
|
-
: {}),
|
|
695
|
+
...(renderedInput?.truncated ? { inputSchemaTruncated: true } : {}),
|
|
514
696
|
...(schemaFormat && output.schema
|
|
515
697
|
? {
|
|
516
698
|
outputSchema: renderedOutput?.schema,
|
|
517
699
|
}
|
|
518
700
|
: {}),
|
|
519
|
-
...(withSchemas && output.source
|
|
520
|
-
|
|
521
|
-
: {}),
|
|
522
|
-
...(renderedOutput?.truncated
|
|
523
|
-
? { outputSchemaTruncated: true }
|
|
524
|
-
: {}),
|
|
701
|
+
...(withSchemas && output.source ? { outputSchemaSource: output.source } : {}),
|
|
702
|
+
...(renderedOutput?.truncated ? { outputSchemaTruncated: true } : {}),
|
|
525
703
|
...(schemaKeys && !renderedInput?.truncated
|
|
526
704
|
? {
|
|
527
|
-
...(schemaKeys.inputKeys
|
|
528
|
-
|
|
529
|
-
: {}),
|
|
530
|
-
...(schemaKeys.requiredInputKeys
|
|
531
|
-
? { requiredInputKeys: schemaKeys.requiredInputKeys }
|
|
532
|
-
: {}),
|
|
705
|
+
...(schemaKeys.inputKeys ? { inputKeys: schemaKeys.inputKeys } : {}),
|
|
706
|
+
...(schemaKeys.requiredInputKeys ? { requiredInputKeys: schemaKeys.requiredInputKeys } : {}),
|
|
533
707
|
}
|
|
534
708
|
: {}),
|
|
535
|
-
...(schemaKeys?.outputKeys && !renderedOutput?.truncated
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
...(match.tool.annotations
|
|
539
|
-
? { annotations: match.tool.annotations }
|
|
540
|
-
: {}),
|
|
541
|
-
// Config lets a program call this write without asking (#566).
|
|
542
|
-
// A marker, not a class: the tool stays approval-required.
|
|
543
|
-
...(isApprovalExempt(this.approval, match.connector, match.tool.name, match.tool)
|
|
544
|
-
? { approval: "exempt" }
|
|
545
|
-
: {}),
|
|
709
|
+
...(schemaKeys?.outputKeys && !renderedOutput?.truncated ? { outputKeys: schemaKeys.outputKeys } : {}),
|
|
710
|
+
...(match.tool.annotations ? { annotations: match.tool.annotations } : {}),
|
|
711
|
+
classification: match.tool.classification,
|
|
546
712
|
...(requiredReasons
|
|
547
713
|
? {
|
|
548
714
|
guideRequired: true,
|
|
@@ -552,14 +718,9 @@ export class CatalogService {
|
|
|
552
718
|
},
|
|
553
719
|
};
|
|
554
720
|
});
|
|
555
|
-
const nextOffset = offset + entries.length < matches.length
|
|
556
|
-
? offset + entries.length
|
|
557
|
-
: undefined;
|
|
721
|
+
const nextOffset = offset + entries.length < matches.length ? offset + entries.length : undefined;
|
|
558
722
|
const pageTools = new Set(pageMatches.map((match) => match.tool));
|
|
559
|
-
const matchingTools = (term) => new Set([
|
|
560
|
-
...(statistics.nameMatches.get(term) ?? []),
|
|
561
|
-
...(statistics.descriptionMatches.get(term) ?? []),
|
|
562
|
-
]);
|
|
723
|
+
const matchingTools = (term) => new Set([...(statistics.nameMatches.get(term) ?? []), ...(statistics.descriptionMatches.get(term) ?? [])]);
|
|
563
724
|
const representedTerms = [];
|
|
564
725
|
const otherResultTerms = [];
|
|
565
726
|
const unmatchedTerms = [];
|
|
@@ -576,32 +737,61 @@ export class CatalogService {
|
|
|
576
737
|
}
|
|
577
738
|
}
|
|
578
739
|
const unavailableCatalogs = catalogs.filter(Result.isFailure).length;
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
740
|
+
const catalogErrors = catalogs.flatMap((catalog, index) => {
|
|
741
|
+
if (Result.isSuccess(catalog))
|
|
742
|
+
return [];
|
|
743
|
+
const connector = connectors[index];
|
|
744
|
+
const classified = classifyCallError(catalog.failure, "catalog_lookup_failed");
|
|
745
|
+
const error = {
|
|
746
|
+
connector: connector.id,
|
|
747
|
+
code: classified.code,
|
|
748
|
+
// Catalog discovery reports recovery facts, never connector-authored
|
|
749
|
+
// error text. Clipping a downstream message does not make it safe.
|
|
750
|
+
message: classified.code === "downstream_oauth_required"
|
|
751
|
+
? `Connector "${connector.id}" requires downstream OAuth authorization. Call authorize_connector and give its handoff to the operator.`
|
|
752
|
+
: classified.code === "auth_required"
|
|
753
|
+
? `Connector "${connector.id}" requires operator-managed credentials or configuration. Call authorize_connector and give its handoff to the operator.`
|
|
754
|
+
: classified.code === "provider_permission_denied"
|
|
755
|
+
? `Connector "${connector.id}" requires permission from the provider's resource owner or administrator. Reconnecting alone will not grant access.`
|
|
756
|
+
: `Connector "${connector.id}" catalog lookup failed (${classified.code}).`,
|
|
757
|
+
retryable: classified.retryable,
|
|
758
|
+
...(classified.retryAfterMs === undefined ? {} : { retryAfterMs: classified.retryAfterMs }),
|
|
598
759
|
};
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
760
|
+
if (error.code === "auth_required" || error.code === "downstream_oauth_required") {
|
|
761
|
+
error.recovery = connector.startAuth
|
|
762
|
+
? "oauth"
|
|
763
|
+
: this.registry.credentialUiAvailable() &&
|
|
764
|
+
connector.credential &&
|
|
765
|
+
this.registry.contextFor(connector.id, this.baseUrl, this.requestScope).credential
|
|
766
|
+
? "operator_config"
|
|
767
|
+
: "unavailable";
|
|
768
|
+
error.nextAction = {
|
|
769
|
+
tool: "authorize_connector",
|
|
770
|
+
arguments: { connector: connector.id },
|
|
771
|
+
operatorHandoff: "Give the URL and instructions it returns to the operator.",
|
|
772
|
+
};
|
|
773
|
+
error.retry = `Retry discovery after the operator completes recovery for "${connector.id}".`;
|
|
774
|
+
}
|
|
775
|
+
else if (error.code === "provider_permission_denied") {
|
|
776
|
+
error.retry =
|
|
777
|
+
"Ask the provider's resource owner or administrator to grant the required permission or scope, then retry. Reconnecting alone will not grant access.";
|
|
778
|
+
}
|
|
779
|
+
return [error];
|
|
780
|
+
});
|
|
781
|
+
const needsAuth = (error) => error.code === "auth_required" ||
|
|
782
|
+
error.code === "downstream_oauth_required" ||
|
|
783
|
+
error.code === "provider_permission_denied";
|
|
784
|
+
catalogErrors.sort((a, b) => Number(needsAuth(b)) - Number(needsAuth(a)));
|
|
785
|
+
const scopedFailure = args.connector ? catalogErrors[0] : undefined;
|
|
786
|
+
const scopedCatalogError = scopedFailure
|
|
787
|
+
? {
|
|
788
|
+
code: scopedFailure.code,
|
|
789
|
+
message: scopedFailure.message,
|
|
790
|
+
retryable: scopedFailure.retryable,
|
|
791
|
+
...(scopedFailure.retryAfterMs === undefined ? {} : { retryAfterMs: scopedFailure.retryAfterMs }),
|
|
792
|
+
}
|
|
793
|
+
: undefined;
|
|
794
|
+
const safetyLabel = safety === "readOnly" ? "read-only " : safety === "approvalRequired" ? "approval-required " : "";
|
|
605
795
|
const filterRecovery = safety === "all" ? "" : " Change safety to inspect the other tools.";
|
|
606
796
|
const scopedGuide = matches.length === 0 && scopedConnector && connectorGuide(scopedConnector)
|
|
607
797
|
? {
|
|
@@ -610,15 +800,9 @@ export class CatalogService {
|
|
|
610
800
|
required: connectorGuideRequired(scopedConnector),
|
|
611
801
|
}
|
|
612
802
|
: undefined;
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
//
|
|
616
|
-
// ranking for every query that already matches tools, so instead a search
|
|
617
|
-
// that matched nothing asks the same lexical question of connector
|
|
618
|
-
// identity and corrects its own sentence: the deployment plainly has this
|
|
619
|
-
// capability, and one scoped browse away are its tools. Unscoped only —
|
|
620
|
-
// guidance for an explicit scope already names that connector rather than
|
|
621
|
-
// claiming the deployment has nothing.
|
|
803
|
+
// Partial identity words can still explain a miss. Exact identities are
|
|
804
|
+
// already ranking inputs above; this guidance handles a title word such
|
|
805
|
+
// as "warehouse" when the full title is "Warehouse stock".
|
|
622
806
|
const identityConnectorIds = matches.length === 0 && !isBrowse && !unsearchableQuery && !scopedConnector
|
|
623
807
|
? connectors
|
|
624
808
|
// The full query, not `analyzedTerms`: that cap exists to bound
|
|
@@ -626,8 +810,7 @@ export class CatalogService {
|
|
|
626
810
|
// term. A ninth term is a real search term, and a search that
|
|
627
811
|
// ranked against it must not deny the connector it names.
|
|
628
812
|
.filter((connector) => queryTerms.some((term) => matchesLexicalTerm(connector.id, term) ||
|
|
629
|
-
(connector.title !== undefined &&
|
|
630
|
-
matchesLexicalTerm(connector.title, term))))
|
|
813
|
+
(connector.title !== undefined && matchesLexicalTerm(connector.title, term))))
|
|
631
814
|
.map((connector) => connector.id)
|
|
632
815
|
: [];
|
|
633
816
|
const namedIdentityConnectors = identityConnectorIds
|
|
@@ -637,17 +820,16 @@ export class CatalogService {
|
|
|
637
820
|
const unnamedIdentityConnectors = identityConnectorIds.length - MAX_IDENTITY_CONNECTORS;
|
|
638
821
|
const identityGuidance = identityConnectorIds.length === 0
|
|
639
822
|
? undefined
|
|
640
|
-
: `No matching ${safetyLabel}capability was found${unavailableCatalogs === 0 ? "" : " in the catalogs that answered"}, but the query names configured connector${identityConnectorIds.length === 1 ? "" : "s"} ${namedIdentityConnectors}${unnamedIdentityConnectors > 0
|
|
641
|
-
? ` and ${unnamedIdentityConnectors} more`
|
|
642
|
-
: ""}. Scope by connector and browse with an empty query to list the tools there.${filterRecovery}`;
|
|
823
|
+
: `No matching ${safetyLabel}capability was found${unavailableCatalogs === 0 ? "" : " in the catalogs that answered"}, but the query names configured connector${identityConnectorIds.length === 1 ? "" : "s"} ${namedIdentityConnectors}${unnamedIdentityConnectors > 0 ? ` and ${unnamedIdentityConnectors} more` : ""}. Scope by connector and browse with an empty query to list the tools there.${filterRecovery}`;
|
|
643
824
|
// A scope that resolved to nothing is the same silence one step earlier in
|
|
644
825
|
// the lookup: no connector resolved, so no catalog was even attempted, so
|
|
645
|
-
// no catalog failed and the unavailable path below never fires.
|
|
646
|
-
//
|
|
647
|
-
// would answer a question they did not ask, past a filter they may not
|
|
648
|
-
// pass.
|
|
826
|
+
// no catalog failed and the unavailable path below never fires. Name
|
|
827
|
+
// only configured connectors in the current identity/pool registry view.
|
|
649
828
|
const unknownConnectorGuidance = args.connector && !scopedConnector
|
|
650
|
-
? `Connector "${args.connector}" is not configured in this deployment. Omit connector to search all configured tools
|
|
829
|
+
? `Connector "${args.connector}" is not configured in this deployment. Omit connector to search all configured tools. Configured connectors for this endpoint: ${this.registry
|
|
830
|
+
.listConnectors()
|
|
831
|
+
.map((connector) => connector.id)
|
|
832
|
+
.join(", ") || "none"}.`
|
|
651
833
|
: undefined;
|
|
652
834
|
// Searchable queries report analysis when the scorer had to degrade. A
|
|
653
835
|
// non-empty query with no searchable terms reports the bounded raw input
|
|
@@ -655,13 +837,16 @@ export class CatalogService {
|
|
|
655
837
|
// no terms to analyse and normally reports none at all. Scope failures are
|
|
656
838
|
// the exception, because an empty result alone looks like a connector that
|
|
657
839
|
// correctly exposes no tools.
|
|
658
|
-
const reportsQueryAnalysis =
|
|
840
|
+
const reportsQueryAnalysis = absence !== undefined ||
|
|
841
|
+
unsearchableQuery ||
|
|
659
842
|
(queryTerms.length > 0
|
|
660
843
|
? matchMode === "partial"
|
|
661
844
|
: unknownConnectorGuidance !== undefined || unavailableCatalogs > 0);
|
|
662
845
|
const searchGuidance = () => {
|
|
663
846
|
if (unknownConnectorGuidance)
|
|
664
847
|
return unknownConnectorGuidance;
|
|
848
|
+
if (absence)
|
|
849
|
+
return absence.message;
|
|
665
850
|
if (unsearchableQuery) {
|
|
666
851
|
return scopedConnector && unavailableCatalogs > 0
|
|
667
852
|
? `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
@@ -672,7 +857,7 @@ export class CatalogService {
|
|
|
672
857
|
return undefined;
|
|
673
858
|
return scopedConnector
|
|
674
859
|
? `Connector "${scopedConnector.id}" could not be browsed because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
675
|
-
: `${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable, so this browse is incomplete.
|
|
860
|
+
: `${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable, so this browse is incomplete. Inspect catalogErrors for the typed reasons and recovery actions.`;
|
|
676
861
|
}
|
|
677
862
|
if (matches.length === 0) {
|
|
678
863
|
if (scopedConnector) {
|
|
@@ -700,44 +885,39 @@ export class CatalogService {
|
|
|
700
885
|
};
|
|
701
886
|
const guidance = searchGuidance();
|
|
702
887
|
return {
|
|
888
|
+
catalogErrors,
|
|
889
|
+
...(absence ? { absence } : {}),
|
|
703
890
|
entries,
|
|
704
891
|
total: matches.length,
|
|
705
892
|
offset,
|
|
706
893
|
limit,
|
|
707
894
|
hasMore: nextOffset !== undefined,
|
|
708
895
|
...(nextOffset !== undefined ? { nextOffset } : {}),
|
|
709
|
-
...(matchMode === "partial" && matches.length > 0
|
|
710
|
-
? { matchMode }
|
|
711
|
-
: {}),
|
|
896
|
+
...(matchMode === "partial" && matches.length > 0 ? { matchMode } : {}),
|
|
712
897
|
...(reportsQueryAnalysis
|
|
713
898
|
? {
|
|
714
899
|
queryAnalysis: {
|
|
715
900
|
representedTerms,
|
|
716
901
|
otherResultTerms,
|
|
717
902
|
unmatchedTerms,
|
|
718
|
-
...(queryMetadataTruncated
|
|
719
|
-
? { truncated: true }
|
|
720
|
-
: {}),
|
|
903
|
+
...(queryMetadataTruncated ? { truncated: true } : {}),
|
|
721
904
|
...(args.connector ? { connectorScope: args.connector } : {}),
|
|
722
905
|
...(args.connector && !scopedConnector
|
|
723
|
-
? {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
906
|
+
? {
|
|
907
|
+
unknownConnector: true,
|
|
908
|
+
configuredConnectors: this.registry.listConnectors().map((connector) => connector.id),
|
|
909
|
+
}
|
|
727
910
|
: {}),
|
|
911
|
+
...(unavailableCatalogs > 0 ? { unavailableConnectorCount: unavailableCatalogs } : {}),
|
|
728
912
|
...(scopedCatalogError ? { catalogError: scopedCatalogError } : {}),
|
|
729
913
|
...(scopedGuide
|
|
730
914
|
? {
|
|
731
915
|
guide: scopedGuide.guide,
|
|
732
|
-
...(scopedGuide.guideSummary
|
|
733
|
-
? { guideSummary: scopedGuide.guideSummary }
|
|
734
|
-
: {}),
|
|
916
|
+
...(scopedGuide.guideSummary ? { guideSummary: scopedGuide.guideSummary } : {}),
|
|
735
917
|
...(scopedGuide.required
|
|
736
918
|
? {
|
|
737
919
|
guideRequired: true,
|
|
738
|
-
guideRequiredReasons: [
|
|
739
|
-
"connector_required",
|
|
740
|
-
],
|
|
920
|
+
guideRequiredReasons: ["connector_required"],
|
|
741
921
|
}
|
|
742
922
|
: {}),
|
|
743
923
|
}
|
|
@@ -756,9 +936,7 @@ export class CatalogService {
|
|
|
756
936
|
return { address, resolved: this.registry.resolveAddress(address) };
|
|
757
937
|
});
|
|
758
938
|
const connectorIds = [
|
|
759
|
-
...new Set(resolved
|
|
760
|
-
.map((entry) => entry.resolved?.connector.id)
|
|
761
|
-
.filter((id) => Boolean(id))),
|
|
939
|
+
...new Set(resolved.map((entry) => entry.resolved?.connector.id).filter((id) => Boolean(id))),
|
|
762
940
|
];
|
|
763
941
|
const loaded = await runEdge(this.discoveryCatalogs(connectorIds, "connecta.describe"));
|
|
764
942
|
const catalogs = new Map(connectorIds.map((id, index) => [id, loaded[index]]));
|
|
@@ -770,6 +948,7 @@ export class CatalogService {
|
|
|
770
948
|
error: message,
|
|
771
949
|
errorDetails: {
|
|
772
950
|
...framingError("unknown_address", message),
|
|
951
|
+
configuredConnectors: this.registry.listConnectors().map((connector) => connector.id),
|
|
773
952
|
nextAction: this.searchRecovery({ query: recoveryQuery(address) }, "Find the configured canonical address before retrying."),
|
|
774
953
|
},
|
|
775
954
|
};
|
|
@@ -785,9 +964,7 @@ export class CatalogService {
|
|
|
785
964
|
code: classified.code,
|
|
786
965
|
message,
|
|
787
966
|
retryable: classified.retryable,
|
|
788
|
-
...(classified.retryAfterMs === undefined
|
|
789
|
-
? {}
|
|
790
|
-
: { retryAfterMs: classified.retryAfterMs }),
|
|
967
|
+
...(classified.retryAfterMs === undefined ? {} : { retryAfterMs: classified.retryAfterMs }),
|
|
791
968
|
},
|
|
792
969
|
};
|
|
793
970
|
}
|
|
@@ -818,16 +995,15 @@ export class CatalogService {
|
|
|
818
995
|
description: true,
|
|
819
996
|
})
|
|
820
997
|
: undefined;
|
|
821
|
-
const compactInput = format === "compact"
|
|
822
|
-
|
|
823
|
-
const compactOutput = format === "compact" && output.schema
|
|
824
|
-
? compactDescriptionSchema(output.schema) : undefined;
|
|
998
|
+
const compactInput = format === "compact" ? compactDescriptionSchema(input) : undefined;
|
|
999
|
+
const compactOutput = format === "compact" && output.schema ? compactDescriptionSchema(output.schema) : undefined;
|
|
825
1000
|
const inputTruncated = compactInput?.truncated === true || signature?.inputTruncated === true;
|
|
826
1001
|
const outputTruncated = compactOutput?.truncated === true || signature?.outputTruncated === true;
|
|
827
1002
|
const requiredReasons = guideRequiredReasons(addressResolution.connector, tool, inputTruncated || outputTruncated);
|
|
828
1003
|
const guideSummary = connectorGuideSummary(addressResolution.connector);
|
|
829
1004
|
return {
|
|
830
1005
|
address,
|
|
1006
|
+
schemaFormat: format === "json" ? "json" : "text",
|
|
831
1007
|
name: tool.name,
|
|
832
1008
|
...(description !== undefined ? { description } : {}),
|
|
833
1009
|
...(connectorGuide(addressResolution.connector)
|
|
@@ -842,9 +1018,7 @@ export class CatalogService {
|
|
|
842
1018
|
guideRequiredReasons: requiredReasons,
|
|
843
1019
|
}
|
|
844
1020
|
: {}),
|
|
845
|
-
...(signature
|
|
846
|
-
? { signature: signature.text }
|
|
847
|
-
: { inputSchema: compactInput?.text ?? input }),
|
|
1021
|
+
...(signature ? { signature: signature.text } : { inputSchema: compactInput?.text ?? input }),
|
|
848
1022
|
...(inputTruncated ? { inputSchemaTruncated: true } : {}),
|
|
849
1023
|
...(outputTruncated ? { outputSchemaTruncated: true } : {}),
|
|
850
1024
|
...(output.schema && !signature
|
|
@@ -854,9 +1028,7 @@ export class CatalogService {
|
|
|
854
1028
|
: {}),
|
|
855
1029
|
...(output.source ? { outputSchemaSource: output.source } : {}),
|
|
856
1030
|
...(tool.annotations ? { annotations: tool.annotations } : {}),
|
|
857
|
-
|
|
858
|
-
? { approval: "exempt" }
|
|
859
|
-
: {}),
|
|
1031
|
+
classification: tool.classification,
|
|
860
1032
|
};
|
|
861
1033
|
});
|
|
862
1034
|
}
|
|
@@ -872,45 +1044,17 @@ function pageTail(page) {
|
|
|
872
1044
|
...(page.queryAnalysis ? { queryAnalysis: page.queryAnalysis } : {}),
|
|
873
1045
|
};
|
|
874
1046
|
}
|
|
875
|
-
export function groupedSearchResult(page) {
|
|
876
|
-
const groups = [];
|
|
877
|
-
const byConnector = new Map();
|
|
878
|
-
for (const entry of page.entries) {
|
|
879
|
-
const existing = byConnector.get(entry.connector.id);
|
|
880
|
-
if (existing) {
|
|
881
|
-
existing.tools.push(entry.tool);
|
|
882
|
-
}
|
|
883
|
-
else {
|
|
884
|
-
const group = {
|
|
885
|
-
id: entry.connector.id,
|
|
886
|
-
...(entry.connector.title ? { title: entry.connector.title } : {}),
|
|
887
|
-
...(entry.guide ? { guide: entry.guide } : {}),
|
|
888
|
-
...(entry.guideSummary
|
|
889
|
-
? { guideSummary: entry.guideSummary }
|
|
890
|
-
: {}),
|
|
891
|
-
tools: [],
|
|
892
|
-
};
|
|
893
|
-
byConnector.set(entry.connector.id, group);
|
|
894
|
-
groups.push(group);
|
|
895
|
-
group.tools.push(entry.tool);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
return {
|
|
899
|
-
connectors: groups,
|
|
900
|
-
...pageTail(page),
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
1047
|
export function flatSearchResult(page) {
|
|
904
1048
|
return {
|
|
1049
|
+
catalogErrors: page.catalogErrors,
|
|
1050
|
+
...(page.absence ? { absence: page.absence } : {}),
|
|
905
1051
|
tools: page.entries.map((entry) => ({
|
|
906
1052
|
...entry.tool,
|
|
907
1053
|
...(entry.connector.title
|
|
908
1054
|
? { connectorTitle: boundedEchoText(entry.connector.title.replace(/\s+/g, " ").trim(), 117) }
|
|
909
1055
|
: {}),
|
|
910
1056
|
...(entry.guide ? { guide: entry.guide } : {}),
|
|
911
|
-
...(entry.guideSummary
|
|
912
|
-
? { guideSummary: entry.guideSummary }
|
|
913
|
-
: {}),
|
|
1057
|
+
...(entry.guideSummary ? { guideSummary: entry.guideSummary } : {}),
|
|
914
1058
|
})),
|
|
915
1059
|
...pageTail(page),
|
|
916
1060
|
};
|