@zackbart/connecta 0.15.1 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +40 -20
- package/CHANGELOG.md +653 -0
- package/README.md +25 -5
- package/bin/connecta.mjs +44 -3
- package/dist/access-tokens.d.ts +0 -1
- package/dist/access-tokens.js +0 -1
- package/dist/activity.d.ts +50 -2
- package/dist/activity.js +34 -1
- package/dist/apps-shell.d.ts +0 -1
- package/dist/apps-shell.js +0 -1
- package/dist/auth/bearer.d.ts +0 -1
- package/dist/auth/bearer.js +0 -1
- package/dist/auth/clerk.d.ts +0 -1
- package/dist/auth/clerk.js +45 -9
- package/dist/auth/downstream-oauth.d.ts +0 -1
- package/dist/auth/downstream-oauth.js +0 -1
- package/dist/call-admission.d.ts +0 -1
- package/dist/call-admission.js +0 -1
- package/dist/catalog-drift.d.ts +99 -0
- package/dist/catalog-drift.js +251 -0
- package/dist/catalog-fingerprint.d.ts +0 -1
- package/dist/catalog-fingerprint.js +0 -1
- package/dist/catalog-limits.d.ts +0 -1
- package/dist/catalog-limits.js +0 -1
- package/dist/catalog-service.d.ts +0 -1
- package/dist/catalog-service.js +41 -5
- package/dist/catalog.d.ts +11 -1
- package/dist/catalog.js +20 -1
- package/dist/concurrency.d.ts +0 -1
- package/dist/concurrency.js +0 -1
- package/dist/connector-scope.d.ts +0 -1
- package/dist/connector-scope.js +0 -1
- package/dist/connectors/api.d.ts +29 -21
- package/dist/connectors/api.js +45 -19
- package/dist/connectors/guarded-fetch.d.ts +106 -0
- package/dist/connectors/guarded-fetch.js +289 -0
- package/dist/connectors/remote-mcp.d.ts +0 -1
- package/dist/connectors/remote-mcp.js +0 -1
- package/dist/credentials.d.ts +0 -1
- package/dist/credentials.js +0 -1
- package/dist/errors.d.ts +30 -3
- package/dist/errors.js +1 -1
- package/dist/execute.d.ts +0 -1
- package/dist/execute.js +0 -1
- package/dist/executor-admission.d.ts +11 -1
- package/dist/executor-admission.js +28 -1
- package/dist/executor-result.d.ts +0 -1
- package/dist/executor-result.js +0 -1
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-child.js +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -1
- package/dist/executors/quickjs-protocol.js +0 -1
- package/dist/executors/quickjs-runtime.d.ts +0 -1
- package/dist/executors/quickjs-runtime.js +0 -1
- package/dist/executors/quickjs.d.ts +0 -1
- package/dist/executors/quickjs.js +2 -1
- package/dist/favicon.d.ts +0 -1
- package/dist/favicon.js +0 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +24 -7
- package/dist/invocation.d.ts +0 -1
- package/dist/invocation.js +0 -1
- package/dist/json-schema.d.ts +0 -1
- package/dist/json-schema.js +0 -1
- package/dist/mcp-result.d.ts +0 -1
- package/dist/mcp-result.js +0 -1
- package/dist/meta-tools.d.ts +0 -1
- package/dist/meta-tools.js +0 -1
- package/dist/node.d.ts +0 -1
- package/dist/node.js +0 -1
- package/dist/operator-ui/generated.d.ts +2 -3
- package/dist/operator-ui/generated.js +3 -4
- package/dist/operator-ui/model.d.ts +10 -1
- package/dist/operator-ui/model.js +0 -1
- package/dist/operator-ui/view.d.ts +167 -0
- package/dist/operator-ui/view.js +270 -0
- package/dist/providers/cloudflare.d.ts +0 -1
- package/dist/providers/cloudflare.js +261 -299
- package/dist/providers/linear.d.ts +26 -5
- package/dist/providers/linear.js +32 -61
- package/dist/providers/mixpanel.d.ts +35 -4
- package/dist/providers/mixpanel.js +53 -79
- package/dist/providers/notion.d.ts +0 -1
- package/dist/providers/notion.js +87 -42
- package/dist/providers/stripe.d.ts +13 -1
- package/dist/providers/stripe.js +31 -60
- package/dist/registry.d.ts +34 -2
- package/dist/registry.js +85 -5
- package/dist/retired-toolkits.d.ts +0 -1
- package/dist/retired-toolkits.js +0 -1
- package/dist/routes/access-tokens.d.ts +0 -1
- package/dist/routes/access-tokens.js +0 -1
- package/dist/routes/activity.d.ts +0 -1
- package/dist/routes/activity.js +0 -1
- package/dist/routes/credentials.d.ts +0 -1
- package/dist/routes/credentials.js +0 -1
- package/dist/routes/mcp.d.ts +0 -1
- package/dist/routes/mcp.js +0 -1
- package/dist/routes/oauth.d.ts +0 -1
- package/dist/routes/oauth.js +0 -1
- package/dist/routes/shared.d.ts +2 -1
- package/dist/routes/shared.js +0 -1
- package/dist/routes/ui.d.ts +0 -1
- package/dist/routes/ui.js +0 -1
- package/dist/server.d.ts +0 -1
- package/dist/server.js +11 -1
- package/dist/skills.d.ts +0 -1
- package/dist/skills.js +0 -1
- package/dist/storage/file.d.ts +0 -1
- package/dist/storage/file.js +0 -1
- package/dist/storage/memory.d.ts +0 -1
- package/dist/storage/memory.js +0 -1
- package/dist/timeout.d.ts +0 -1
- package/dist/timeout.js +0 -1
- package/dist/tool-safety.d.ts +0 -1
- package/dist/tool-safety.js +0 -1
- package/dist/types.d.ts +45 -1
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +0 -1
- package/dist/ui.js +13 -137
- package/dist/validate.d.ts +13 -15
- package/dist/validate.js +24 -11
- package/dist/version.d.ts +1 -2
- package/dist/version.js +1 -2
- package/documentation/architecture.md +186 -5
- package/documentation/auth.md +11 -0
- package/documentation/call-admission.md +142 -6
- package/documentation/cloudflare.md +92 -10
- package/documentation/code-first-exploration.md +2 -2
- package/documentation/code-mode.md +14 -14
- package/documentation/connector-guides.md +172 -5
- package/documentation/connectors.md +89 -0
- package/documentation/linear.md +20 -2
- package/documentation/mcp-ui-design.md +2 -2
- package/documentation/meta-tools.md +7 -1
- package/documentation/mixpanel.md +42 -9
- package/documentation/notion.md +23 -2
- package/documentation/operations.md +325 -5
- package/documentation/operator-ui.md +120 -5
- package/documentation/provider-audit.md +160 -0
- package/documentation/provider-conventions.md +667 -0
- package/documentation/request-admission.md +167 -5
- package/documentation/rich-output-design.md +1 -2
- package/documentation/stripe.md +15 -0
- package/documentation/upgrading.md +454 -0
- package/ethos.md +14 -9
- package/examples/worker/README.md +102 -12
- package/examples/worker/src/index.ts +46 -1
- package/examples/worker/wrangler.jsonc +14 -1
- package/package.json +10 -6
- package/templates/node/.dockerignore +10 -0
- package/templates/node/.env.example +40 -3
- package/templates/node/AGENTS.md +16 -1
- package/templates/node/Dockerfile +49 -0
- package/templates/node/README.md +117 -2
- package/templates/node/docker-compose.yml +39 -0
- package/templates/node/package.json +1 -1
- package/templates/node/src/file-activity.ts +182 -0
- package/templates/node/src/index.ts +80 -4
- package/assets/connecta-clay-hero.png +0 -0
- package/dist/access-tokens.d.ts.map +0 -1
- package/dist/access-tokens.js.map +0 -1
- package/dist/activity.d.ts.map +0 -1
- package/dist/activity.js.map +0 -1
- package/dist/apps-shell.d.ts.map +0 -1
- package/dist/apps-shell.js.map +0 -1
- package/dist/auth/bearer.d.ts.map +0 -1
- package/dist/auth/bearer.js.map +0 -1
- package/dist/auth/clerk.d.ts.map +0 -1
- package/dist/auth/clerk.js.map +0 -1
- package/dist/auth/downstream-oauth.d.ts.map +0 -1
- package/dist/auth/downstream-oauth.js.map +0 -1
- package/dist/call-admission.d.ts.map +0 -1
- package/dist/call-admission.js.map +0 -1
- package/dist/catalog-fingerprint.d.ts.map +0 -1
- package/dist/catalog-fingerprint.js.map +0 -1
- package/dist/catalog-limits.d.ts.map +0 -1
- package/dist/catalog-limits.js.map +0 -1
- package/dist/catalog-service.d.ts.map +0 -1
- package/dist/catalog-service.js.map +0 -1
- package/dist/catalog.d.ts.map +0 -1
- package/dist/catalog.js.map +0 -1
- package/dist/concurrency.d.ts.map +0 -1
- package/dist/concurrency.js.map +0 -1
- package/dist/connector-scope.d.ts.map +0 -1
- package/dist/connector-scope.js.map +0 -1
- package/dist/connectors/api.d.ts.map +0 -1
- package/dist/connectors/api.js.map +0 -1
- package/dist/connectors/remote-mcp.d.ts.map +0 -1
- package/dist/connectors/remote-mcp.js.map +0 -1
- package/dist/credentials.d.ts.map +0 -1
- package/dist/credentials.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/execute.d.ts.map +0 -1
- package/dist/execute.js.map +0 -1
- package/dist/executor-admission.d.ts.map +0 -1
- package/dist/executor-admission.js.map +0 -1
- package/dist/executor-result.d.ts.map +0 -1
- package/dist/executor-result.js.map +0 -1
- package/dist/executors/quickjs-child.d.ts.map +0 -1
- package/dist/executors/quickjs-child.js.map +0 -1
- package/dist/executors/quickjs-protocol.d.ts.map +0 -1
- package/dist/executors/quickjs-protocol.js.map +0 -1
- package/dist/executors/quickjs-runtime.d.ts.map +0 -1
- package/dist/executors/quickjs-runtime.js.map +0 -1
- package/dist/executors/quickjs.d.ts.map +0 -1
- package/dist/executors/quickjs.js.map +0 -1
- package/dist/favicon.d.ts.map +0 -1
- package/dist/favicon.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/invocation.d.ts.map +0 -1
- package/dist/invocation.js.map +0 -1
- package/dist/json-schema.d.ts.map +0 -1
- package/dist/json-schema.js.map +0 -1
- package/dist/mcp-result.d.ts.map +0 -1
- package/dist/mcp-result.js.map +0 -1
- package/dist/meta-tools.d.ts.map +0 -1
- package/dist/meta-tools.js.map +0 -1
- package/dist/node.d.ts.map +0 -1
- package/dist/node.js.map +0 -1
- package/dist/operator-ui/generated.d.ts.map +0 -1
- package/dist/operator-ui/generated.js.map +0 -1
- package/dist/operator-ui/model.d.ts.map +0 -1
- package/dist/operator-ui/model.js.map +0 -1
- package/dist/providers/cloudflare.d.ts.map +0 -1
- package/dist/providers/cloudflare.js.map +0 -1
- package/dist/providers/linear.d.ts.map +0 -1
- package/dist/providers/linear.js.map +0 -1
- package/dist/providers/mixpanel.d.ts.map +0 -1
- package/dist/providers/mixpanel.js.map +0 -1
- package/dist/providers/notion.d.ts.map +0 -1
- package/dist/providers/notion.js.map +0 -1
- package/dist/providers/stripe.d.ts.map +0 -1
- package/dist/providers/stripe.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/retired-toolkits.d.ts.map +0 -1
- package/dist/retired-toolkits.js.map +0 -1
- package/dist/routes/access-tokens.d.ts.map +0 -1
- package/dist/routes/access-tokens.js.map +0 -1
- package/dist/routes/activity.d.ts.map +0 -1
- package/dist/routes/activity.js.map +0 -1
- package/dist/routes/credentials.d.ts.map +0 -1
- package/dist/routes/credentials.js.map +0 -1
- package/dist/routes/mcp.d.ts.map +0 -1
- package/dist/routes/mcp.js.map +0 -1
- package/dist/routes/oauth.d.ts.map +0 -1
- package/dist/routes/oauth.js.map +0 -1
- package/dist/routes/shared.d.ts.map +0 -1
- package/dist/routes/shared.js.map +0 -1
- package/dist/routes/ui.d.ts.map +0 -1
- package/dist/routes/ui.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js.map +0 -1
- package/dist/storage/file.d.ts.map +0 -1
- package/dist/storage/file.js.map +0 -1
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js.map +0 -1
- package/dist/timeout.d.ts.map +0 -1
- package/dist/timeout.js.map +0 -1
- package/dist/tool-safety.d.ts.map +0 -1
- package/dist/tool-safety.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/ui.d.ts.map +0 -1
- package/dist/ui.js.map +0 -1
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
- package/examples/node/README.md +0 -52
- package/examples/node/src/index.ts +0 -71
- package/src/access-tokens.ts +0 -289
- package/src/activity.ts +0 -234
- package/src/apps-shell.ts +0 -370
- package/src/auth/bearer.ts +0 -61
- package/src/auth/clerk.ts +0 -553
- package/src/auth/downstream-oauth.ts +0 -575
- package/src/call-admission.ts +0 -521
- package/src/catalog-fingerprint.ts +0 -52
- package/src/catalog-limits.ts +0 -11
- package/src/catalog-service.ts +0 -1153
- package/src/catalog.ts +0 -790
- package/src/concurrency.ts +0 -39
- package/src/connector-scope.ts +0 -66
- package/src/connectors/api.ts +0 -159
- package/src/connectors/remote-mcp.ts +0 -1090
- package/src/credentials.ts +0 -428
- package/src/errors.ts +0 -364
- package/src/execute.ts +0 -1406
- package/src/executor-admission.ts +0 -328
- package/src/executor-result.ts +0 -107
- package/src/executors/quickjs-child.ts +0 -172
- package/src/executors/quickjs-protocol.ts +0 -99
- package/src/executors/quickjs-runtime.ts +0 -470
- package/src/executors/quickjs.ts +0 -776
- package/src/favicon.ts +0 -53
- package/src/index.ts +0 -666
- package/src/invocation.ts +0 -655
- package/src/json-schema.ts +0 -11
- package/src/mcp-result.ts +0 -43
- package/src/meta-tools.ts +0 -1607
- package/src/node.ts +0 -206
- package/src/operator-ui/browser.css +0 -423
- package/src/operator-ui/browser.ts +0 -1281
- package/src/operator-ui/generated.ts +0 -4
- package/src/operator-ui/model.ts +0 -111
- package/src/providers/cloudflare.ts +0 -3999
- package/src/providers/linear.ts +0 -302
- package/src/providers/mixpanel.ts +0 -229
- package/src/providers/notion.ts +0 -1879
- package/src/providers/stripe.ts +0 -307
- package/src/registry.ts +0 -1085
- package/src/retired-toolkits.ts +0 -14
- package/src/routes/access-tokens.ts +0 -115
- package/src/routes/activity.ts +0 -215
- package/src/routes/credentials.ts +0 -254
- package/src/routes/mcp.ts +0 -429
- package/src/routes/oauth.ts +0 -322
- package/src/routes/shared.ts +0 -247
- package/src/routes/ui.ts +0 -152
- package/src/server.ts +0 -195
- package/src/skills.ts +0 -271
- package/src/storage/file.ts +0 -123
- package/src/storage/memory.ts +0 -44
- package/src/timeout.ts +0 -84
- package/src/tool-safety.ts +0 -15
- package/src/types.ts +0 -523
- package/src/ui.ts +0 -744
- package/src/validate.ts +0 -387
- package/src/version.ts +0 -7
package/src/catalog-service.ts
DELETED
|
@@ -1,1153 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
compactDiscoverySchema,
|
|
3
|
-
compactSchema,
|
|
4
|
-
lexicalCorpusStatistics,
|
|
5
|
-
lexicalQueryTerms,
|
|
6
|
-
lexicalSearchQuery,
|
|
7
|
-
rankTools,
|
|
8
|
-
schemaObjectKeys,
|
|
9
|
-
summarizeDiscoveryDescription,
|
|
10
|
-
summarizeDescription,
|
|
11
|
-
} from "./catalog.js";
|
|
12
|
-
import {
|
|
13
|
-
mapSettledWithConcurrency,
|
|
14
|
-
resolveDiscoveryConcurrency,
|
|
15
|
-
} from "./concurrency.js";
|
|
16
|
-
import {
|
|
17
|
-
boundedEchoText,
|
|
18
|
-
classifyCallError,
|
|
19
|
-
framingError,
|
|
20
|
-
} from "./errors.js";
|
|
21
|
-
import type { CallErrorDetails } from "./errors.js";
|
|
22
|
-
import type {
|
|
23
|
-
ConnectorOperationOptions,
|
|
24
|
-
RegistryView,
|
|
25
|
-
} from "./registry.js";
|
|
26
|
-
import {
|
|
27
|
-
connectorGuide,
|
|
28
|
-
connectorGuideRequired,
|
|
29
|
-
connectorGuideSummary,
|
|
30
|
-
connectorSkillName,
|
|
31
|
-
} from "./skills.js";
|
|
32
|
-
import {
|
|
33
|
-
DEFAULT_PROBE_TIMEOUT_MS,
|
|
34
|
-
normalizeTimeoutMs,
|
|
35
|
-
withAbortableTimeout,
|
|
36
|
-
} from "./timeout.js";
|
|
37
|
-
import { isExplicitlyReadOnly } from "./tool-safety.js";
|
|
38
|
-
import type {
|
|
39
|
-
Connector,
|
|
40
|
-
JsonSchema,
|
|
41
|
-
ToolDef,
|
|
42
|
-
} from "./types.js";
|
|
43
|
-
|
|
44
|
-
export const DEFAULT_SEARCH_LIMIT = 8;
|
|
45
|
-
export const MAX_SEARCH_LIMIT = 100;
|
|
46
|
-
export const MAX_DESCRIBE_ADDRESSES = 100;
|
|
47
|
-
export const MAX_DISCOVERY_RESULT_BYTES = 256_000;
|
|
48
|
-
const MAX_QUERY_TERMS = 8;
|
|
49
|
-
const MAX_QUERY_TERM_LENGTH = 64;
|
|
50
|
-
|
|
51
|
-
const encoder = new TextEncoder();
|
|
52
|
-
|
|
53
|
-
/** Clip one echoed query term without splitting a non-BMP code point. */
|
|
54
|
-
function boundedQueryTerm(term: string): {
|
|
55
|
-
text: string;
|
|
56
|
-
truncated: boolean;
|
|
57
|
-
} {
|
|
58
|
-
const characters: string[] = [];
|
|
59
|
-
for (const character of term) {
|
|
60
|
-
characters.push(character);
|
|
61
|
-
if (characters.length > MAX_QUERY_TERM_LENGTH) {
|
|
62
|
-
return {
|
|
63
|
-
text: `${characters.slice(0, MAX_QUERY_TERM_LENGTH - 1).join("")}…`,
|
|
64
|
-
truncated: true,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return { text: characters.join(""), truncated: false };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* The discovery route a routing failure should send a caller back through. Same
|
|
73
|
-
* catalog logic serves both the top-level `search_tools` path and the
|
|
74
|
-
* in-program `connecta.search` path, so callers pass the route they own.
|
|
75
|
-
*/
|
|
76
|
-
export type SearchRoute = "search_tools" | "connecta.search";
|
|
77
|
-
|
|
78
|
-
export class DiscoveryPolicyError extends Error {
|
|
79
|
-
constructor(
|
|
80
|
-
readonly code: "invalid_args" | "result_too_large",
|
|
81
|
-
message: string,
|
|
82
|
-
) {
|
|
83
|
-
super(message);
|
|
84
|
-
this.name = "DiscoveryPolicyError";
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Validate before ranking so a huge page request does no proportional work. */
|
|
89
|
-
function discoverySearchLimit(value: unknown): number {
|
|
90
|
-
if (value === undefined) return DEFAULT_SEARCH_LIMIT;
|
|
91
|
-
if (
|
|
92
|
-
typeof value !== "number" ||
|
|
93
|
-
!Number.isInteger(value) ||
|
|
94
|
-
value < 1 ||
|
|
95
|
-
value > MAX_SEARCH_LIMIT
|
|
96
|
-
) {
|
|
97
|
-
throw new DiscoveryPolicyError(
|
|
98
|
-
"invalid_args",
|
|
99
|
-
`limit must be a whole number from 1 through ${MAX_SEARCH_LIMIT}. Page through larger catalogs with offset.`,
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
return value;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** Normalize the single-address convenience form, then validate the bounded list. */
|
|
106
|
-
function discoveryAddresses(args: CatalogDescribeArgs): unknown[] {
|
|
107
|
-
if (args.address !== undefined && args.addresses !== undefined) {
|
|
108
|
-
throw new DiscoveryPolicyError(
|
|
109
|
-
"invalid_args",
|
|
110
|
-
"describe takes either address or addresses, not both.",
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
const value =
|
|
114
|
-
args.address !== undefined
|
|
115
|
-
? typeof args.address === "string"
|
|
116
|
-
? [args.address]
|
|
117
|
-
: undefined
|
|
118
|
-
: args.addresses;
|
|
119
|
-
if (!Array.isArray(value)) {
|
|
120
|
-
throw new DiscoveryPolicyError(
|
|
121
|
-
"invalid_args",
|
|
122
|
-
'describe takes { address: "<connectorId>.<toolName>" } or { addresses: ["<connectorId>.<toolName>", ...] }.',
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
if (value.length > MAX_DESCRIBE_ADDRESSES) {
|
|
126
|
-
throw new DiscoveryPolicyError(
|
|
127
|
-
"invalid_args",
|
|
128
|
-
`addresses must contain at most ${MAX_DESCRIBE_ADDRESSES} entries. Split a larger list across connecta.describe calls.`,
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
return value;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Search terms derived from an address the catalog could not resolve. Bounded
|
|
136
|
-
* because the address is entirely caller-authored: an invented one can be any
|
|
137
|
-
* length, and this string is copied into a recovery record that is itself
|
|
138
|
-
* copied into both halves of the result envelope.
|
|
139
|
-
*/
|
|
140
|
-
function recoveryQuery(address: string): string {
|
|
141
|
-
const separator = address.indexOf(".");
|
|
142
|
-
const candidate = separator >= 0 ? address.slice(separator + 1) : address;
|
|
143
|
-
return boundedEchoText(
|
|
144
|
-
candidate.replaceAll(/[._-]+/g, " ").trim() || address,
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** Serialize once and count the exact bytes the MCP adapter would emit. */
|
|
149
|
-
export function boundedDiscoveryText(value: unknown, hint: string): string {
|
|
150
|
-
const text = JSON.stringify(value);
|
|
151
|
-
if (text === undefined) {
|
|
152
|
-
throw new TypeError("Discovery result is not JSON-serializable.");
|
|
153
|
-
}
|
|
154
|
-
const bytes = encoder.encode(text).length;
|
|
155
|
-
if (bytes > MAX_DISCOVERY_RESULT_BYTES) {
|
|
156
|
-
throw new DiscoveryPolicyError(
|
|
157
|
-
"result_too_large",
|
|
158
|
-
`Discovery result is ${bytes} UTF-8 bytes, over the ${MAX_DISCOVERY_RESULT_BYTES}-byte ceiling. ${hint}`,
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
return text;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export interface CatalogSearchArgs {
|
|
165
|
-
query?: string;
|
|
166
|
-
connector?: string;
|
|
167
|
-
/**
|
|
168
|
-
* Result classification only; never changes which tools exist or what may
|
|
169
|
-
* execute. Omitted and "all" preserve the complete configured catalog.
|
|
170
|
-
*/
|
|
171
|
-
safety?: "readOnly" | "approvalRequired" | "all";
|
|
172
|
-
limit?: number;
|
|
173
|
-
offset?: number;
|
|
174
|
-
fullDescriptions?: boolean;
|
|
175
|
-
includeSchemas?: "compact" | "json";
|
|
176
|
-
/** Code-mode helper metadata; never exposed by the public search_tools schema. */
|
|
177
|
-
includeSchemaKeys?: boolean;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function discoverySafety(
|
|
181
|
-
value: unknown,
|
|
182
|
-
): "readOnly" | "approvalRequired" | "all" {
|
|
183
|
-
if (value === undefined) return "all";
|
|
184
|
-
if (
|
|
185
|
-
value !== "readOnly" &&
|
|
186
|
-
value !== "approvalRequired" &&
|
|
187
|
-
value !== "all"
|
|
188
|
-
) {
|
|
189
|
-
throw new DiscoveryPolicyError(
|
|
190
|
-
"invalid_args",
|
|
191
|
-
'safety must be "readOnly", "approvalRequired", or "all".',
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
return value;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function toolsForSafety(
|
|
198
|
-
tools: ToolDef[],
|
|
199
|
-
safety: "readOnly" | "approvalRequired" | "all",
|
|
200
|
-
): ToolDef[] {
|
|
201
|
-
if (safety === "all") return tools;
|
|
202
|
-
return tools.filter((tool) =>
|
|
203
|
-
safety === "readOnly"
|
|
204
|
-
? isExplicitlyReadOnly(tool)
|
|
205
|
-
: !isExplicitlyReadOnly(tool),
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export interface CatalogDescribeArgs {
|
|
210
|
-
address?: unknown;
|
|
211
|
-
addresses?: unknown;
|
|
212
|
-
format?: "compact" | "json";
|
|
213
|
-
fullDescriptions?: boolean;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
interface CatalogSearchEntry {
|
|
217
|
-
connector: Connector;
|
|
218
|
-
guide?: string;
|
|
219
|
-
guideSummary?: string;
|
|
220
|
-
tool: {
|
|
221
|
-
name: string;
|
|
222
|
-
address: string;
|
|
223
|
-
description?: string;
|
|
224
|
-
inputSchema?: unknown;
|
|
225
|
-
outputSchema?: unknown;
|
|
226
|
-
inputSchemaTruncated?: true;
|
|
227
|
-
outputSchemaTruncated?: true;
|
|
228
|
-
inputKeys?: string[];
|
|
229
|
-
requiredInputKeys?: string[];
|
|
230
|
-
outputKeys?: string[];
|
|
231
|
-
annotations?: ToolDef["annotations"];
|
|
232
|
-
guideRequired?: true;
|
|
233
|
-
guideRequiredReasons?: GuideRequiredReason[];
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
type GuideRequiredReason =
|
|
238
|
-
| "connector_required"
|
|
239
|
-
| "approval_required"
|
|
240
|
-
| "schema_truncated";
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Reasons discovery can determine without reading arguments or guessing at a
|
|
244
|
-
* task. Summary-only conventions remain an agent decision; hard requirements
|
|
245
|
-
* are explicit and machine-readable.
|
|
246
|
-
*/
|
|
247
|
-
function guideRequiredReasons(
|
|
248
|
-
connector: Connector,
|
|
249
|
-
tool: ToolDef,
|
|
250
|
-
schemaTruncated: boolean,
|
|
251
|
-
): GuideRequiredReason[] | undefined {
|
|
252
|
-
if (!connectorGuide(connector)) return undefined;
|
|
253
|
-
const reasons: GuideRequiredReason[] = [];
|
|
254
|
-
if (connectorGuideRequired(connector)) reasons.push("connector_required");
|
|
255
|
-
if (!isExplicitlyReadOnly(tool)) reasons.push("approval_required");
|
|
256
|
-
if (schemaTruncated) reasons.push("schema_truncated");
|
|
257
|
-
return reasons.length > 0 ? reasons : undefined;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Code-mode key metadata for one match. Each half is omitted when its schema
|
|
262
|
-
* does not resolve to an object shape, so a program reads "no metadata, use the
|
|
263
|
-
* rendered schema" rather than "this tool has no fields".
|
|
264
|
-
*/
|
|
265
|
-
function schemaKeyMetadata(
|
|
266
|
-
input: JsonSchema,
|
|
267
|
-
output: JsonSchema | undefined,
|
|
268
|
-
): {
|
|
269
|
-
inputKeys?: string[];
|
|
270
|
-
requiredInputKeys?: string[];
|
|
271
|
-
outputKeys?: string[];
|
|
272
|
-
} {
|
|
273
|
-
const inputKeys = schemaObjectKeys(input);
|
|
274
|
-
const outputKeys = schemaObjectKeys(output);
|
|
275
|
-
return {
|
|
276
|
-
...(inputKeys
|
|
277
|
-
? {
|
|
278
|
-
inputKeys: inputKeys.properties,
|
|
279
|
-
requiredInputKeys: inputKeys.required,
|
|
280
|
-
}
|
|
281
|
-
: {}),
|
|
282
|
-
...(outputKeys && outputKeys.properties.length > 0
|
|
283
|
-
? { outputKeys: outputKeys.properties }
|
|
284
|
-
: {}),
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* The classified-failure subset a scoped search may echo: enough to tell a
|
|
290
|
-
* transient outage from one an operator must clear, and nothing more. Kept as
|
|
291
|
-
* its own type rather than `CallErrorDetails` so widening the call-path
|
|
292
|
-
* classifier cannot widen this discovery-surface field by accident.
|
|
293
|
-
*/
|
|
294
|
-
interface CatalogFailureDetail {
|
|
295
|
-
code: string;
|
|
296
|
-
message: string;
|
|
297
|
-
retryable: boolean;
|
|
298
|
-
retryAfterMs?: number;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
export interface CatalogSearchPage {
|
|
302
|
-
entries: CatalogSearchEntry[];
|
|
303
|
-
total: number;
|
|
304
|
-
offset: number;
|
|
305
|
-
limit: number;
|
|
306
|
-
hasMore: boolean;
|
|
307
|
-
nextOffset?: number;
|
|
308
|
-
matchMode?: "partial";
|
|
309
|
-
queryAnalysis?: {
|
|
310
|
-
representedTerms: string[];
|
|
311
|
-
otherResultTerms: string[];
|
|
312
|
-
unmatchedTerms: string[];
|
|
313
|
-
truncated?: true;
|
|
314
|
-
connectorScope?: string;
|
|
315
|
-
unknownConnector?: true;
|
|
316
|
-
unavailableConnectorCount?: number;
|
|
317
|
-
/** Bounded typed failure for an explicitly scoped unavailable catalog. */
|
|
318
|
-
catalogError?: CatalogFailureDetail;
|
|
319
|
-
guide?: string;
|
|
320
|
-
guideSummary?: string;
|
|
321
|
-
guideRequired?: true;
|
|
322
|
-
guideRequiredReasons?: GuideRequiredReason[];
|
|
323
|
-
guidance?: string;
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
export interface CatalogDescription {
|
|
328
|
-
address: string;
|
|
329
|
-
name?: string;
|
|
330
|
-
description?: string;
|
|
331
|
-
guide?: string;
|
|
332
|
-
guideSummary?: string;
|
|
333
|
-
guideRequired?: true;
|
|
334
|
-
guideRequiredReasons?: GuideRequiredReason[];
|
|
335
|
-
inputSchema?: unknown;
|
|
336
|
-
outputSchema?: unknown;
|
|
337
|
-
annotations?: ToolDef["annotations"];
|
|
338
|
-
error?: string;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
export interface ResolvedCatalogTool {
|
|
342
|
-
connector: Connector;
|
|
343
|
-
toolName: string;
|
|
344
|
-
definition: ToolDef;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export type CatalogResolution =
|
|
348
|
-
| {
|
|
349
|
-
ok: true;
|
|
350
|
-
resolved: ResolvedCatalogTool;
|
|
351
|
-
catalogMs: number;
|
|
352
|
-
}
|
|
353
|
-
| {
|
|
354
|
-
ok: false;
|
|
355
|
-
error: CallErrorDetails;
|
|
356
|
-
catalogMs: number;
|
|
357
|
-
connector?: Connector;
|
|
358
|
-
toolName?: string;
|
|
359
|
-
cause?: unknown;
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
function renderSchema(schema: JsonSchema, format: "compact" | "json"): unknown {
|
|
363
|
-
return format === "json" ? schema : compactSchema(schema);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function renderSearchSchema(
|
|
367
|
-
schema: JsonSchema,
|
|
368
|
-
format: "compact" | "json",
|
|
369
|
-
): { schema: unknown; truncated: boolean } {
|
|
370
|
-
if (format === "json") return { schema, truncated: false };
|
|
371
|
-
const compact = compactDiscoverySchema(schema);
|
|
372
|
-
return { schema: compact.text, truncated: compact.truncated };
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Request-local catalog operations shared by MCP meta-tools and code mode.
|
|
377
|
-
* Successful catalogs may be reused inside this request; failures are not
|
|
378
|
-
* retained, and the service itself never escapes its request adapter.
|
|
379
|
-
*/
|
|
380
|
-
export class CatalogService {
|
|
381
|
-
readonly requestScope: object;
|
|
382
|
-
private readonly probeTimeoutMs: number;
|
|
383
|
-
private readonly concurrency: number;
|
|
384
|
-
private readonly searchRoute: SearchRoute;
|
|
385
|
-
private readonly loaded = new Map<string, ToolDef[]>();
|
|
386
|
-
private readonly loading = new Map<string, Promise<ToolDef[]>>();
|
|
387
|
-
|
|
388
|
-
constructor(
|
|
389
|
-
private readonly registry: RegistryView,
|
|
390
|
-
readonly baseUrl: string,
|
|
391
|
-
options: {
|
|
392
|
-
requestScope?: object;
|
|
393
|
-
probeTimeoutMs?: number;
|
|
394
|
-
concurrency?: number;
|
|
395
|
-
/** The discovery route recovery records name. Default `search_tools`. */
|
|
396
|
-
searchRoute?: SearchRoute;
|
|
397
|
-
} = {},
|
|
398
|
-
) {
|
|
399
|
-
this.requestScope = options.requestScope ?? {};
|
|
400
|
-
this.probeTimeoutMs =
|
|
401
|
-
normalizeTimeoutMs(options.probeTimeoutMs) ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
402
|
-
this.concurrency = resolveDiscoveryConcurrency(options.concurrency);
|
|
403
|
-
this.searchRoute = options.searchRoute ?? "search_tools";
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Send a caller back to discovery through the surface it can actually reach.
|
|
408
|
-
* Both variants carry the same scoping arguments because `connecta.search`
|
|
409
|
-
* takes the same ones `search_tools` does; only the key naming the callable
|
|
410
|
-
* differs, the way the ambiguous-alias record already names a function.
|
|
411
|
-
*
|
|
412
|
-
* Not private: `InvocationService` builds the same class of record when a
|
|
413
|
-
* call fails schema validation, and it is this catalog's route that decides
|
|
414
|
-
* which key that record carries. Duplicating the branch there would let the
|
|
415
|
-
* two drift.
|
|
416
|
-
*/
|
|
417
|
-
searchRecovery(
|
|
418
|
-
args: { query: string; connector?: string },
|
|
419
|
-
purpose: string,
|
|
420
|
-
): NonNullable<CallErrorDetails["nextAction"]> {
|
|
421
|
-
const searchArgs = {
|
|
422
|
-
query: args.query,
|
|
423
|
-
...(args.connector !== undefined ? { connector: args.connector } : {}),
|
|
424
|
-
includeSchemas: "compact" as const,
|
|
425
|
-
};
|
|
426
|
-
return this.searchRoute === "connecta.search"
|
|
427
|
-
? { function: "connecta.search", arguments: searchArgs, purpose }
|
|
428
|
-
: { tool: "search_tools", arguments: searchArgs, purpose };
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
async loadConnector(
|
|
432
|
-
id: string,
|
|
433
|
-
callOptions: ConnectorOperationOptions = {},
|
|
434
|
-
): Promise<ToolDef[]> {
|
|
435
|
-
const cached = this.loaded.get(id);
|
|
436
|
-
if (cached) return cached;
|
|
437
|
-
const inFlight = this.loading.get(id);
|
|
438
|
-
if (inFlight) return inFlight;
|
|
439
|
-
const loading = this.registry
|
|
440
|
-
.getTools(id, this.baseUrl, this.requestScope, callOptions)
|
|
441
|
-
.then((tools) => {
|
|
442
|
-
this.loaded.set(id, tools);
|
|
443
|
-
return tools;
|
|
444
|
-
})
|
|
445
|
-
.finally(() => {
|
|
446
|
-
if (this.loading.get(id) === loading) this.loading.delete(id);
|
|
447
|
-
});
|
|
448
|
-
this.loading.set(id, loading);
|
|
449
|
-
return loading;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
private loadForDiscovery(id: string, label: string): Promise<ToolDef[]> {
|
|
453
|
-
return withAbortableTimeout(
|
|
454
|
-
(signal) =>
|
|
455
|
-
this.loadConnector(id, {
|
|
456
|
-
signal,
|
|
457
|
-
timeoutMs: this.probeTimeoutMs,
|
|
458
|
-
}),
|
|
459
|
-
this.probeTimeoutMs,
|
|
460
|
-
label,
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
async resolveTool(
|
|
465
|
-
address: string,
|
|
466
|
-
callOptions: ConnectorOperationOptions = {},
|
|
467
|
-
): Promise<CatalogResolution> {
|
|
468
|
-
const resolved = this.registry.resolveAddress(address);
|
|
469
|
-
if (!resolved) {
|
|
470
|
-
return {
|
|
471
|
-
ok: false,
|
|
472
|
-
error: {
|
|
473
|
-
...framingError(
|
|
474
|
-
"unknown_address",
|
|
475
|
-
`Unknown address "${boundedEchoText(address)}"`,
|
|
476
|
-
),
|
|
477
|
-
nextAction: this.searchRecovery(
|
|
478
|
-
{ query: recoveryQuery(address) },
|
|
479
|
-
"Find the configured canonical address before retrying.",
|
|
480
|
-
),
|
|
481
|
-
},
|
|
482
|
-
catalogMs: 0,
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
const started = Date.now();
|
|
486
|
-
let tools: ToolDef[];
|
|
487
|
-
try {
|
|
488
|
-
tools = await this.loadConnector(resolved.connector.id, callOptions);
|
|
489
|
-
} catch (cause) {
|
|
490
|
-
return {
|
|
491
|
-
ok: false,
|
|
492
|
-
error: classifyCallError(cause, "catalog_lookup_failed"),
|
|
493
|
-
catalogMs: Date.now() - started,
|
|
494
|
-
connector: resolved.connector,
|
|
495
|
-
toolName: resolved.toolName,
|
|
496
|
-
cause,
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
const definition = tools.find((tool) => tool.name === resolved.toolName);
|
|
500
|
-
if (!definition) {
|
|
501
|
-
return {
|
|
502
|
-
ok: false,
|
|
503
|
-
error: {
|
|
504
|
-
...framingError(
|
|
505
|
-
"unknown_tool",
|
|
506
|
-
`Unknown tool "${boundedEchoText(resolved.toolName)}" on connector "${resolved.connector.id}"`,
|
|
507
|
-
),
|
|
508
|
-
nextAction: this.searchRecovery(
|
|
509
|
-
{
|
|
510
|
-
query: recoveryQuery(resolved.toolName),
|
|
511
|
-
connector: resolved.connector.id,
|
|
512
|
-
},
|
|
513
|
-
"Find the connector's current canonical tool address.",
|
|
514
|
-
),
|
|
515
|
-
},
|
|
516
|
-
catalogMs: Date.now() - started,
|
|
517
|
-
connector: resolved.connector,
|
|
518
|
-
toolName: resolved.toolName,
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
return {
|
|
522
|
-
ok: true,
|
|
523
|
-
resolved: {
|
|
524
|
-
connector: resolved.connector,
|
|
525
|
-
toolName: resolved.toolName,
|
|
526
|
-
definition,
|
|
527
|
-
},
|
|
528
|
-
catalogMs: Date.now() - started,
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* Resolve the JavaScript-safe property used by a lazy code-mode namespace
|
|
534
|
-
* back to exactly one catalog tool. Ambiguous aliases fail with an explicit
|
|
535
|
-
* escape hatch instead of silently choosing the first tool.
|
|
536
|
-
*/
|
|
537
|
-
async resolveToolAlias(
|
|
538
|
-
connectorId: string,
|
|
539
|
-
alias: string,
|
|
540
|
-
aliasFor: (toolName: string) => string,
|
|
541
|
-
callOptions: ConnectorOperationOptions = {},
|
|
542
|
-
): Promise<CatalogResolution> {
|
|
543
|
-
const connector = this.registry.getConnector(connectorId);
|
|
544
|
-
if (!connector) {
|
|
545
|
-
return {
|
|
546
|
-
ok: false,
|
|
547
|
-
error: {
|
|
548
|
-
...framingError(
|
|
549
|
-
"unknown_address",
|
|
550
|
-
`Unknown address "${boundedEchoText(`${connectorId}.${alias}`)}"`,
|
|
551
|
-
),
|
|
552
|
-
nextAction: this.searchRecovery(
|
|
553
|
-
{ query: recoveryQuery(alias) },
|
|
554
|
-
"Find the configured canonical address before retrying.",
|
|
555
|
-
),
|
|
556
|
-
},
|
|
557
|
-
catalogMs: 0,
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
const started = Date.now();
|
|
561
|
-
let tools: ToolDef[];
|
|
562
|
-
try {
|
|
563
|
-
tools = await this.loadConnector(connector.id, callOptions);
|
|
564
|
-
} catch (cause) {
|
|
565
|
-
return {
|
|
566
|
-
ok: false,
|
|
567
|
-
error: classifyCallError(cause, "catalog_lookup_failed"),
|
|
568
|
-
catalogMs: Date.now() - started,
|
|
569
|
-
connector,
|
|
570
|
-
toolName: alias,
|
|
571
|
-
cause,
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
const [definition, ...collisions] = tools.filter(
|
|
575
|
-
(tool) => aliasFor(tool.name) === alias,
|
|
576
|
-
);
|
|
577
|
-
if (!definition) {
|
|
578
|
-
return {
|
|
579
|
-
ok: false,
|
|
580
|
-
error: {
|
|
581
|
-
...framingError(
|
|
582
|
-
"unknown_tool",
|
|
583
|
-
`Unknown tool "${boundedEchoText(alias)}" on connector "${connector.id}"`,
|
|
584
|
-
),
|
|
585
|
-
nextAction: this.searchRecovery(
|
|
586
|
-
{ query: recoveryQuery(alias), connector: connector.id },
|
|
587
|
-
"Find the connector's current canonical tool address.",
|
|
588
|
-
),
|
|
589
|
-
},
|
|
590
|
-
catalogMs: Date.now() - started,
|
|
591
|
-
connector,
|
|
592
|
-
toolName: alias,
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
if (collisions.length > 0) {
|
|
596
|
-
const names = [definition, ...collisions]
|
|
597
|
-
.map((tool) => `"${tool.name}"`)
|
|
598
|
-
.join(", ");
|
|
599
|
-
return {
|
|
600
|
-
ok: false,
|
|
601
|
-
error: {
|
|
602
|
-
code: "ambiguous_tool_alias",
|
|
603
|
-
message: `Tool alias "${boundedEchoText(alias)}" is ambiguous on connector "${connector.id}" because ${names} sanitize to the same name. Use connecta.call with an exact address.`,
|
|
604
|
-
retryable: false,
|
|
605
|
-
nextAction: {
|
|
606
|
-
function: "connecta.call",
|
|
607
|
-
addresses: [definition, ...collisions].map(
|
|
608
|
-
(tool) => `${connector.id}.${tool.name}`,
|
|
609
|
-
),
|
|
610
|
-
purpose:
|
|
611
|
-
"Choose the intended canonical address and call it with the original arguments.",
|
|
612
|
-
},
|
|
613
|
-
},
|
|
614
|
-
catalogMs: Date.now() - started,
|
|
615
|
-
connector,
|
|
616
|
-
toolName: alias,
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
return {
|
|
620
|
-
ok: true,
|
|
621
|
-
resolved: {
|
|
622
|
-
connector,
|
|
623
|
-
toolName: definition.name,
|
|
624
|
-
definition,
|
|
625
|
-
},
|
|
626
|
-
catalogMs: Date.now() - started,
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
async search(args: CatalogSearchArgs): Promise<CatalogSearchPage> {
|
|
631
|
-
const query = args.query ?? "";
|
|
632
|
-
const retrievalQuery = lexicalSearchQuery(query);
|
|
633
|
-
const safety = discoverySafety(args.safety);
|
|
634
|
-
const limit = discoverySearchLimit(args.limit);
|
|
635
|
-
const offset = Math.max(0, Math.trunc(args.offset ?? 0));
|
|
636
|
-
const scopedConnector = args.connector
|
|
637
|
-
? this.registry.getConnector(args.connector)
|
|
638
|
-
: undefined;
|
|
639
|
-
const connectors = args.connector
|
|
640
|
-
? scopedConnector
|
|
641
|
-
? [scopedConnector]
|
|
642
|
-
: []
|
|
643
|
-
: this.registry.listConnectors();
|
|
644
|
-
const catalogs = await mapSettledWithConcurrency(
|
|
645
|
-
connectors,
|
|
646
|
-
this.concurrency,
|
|
647
|
-
(connector) =>
|
|
648
|
-
// Unlike the describe path, this label never reaches a caller: search
|
|
649
|
-
// only counts rejected catalogs (`unavailableCatalogs` below) and
|
|
650
|
-
// renders its own guidance, so the folded name here stays internal and
|
|
651
|
-
// needs no surface awareness.
|
|
652
|
-
this.loadForDiscovery(
|
|
653
|
-
connector.id,
|
|
654
|
-
`search_tools probe of "${connector.id}"`,
|
|
655
|
-
),
|
|
656
|
-
);
|
|
657
|
-
const searchableCatalogs = catalogs.map((catalog) =>
|
|
658
|
-
catalog.status === "fulfilled"
|
|
659
|
-
? {
|
|
660
|
-
status: "fulfilled" as const,
|
|
661
|
-
value: toolsForSafety(catalog.value, safety),
|
|
662
|
-
}
|
|
663
|
-
: catalog,
|
|
664
|
-
);
|
|
665
|
-
const matches: Array<{
|
|
666
|
-
connector: Connector;
|
|
667
|
-
tool: ToolDef;
|
|
668
|
-
score: number;
|
|
669
|
-
order: number;
|
|
670
|
-
exactName: boolean;
|
|
671
|
-
matchedTermCount: number;
|
|
672
|
-
complete: boolean;
|
|
673
|
-
}> = [];
|
|
674
|
-
let matchMode: "all" | "partial" = "all";
|
|
675
|
-
const statistics = lexicalCorpusStatistics(
|
|
676
|
-
searchableCatalogs.flatMap((catalog) =>
|
|
677
|
-
catalog.status === "fulfilled" ? [catalog.value] : [],
|
|
678
|
-
),
|
|
679
|
-
retrievalQuery,
|
|
680
|
-
);
|
|
681
|
-
const trimmedQuery = query.trim();
|
|
682
|
-
const isBrowse = trimmedQuery.length === 0;
|
|
683
|
-
const queryTerms = lexicalQueryTerms(retrievalQuery);
|
|
684
|
-
const queryTermCount = queryTerms.length;
|
|
685
|
-
const unsearchableQuery = !isBrowse && queryTermCount === 0;
|
|
686
|
-
const analysisTerms = unsearchableQuery ? [trimmedQuery] : queryTerms;
|
|
687
|
-
const analyzedTerms = analysisTerms.slice(0, MAX_QUERY_TERMS);
|
|
688
|
-
const displayTerm = (term: string) => boundedQueryTerm(term).text;
|
|
689
|
-
const queryMetadataTruncated =
|
|
690
|
-
analysisTerms.length > analyzedTerms.length ||
|
|
691
|
-
analyzedTerms.some((term) => boundedQueryTerm(term).truncated);
|
|
692
|
-
const collectMatches = (mode: "all" | "partial") => {
|
|
693
|
-
const collected: typeof matches = [];
|
|
694
|
-
let orderBase = 0;
|
|
695
|
-
searchableCatalogs.forEach((catalog, connectorIndex) => {
|
|
696
|
-
const connector = connectors[connectorIndex];
|
|
697
|
-
if (!connector) {
|
|
698
|
-
throw new Error("Catalog result has no corresponding connector");
|
|
699
|
-
}
|
|
700
|
-
if (catalog.status === "fulfilled") {
|
|
701
|
-
for (const ranked of rankTools(
|
|
702
|
-
catalog.value,
|
|
703
|
-
retrievalQuery,
|
|
704
|
-
mode,
|
|
705
|
-
statistics,
|
|
706
|
-
query,
|
|
707
|
-
)) {
|
|
708
|
-
collected.push({
|
|
709
|
-
connector,
|
|
710
|
-
tool: ranked.tool,
|
|
711
|
-
score: ranked.score,
|
|
712
|
-
order: orderBase + ranked.order,
|
|
713
|
-
exactName: ranked.exactName,
|
|
714
|
-
matchedTermCount: ranked.matchedTermCount,
|
|
715
|
-
complete:
|
|
716
|
-
isBrowse || ranked.matchedTermCount === queryTermCount,
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
orderBase +=
|
|
721
|
-
catalog.status === "fulfilled" ? catalog.value.length : 1;
|
|
722
|
-
});
|
|
723
|
-
return collected;
|
|
724
|
-
};
|
|
725
|
-
// A non-empty query that normalizes to no lexical terms is not a browse.
|
|
726
|
-
// Ranking an empty phrase would otherwise return every tool as an
|
|
727
|
-
// unrelated zero-score match, with no coverage to explain the result.
|
|
728
|
-
const rankedMatches = unsearchableQuery
|
|
729
|
-
? []
|
|
730
|
-
: collectMatches(isBrowse ? "all" : "partial");
|
|
731
|
-
const completeMatchCount = rankedMatches.filter(
|
|
732
|
-
(match) => match.complete,
|
|
733
|
-
).length;
|
|
734
|
-
matches.push(
|
|
735
|
-
...rankedMatches.filter(
|
|
736
|
-
(match) =>
|
|
737
|
-
match.complete ||
|
|
738
|
-
completeMatchCount === 0 ||
|
|
739
|
-
match.exactName ||
|
|
740
|
-
match.matchedTermCount >= 2,
|
|
741
|
-
),
|
|
742
|
-
);
|
|
743
|
-
if (!isBrowse && !unsearchableQuery && completeMatchCount === 0) {
|
|
744
|
-
matchMode = "partial";
|
|
745
|
-
}
|
|
746
|
-
// Complete matches and exact tool-name phrases share the first rank tier.
|
|
747
|
-
// This lets a strong action/object name beat a weak description-only
|
|
748
|
-
// decoy. Other partial matches fill the remaining page only after every
|
|
749
|
-
// complete match, regardless of a rare-term score spike.
|
|
750
|
-
matches.sort((a, b) => {
|
|
751
|
-
const aFirstTier = a.complete || a.exactName;
|
|
752
|
-
const bFirstTier = b.complete || b.exactName;
|
|
753
|
-
return (
|
|
754
|
-
Number(bFirstTier) - Number(aFirstTier) ||
|
|
755
|
-
b.score - a.score ||
|
|
756
|
-
a.order - b.order
|
|
757
|
-
);
|
|
758
|
-
});
|
|
759
|
-
const pageMatches = matches.slice(offset, offset + limit);
|
|
760
|
-
const entries = pageMatches.map((match) => {
|
|
761
|
-
const input = match.tool.inputSchema ?? { type: "object" };
|
|
762
|
-
const renderedInput = args.includeSchemas
|
|
763
|
-
? renderSearchSchema(input, args.includeSchemas)
|
|
764
|
-
: undefined;
|
|
765
|
-
const renderedOutput =
|
|
766
|
-
args.includeSchemas && match.tool.outputSchema
|
|
767
|
-
? renderSearchSchema(match.tool.outputSchema, args.includeSchemas)
|
|
768
|
-
: undefined;
|
|
769
|
-
const schemaKeys =
|
|
770
|
-
args.includeSchemas && args.includeSchemaKeys
|
|
771
|
-
? schemaKeyMetadata(input, match.tool.outputSchema)
|
|
772
|
-
: undefined;
|
|
773
|
-
const description = summarizeDiscoveryDescription(
|
|
774
|
-
match.tool.description,
|
|
775
|
-
args.fullDescriptions === true,
|
|
776
|
-
);
|
|
777
|
-
const requiredReasons = guideRequiredReasons(
|
|
778
|
-
match.connector,
|
|
779
|
-
match.tool,
|
|
780
|
-
renderedInput?.truncated === true || renderedOutput?.truncated === true,
|
|
781
|
-
);
|
|
782
|
-
const guideSummary = connectorGuideSummary(match.connector);
|
|
783
|
-
return {
|
|
784
|
-
connector: match.connector,
|
|
785
|
-
...(connectorGuide(match.connector)
|
|
786
|
-
? {
|
|
787
|
-
guide: connectorSkillName(match.connector.id),
|
|
788
|
-
...(guideSummary ? { guideSummary } : {}),
|
|
789
|
-
}
|
|
790
|
-
: {}),
|
|
791
|
-
tool: {
|
|
792
|
-
name: match.tool.name,
|
|
793
|
-
address: `${match.connector.id}.${match.tool.name}`,
|
|
794
|
-
...(description !== undefined ? { description } : {}),
|
|
795
|
-
...(args.includeSchemas
|
|
796
|
-
? {
|
|
797
|
-
inputSchema: renderedInput?.schema,
|
|
798
|
-
}
|
|
799
|
-
: {}),
|
|
800
|
-
...(renderedInput?.truncated
|
|
801
|
-
? { inputSchemaTruncated: true as const }
|
|
802
|
-
: {}),
|
|
803
|
-
...(args.includeSchemas && match.tool.outputSchema
|
|
804
|
-
? {
|
|
805
|
-
outputSchema: renderedOutput?.schema,
|
|
806
|
-
}
|
|
807
|
-
: {}),
|
|
808
|
-
...(renderedOutput?.truncated
|
|
809
|
-
? { outputSchemaTruncated: true as const }
|
|
810
|
-
: {}),
|
|
811
|
-
...(schemaKeys && !renderedInput?.truncated
|
|
812
|
-
? {
|
|
813
|
-
...(schemaKeys.inputKeys
|
|
814
|
-
? { inputKeys: schemaKeys.inputKeys }
|
|
815
|
-
: {}),
|
|
816
|
-
...(schemaKeys.requiredInputKeys
|
|
817
|
-
? { requiredInputKeys: schemaKeys.requiredInputKeys }
|
|
818
|
-
: {}),
|
|
819
|
-
}
|
|
820
|
-
: {}),
|
|
821
|
-
...(schemaKeys?.outputKeys && !renderedOutput?.truncated
|
|
822
|
-
? { outputKeys: schemaKeys.outputKeys }
|
|
823
|
-
: {}),
|
|
824
|
-
...(match.tool.annotations
|
|
825
|
-
? { annotations: match.tool.annotations }
|
|
826
|
-
: {}),
|
|
827
|
-
...(requiredReasons
|
|
828
|
-
? {
|
|
829
|
-
guideRequired: true as const,
|
|
830
|
-
guideRequiredReasons: requiredReasons,
|
|
831
|
-
}
|
|
832
|
-
: {}),
|
|
833
|
-
},
|
|
834
|
-
};
|
|
835
|
-
});
|
|
836
|
-
const nextOffset =
|
|
837
|
-
offset + entries.length < matches.length
|
|
838
|
-
? offset + entries.length
|
|
839
|
-
: undefined;
|
|
840
|
-
const pageTools = new Set(pageMatches.map((match) => match.tool));
|
|
841
|
-
const matchingTools = (term: string) =>
|
|
842
|
-
new Set([
|
|
843
|
-
...(statistics.nameMatches.get(term) ?? []),
|
|
844
|
-
...(statistics.descriptionMatches.get(term) ?? []),
|
|
845
|
-
]);
|
|
846
|
-
const representedTerms: string[] = [];
|
|
847
|
-
const otherResultTerms: string[] = [];
|
|
848
|
-
const unmatchedTerms: string[] = [];
|
|
849
|
-
for (const term of analyzedTerms) {
|
|
850
|
-
const termTools = matchingTools(term);
|
|
851
|
-
if ([...termTools].some((tool) => pageTools.has(tool))) {
|
|
852
|
-
representedTerms.push(displayTerm(term));
|
|
853
|
-
} else if (termTools.size > 0) {
|
|
854
|
-
otherResultTerms.push(displayTerm(term));
|
|
855
|
-
} else {
|
|
856
|
-
unmatchedTerms.push(displayTerm(term));
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
const unavailableCatalogs = catalogs.filter(
|
|
860
|
-
(catalog) => catalog.status === "rejected",
|
|
861
|
-
).length;
|
|
862
|
-
// Named field by field rather than spread: `CallErrorDetails` also carries
|
|
863
|
-
// connector, operation, recovery, and nextAction, and a discovery read is
|
|
864
|
-
// not a call — widening the classifier must not silently widen what a
|
|
865
|
-
// catalog search hands back.
|
|
866
|
-
const scopedCatalogError = ((): CatalogFailureDetail | undefined => {
|
|
867
|
-
if (!scopedConnector || catalogs[0]?.status !== "rejected") {
|
|
868
|
-
return undefined;
|
|
869
|
-
}
|
|
870
|
-
const error = classifyCallError(
|
|
871
|
-
catalogs[0].reason,
|
|
872
|
-
"catalog_lookup_failed",
|
|
873
|
-
);
|
|
874
|
-
return {
|
|
875
|
-
code: error.code,
|
|
876
|
-
message: boundedEchoText(error.message),
|
|
877
|
-
retryable: error.retryable,
|
|
878
|
-
...(error.retryAfterMs === undefined
|
|
879
|
-
? {}
|
|
880
|
-
: { retryAfterMs: error.retryAfterMs }),
|
|
881
|
-
};
|
|
882
|
-
})();
|
|
883
|
-
const safetyLabel =
|
|
884
|
-
safety === "readOnly"
|
|
885
|
-
? "read-only "
|
|
886
|
-
: safety === "approvalRequired"
|
|
887
|
-
? "approval-required "
|
|
888
|
-
: "";
|
|
889
|
-
const filterRecovery =
|
|
890
|
-
safety === "all" ? "" : " Change safety to inspect the other tools.";
|
|
891
|
-
const scopedGuide =
|
|
892
|
-
matches.length === 0 && scopedConnector && connectorGuide(scopedConnector)
|
|
893
|
-
? {
|
|
894
|
-
guide: connectorSkillName(scopedConnector.id),
|
|
895
|
-
guideSummary: connectorGuideSummary(scopedConnector),
|
|
896
|
-
required: connectorGuideRequired(scopedConnector),
|
|
897
|
-
}
|
|
898
|
-
: undefined;
|
|
899
|
-
// A scope that resolved to nothing is the same silence one step earlier in
|
|
900
|
-
// the lookup: no connector resolved, so no catalog was even attempted, so
|
|
901
|
-
// no catalog failed and the unavailable path below never fires. Echo only
|
|
902
|
-
// the ID the caller already supplied — naming what else is configured
|
|
903
|
-
// would answer a question they did not ask, past a filter they may not
|
|
904
|
-
// pass.
|
|
905
|
-
const unknownConnectorGuidance =
|
|
906
|
-
args.connector && !scopedConnector
|
|
907
|
-
? `Connector "${args.connector}" is not configured in this deployment. Omit connector to search all configured tools.`
|
|
908
|
-
: undefined;
|
|
909
|
-
// Searchable queries report analysis when the scorer had to degrade. A
|
|
910
|
-
// non-empty query with no searchable terms reports the bounded raw input
|
|
911
|
-
// as unmatched instead of silently becoming a browse. A real browse has
|
|
912
|
-
// no terms to analyse and normally reports none at all. Scope failures are
|
|
913
|
-
// the exception, because an empty result alone looks like a connector that
|
|
914
|
-
// correctly exposes no tools.
|
|
915
|
-
const reportsQueryAnalysis =
|
|
916
|
-
unsearchableQuery ||
|
|
917
|
-
(queryTerms.length > 0
|
|
918
|
-
? matchMode === "partial"
|
|
919
|
-
: unknownConnectorGuidance !== undefined || unavailableCatalogs > 0);
|
|
920
|
-
const guidance =
|
|
921
|
-
unsearchableQuery
|
|
922
|
-
? (unknownConnectorGuidance ??
|
|
923
|
-
(scopedConnector && unavailableCatalogs > 0
|
|
924
|
-
? `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
925
|
-
: "The query contained no searchable lexical terms. Use 2–4 ASCII action/object terms, or browse with an empty query."))
|
|
926
|
-
: queryTerms.length === 0
|
|
927
|
-
? // A browse has no terms to advise about, so it stays silent unless
|
|
928
|
-
// the scope itself failed: the guidance on a scoped miss recommends
|
|
929
|
-
// browsing with an empty query, and that advice must not lead into a
|
|
930
|
-
// dead end that looks like a connector with no tools.
|
|
931
|
-
(unknownConnectorGuidance ??
|
|
932
|
-
(unavailableCatalogs === 0
|
|
933
|
-
? undefined
|
|
934
|
-
: scopedConnector
|
|
935
|
-
? `Connector "${scopedConnector.id}" could not be browsed because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
936
|
-
: `${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable, so this browse is incomplete. Scope by connector to see the typed reason.`))
|
|
937
|
-
: matches.length === 0
|
|
938
|
-
? (unknownConnectorGuidance ??
|
|
939
|
-
(scopedConnector
|
|
940
|
-
? unavailableCatalogs > 0
|
|
941
|
-
? `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
942
|
-
: scopedGuide?.required
|
|
943
|
-
? `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Fetch queryAnalysis.guide before calling, then refine terms or browse with an empty query.${filterRecovery}`
|
|
944
|
-
: `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Refine terms or browse it with an empty query.${filterRecovery}`
|
|
945
|
-
: unavailableCatalogs === 0
|
|
946
|
-
? `No matching ${safetyLabel}capability is configured in this deployment. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`
|
|
947
|
-
: `No matching ${safetyLabel}capability was found in the catalogs that answered; ${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`))
|
|
948
|
-
: matchMode === "partial"
|
|
949
|
-
? scopedConnector
|
|
950
|
-
? `No single tool on connector "${scopedConnector.id}" matched every term. Split distinct intents into separate searches.`
|
|
951
|
-
: unavailableCatalogs === 0
|
|
952
|
-
? "No single tool matched every term. Split distinct intents into separate searches."
|
|
953
|
-
: "No single tool matched every term in the catalogs that answered. Split distinct intents into separate searches."
|
|
954
|
-
: undefined;
|
|
955
|
-
return {
|
|
956
|
-
entries,
|
|
957
|
-
total: matches.length,
|
|
958
|
-
offset,
|
|
959
|
-
limit,
|
|
960
|
-
hasMore: nextOffset !== undefined,
|
|
961
|
-
...(nextOffset !== undefined ? { nextOffset } : {}),
|
|
962
|
-
...(matchMode === "partial" && matches.length > 0
|
|
963
|
-
? { matchMode }
|
|
964
|
-
: {}),
|
|
965
|
-
...(reportsQueryAnalysis
|
|
966
|
-
? {
|
|
967
|
-
queryAnalysis: {
|
|
968
|
-
representedTerms,
|
|
969
|
-
otherResultTerms,
|
|
970
|
-
unmatchedTerms,
|
|
971
|
-
...(queryMetadataTruncated
|
|
972
|
-
? { truncated: true as const }
|
|
973
|
-
: {}),
|
|
974
|
-
...(args.connector ? { connectorScope: args.connector } : {}),
|
|
975
|
-
...(args.connector && !scopedConnector
|
|
976
|
-
? { unknownConnector: true as const }
|
|
977
|
-
: {}),
|
|
978
|
-
...(unavailableCatalogs > 0
|
|
979
|
-
? { unavailableConnectorCount: unavailableCatalogs }
|
|
980
|
-
: {}),
|
|
981
|
-
...(scopedCatalogError ? { catalogError: scopedCatalogError } : {}),
|
|
982
|
-
...(scopedGuide
|
|
983
|
-
? {
|
|
984
|
-
guide: scopedGuide.guide,
|
|
985
|
-
...(scopedGuide.guideSummary
|
|
986
|
-
? { guideSummary: scopedGuide.guideSummary }
|
|
987
|
-
: {}),
|
|
988
|
-
...(scopedGuide.required
|
|
989
|
-
? {
|
|
990
|
-
guideRequired: true as const,
|
|
991
|
-
guideRequiredReasons: [
|
|
992
|
-
"connector_required" as const,
|
|
993
|
-
],
|
|
994
|
-
}
|
|
995
|
-
: {}),
|
|
996
|
-
}
|
|
997
|
-
: {}),
|
|
998
|
-
...(guidance ? { guidance } : {}),
|
|
999
|
-
},
|
|
1000
|
-
}
|
|
1001
|
-
: {}),
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
async describe(args: CatalogDescribeArgs): Promise<CatalogDescription[]> {
|
|
1006
|
-
const addresses = discoveryAddresses(args);
|
|
1007
|
-
const format = args.format ?? "compact";
|
|
1008
|
-
const resolved = addresses.map((rawAddress) => {
|
|
1009
|
-
const address = String(rawAddress);
|
|
1010
|
-
return { address, resolved: this.registry.resolveAddress(address) };
|
|
1011
|
-
});
|
|
1012
|
-
const connectorIds = [
|
|
1013
|
-
...new Set(
|
|
1014
|
-
resolved
|
|
1015
|
-
.map((entry) => entry.resolved?.connector.id)
|
|
1016
|
-
.filter((id): id is string => Boolean(id)),
|
|
1017
|
-
),
|
|
1018
|
-
];
|
|
1019
|
-
const loaded = await mapSettledWithConcurrency(
|
|
1020
|
-
connectorIds,
|
|
1021
|
-
this.concurrency,
|
|
1022
|
-
(id) =>
|
|
1023
|
-
this.loadForDiscovery(id, `connecta.describe probe of "${id}"`),
|
|
1024
|
-
);
|
|
1025
|
-
const catalogs = new Map<string, ToolDef[] | Error>();
|
|
1026
|
-
loaded.forEach((result, index) => {
|
|
1027
|
-
const connectorId = connectorIds[index];
|
|
1028
|
-
if (connectorId === undefined) {
|
|
1029
|
-
throw new Error("Catalog result has no corresponding connector id");
|
|
1030
|
-
}
|
|
1031
|
-
catalogs.set(
|
|
1032
|
-
connectorId,
|
|
1033
|
-
result.status === "fulfilled"
|
|
1034
|
-
? result.value
|
|
1035
|
-
: result.reason instanceof Error
|
|
1036
|
-
? result.reason
|
|
1037
|
-
: new Error(String(result.reason)),
|
|
1038
|
-
);
|
|
1039
|
-
});
|
|
1040
|
-
return resolved.map(({ address, resolved: addressResolution }) => {
|
|
1041
|
-
if (!addressResolution) {
|
|
1042
|
-
return { address, error: `Unknown address "${address}"` };
|
|
1043
|
-
}
|
|
1044
|
-
const catalog = catalogs.get(addressResolution.connector.id);
|
|
1045
|
-
if (catalog instanceof Error) {
|
|
1046
|
-
return { address, error: catalog.message };
|
|
1047
|
-
}
|
|
1048
|
-
const tool = catalog?.find(
|
|
1049
|
-
(item) => item.name === addressResolution.toolName,
|
|
1050
|
-
);
|
|
1051
|
-
if (!tool) {
|
|
1052
|
-
return {
|
|
1053
|
-
address,
|
|
1054
|
-
error: `Unknown tool "${addressResolution.toolName}" on connector "${addressResolution.connector.id}"`,
|
|
1055
|
-
};
|
|
1056
|
-
}
|
|
1057
|
-
const input = tool.inputSchema ?? { type: "object" };
|
|
1058
|
-
const description = summarizeDescription(
|
|
1059
|
-
tool.description,
|
|
1060
|
-
args.fullDescriptions === true,
|
|
1061
|
-
);
|
|
1062
|
-
const requiredReasons = guideRequiredReasons(
|
|
1063
|
-
addressResolution.connector,
|
|
1064
|
-
tool,
|
|
1065
|
-
false,
|
|
1066
|
-
);
|
|
1067
|
-
const guideSummary = connectorGuideSummary(addressResolution.connector);
|
|
1068
|
-
return {
|
|
1069
|
-
address,
|
|
1070
|
-
name: tool.name,
|
|
1071
|
-
...(description !== undefined ? { description } : {}),
|
|
1072
|
-
...(connectorGuide(addressResolution.connector)
|
|
1073
|
-
? {
|
|
1074
|
-
guide: connectorSkillName(addressResolution.connector.id),
|
|
1075
|
-
...(guideSummary ? { guideSummary } : {}),
|
|
1076
|
-
}
|
|
1077
|
-
: {}),
|
|
1078
|
-
...(requiredReasons
|
|
1079
|
-
? {
|
|
1080
|
-
guideRequired: true as const,
|
|
1081
|
-
guideRequiredReasons: requiredReasons,
|
|
1082
|
-
}
|
|
1083
|
-
: {}),
|
|
1084
|
-
inputSchema: renderSchema(input, format),
|
|
1085
|
-
...(tool.outputSchema
|
|
1086
|
-
? {
|
|
1087
|
-
outputSchema: renderSchema(tool.outputSchema, format),
|
|
1088
|
-
}
|
|
1089
|
-
: {}),
|
|
1090
|
-
...(tool.annotations ? { annotations: tool.annotations } : {}),
|
|
1091
|
-
};
|
|
1092
|
-
});
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
export function groupedSearchResult(page: CatalogSearchPage) {
|
|
1097
|
-
const groups: Array<{
|
|
1098
|
-
id: string;
|
|
1099
|
-
title?: string;
|
|
1100
|
-
guide?: string;
|
|
1101
|
-
guideSummary?: string;
|
|
1102
|
-
tools: CatalogSearchEntry["tool"][];
|
|
1103
|
-
}> = [];
|
|
1104
|
-
const byConnector = new Map<string, (typeof groups)[number]>();
|
|
1105
|
-
for (const entry of page.entries) {
|
|
1106
|
-
const existing = byConnector.get(entry.connector.id);
|
|
1107
|
-
if (existing) {
|
|
1108
|
-
existing.tools.push(entry.tool);
|
|
1109
|
-
} else {
|
|
1110
|
-
const group: (typeof groups)[number] = {
|
|
1111
|
-
id: entry.connector.id,
|
|
1112
|
-
...(entry.connector.title ? { title: entry.connector.title } : {}),
|
|
1113
|
-
...(entry.guide ? { guide: entry.guide } : {}),
|
|
1114
|
-
...(entry.guideSummary
|
|
1115
|
-
? { guideSummary: entry.guideSummary }
|
|
1116
|
-
: {}),
|
|
1117
|
-
tools: [],
|
|
1118
|
-
};
|
|
1119
|
-
byConnector.set(entry.connector.id, group);
|
|
1120
|
-
groups.push(group);
|
|
1121
|
-
group.tools.push(entry.tool);
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
return {
|
|
1125
|
-
connectors: groups,
|
|
1126
|
-
total: page.total,
|
|
1127
|
-
offset: page.offset,
|
|
1128
|
-
limit: page.limit,
|
|
1129
|
-
hasMore: page.hasMore,
|
|
1130
|
-
...(page.nextOffset !== undefined ? { nextOffset: page.nextOffset } : {}),
|
|
1131
|
-
...(page.matchMode ? { matchMode: page.matchMode } : {}),
|
|
1132
|
-
...(page.queryAnalysis ? { queryAnalysis: page.queryAnalysis } : {}),
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
export function flatSearchResult(page: CatalogSearchPage) {
|
|
1137
|
-
return {
|
|
1138
|
-
tools: page.entries.map((entry) => ({
|
|
1139
|
-
...entry.tool,
|
|
1140
|
-
...(entry.guide ? { guide: entry.guide } : {}),
|
|
1141
|
-
...(entry.guideSummary
|
|
1142
|
-
? { guideSummary: entry.guideSummary }
|
|
1143
|
-
: {}),
|
|
1144
|
-
})),
|
|
1145
|
-
total: page.total,
|
|
1146
|
-
offset: page.offset,
|
|
1147
|
-
limit: page.limit,
|
|
1148
|
-
hasMore: page.hasMore,
|
|
1149
|
-
...(page.nextOffset !== undefined ? { nextOffset: page.nextOffset } : {}),
|
|
1150
|
-
...(page.matchMode ? { matchMode: page.matchMode } : {}),
|
|
1151
|
-
...(page.queryAnalysis ? { queryAnalysis: page.queryAnalysis } : {}),
|
|
1152
|
-
};
|
|
1153
|
-
}
|