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