@zackbart/connecta 0.18.2 → 0.19.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 +100 -4
- package/README.md +4 -0
- package/dist/catalog-service.d.ts +20 -13
- package/dist/catalog-service.js +123 -116
- package/dist/catalog.js +29 -46
- package/dist/connector-scope.js +2 -7
- package/dist/connectors/api.d.ts +4 -16
- package/dist/connectors/api.js +19 -46
- package/dist/connectors/guarded-fetch.d.ts +9 -23
- package/dist/connectors/guarded-fetch.js +38 -76
- package/dist/connectors/remote-mcp.js +36 -79
- package/dist/errors.d.ts +6 -27
- package/dist/errors.js +8 -5
- package/dist/execute.d.ts +24 -22
- package/dist/execute.js +98 -145
- package/dist/executor-result.d.ts +1 -0
- package/dist/executor-result.js +4 -11
- package/dist/executors/quickjs-child.js +1 -3
- package/dist/executors/quickjs-runtime.js +1 -3
- package/dist/executors/quickjs.js +1 -3
- package/dist/index.js +27 -57
- package/dist/invocation.js +114 -178
- package/dist/meta-tools.d.ts +15 -28
- package/dist/meta-tools.js +33 -89
- package/dist/providers/cloudflare.d.ts +2 -18
- package/dist/providers/cloudflare.js +1460 -2451
- package/dist/providers/linear.d.ts +4 -41
- package/dist/providers/linear.js +8 -39
- package/dist/providers/mixpanel.d.ts +3 -25
- package/dist/providers/mixpanel.js +7 -22
- package/dist/providers/notion.d.ts +1 -15
- package/dist/providers/notion.js +44 -173
- package/dist/providers/revenuecat.d.ts +4 -57
- package/dist/providers/revenuecat.js +10 -93
- package/dist/providers/stripe.d.ts +1 -12
- package/dist/providers/stripe.js +7 -45
- package/dist/registry.d.ts +16 -34
- package/dist/registry.js +18 -103
- package/dist/result-shapes.d.ts +13 -0
- package/dist/result-shapes.js +331 -0
- package/dist/routes/mcp.js +1 -1
- package/dist/routes/oauth.js +3 -3
- package/dist/routes/shared.d.ts +15 -15
- package/dist/routes/shared.js +1 -3
- package/dist/skills.js +3 -3
- package/dist/timeout.d.ts +8 -7
- package/dist/timeout.js +47 -38
- package/dist/types.d.ts +3 -3
- package/dist/ui.d.ts +1 -25
- package/dist/ui.js +18 -45
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +5 -2
- package/documentation/call-admission.md +1 -1
- package/documentation/cloudflare.md +1 -1
- package/documentation/code-mode.md +13 -13
- package/documentation/connectors.md +34 -1
- package/documentation/linear.md +1 -1
- package/documentation/meta-tools.md +17 -3
- package/documentation/mixpanel.md +1 -1
- package/documentation/notion.md +1 -1
- package/documentation/operations.md +20 -15
- package/documentation/provider-conventions.md +1 -1
- package/documentation/revenuecat.md +1 -1
- package/documentation/stripe.md +1 -1
- package/documentation/upgrading.md +24 -4
- package/ethos.md +74 -120
- package/package.json +3 -4
- package/templates/node/package.json +1 -1
- package/documentation/code-first-exploration.md +0 -292
- package/documentation/mcp-2026-07-28.md +0 -46
- package/documentation/mcp-ui-design.md +0 -382
- package/documentation/program-ui-read-calls.md +0 -213
- package/documentation/provider-audit.md +0 -198
- package/documentation/rich-output-design.md +0 -211
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,102 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.19.0 — 2026-08-25
|
|
6
|
+
|
|
7
|
+
This release is a smaller, simpler package with no behavioral change for a
|
|
8
|
+
deployment. A deployment can ignore everything here. The one packaging change
|
|
9
|
+
is to maintainer history: six finished design records now live in the repository
|
|
10
|
+
at their GitHub URLs instead of shipping in the npm package, cutting 91 KB from
|
|
11
|
+
the package. The constitution is a fifth of its former size and keeps the same
|
|
12
|
+
decisions. The core shrank from 28,756 to 27,417 lines and the tests from 42,341
|
|
13
|
+
to 40,375 before the final pure-suite splits, which put 198 lines back — a
|
|
14
|
+
copied preamble the next pass moves into a fixture (#479). Two dead paths
|
|
15
|
+
finally left with the rest: write-only health accessors on `RegistryView`,
|
|
16
|
+
residue from #179, and the v1 persisted-catalog reader, unreachable since
|
|
17
|
+
2026-07-28.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Providers.** Every Cloudflare tool is built through `cfTool()` and
|
|
22
|
+
`compact()`, so the repeated schema headers, scope properties, and
|
|
23
|
+
conditional-spread projections are written once; the paged-list, delete-ack,
|
|
24
|
+
and passthrough handlers are shared while every tool keeps its own literal
|
|
25
|
+
name, description, annotations, and schemas; the authentication `Symbol` is
|
|
26
|
+
gone because the scheme is fixed per connector (#465, #467). Cloudflare and
|
|
27
|
+
Notion share one Retry-After parser and one guarded JSON accessor, and
|
|
28
|
+
`callCloudflareContent` still classifies an unreadable error body by status
|
|
29
|
+
(#468). The hosted providers' comment blocks point at their guides instead of
|
|
30
|
+
restating them, and `api()` omits undefined options once (#463).
|
|
31
|
+
- **Core.** Internal option types accept `undefined`, so `createConnecta` and
|
|
32
|
+
the registration helpers forward configuration directly (#462). One deadline,
|
|
33
|
+
sleep, and message helper serve the whole call path; `invocation.ts` lost its
|
|
34
|
+
hand-rolled controller, timer, and listener choreography with Retry-After,
|
|
35
|
+
per-phase timing, and health exclusion unchanged (#466). The catalog,
|
|
36
|
+
execution, remote-MCP, and operator-data paths share their failure envelopes,
|
|
37
|
+
close helpers, and result shapers (#472).
|
|
38
|
+
- **Package and docs.** Six finished design records moved to
|
|
39
|
+
[`records/`](https://github.com/zackbart/connecta/tree/main/records), outside
|
|
40
|
+
the published files, and packed-link validation folded into the documentation
|
|
41
|
+
checker: −1,342 shipped lines, −91 KB (#464). `ethos.md` is the constitution
|
|
42
|
+
again — refusals, invariants, and what this is, at 1,188 words instead of
|
|
43
|
+
3,174, with every decision intact and a word cap replacing the line cap
|
|
44
|
+
(#471).
|
|
45
|
+
- **Tests.** Shared fixtures replace 122 hand-rolled connector literals, five
|
|
46
|
+
fake downstream MCP servers, four fake Clerk auths, and the per-suite JSON-RPC
|
|
47
|
+
readers, deferreds, and logger spies (#473, #474, #475); the six
|
|
48
|
+
provider-registry suites are one `describe.each` and the provider error
|
|
49
|
+
mappings are tables (#476); repeated cases are `it.each` tables and the
|
|
50
|
+
duplicated assertion layers are gone (#477); the two largest suites are split
|
|
51
|
+
by subject (#460). Every `it()` and `expect()` that pins behavior survived —
|
|
52
|
+
3,400 fewer lines, no assertion dropped.
|
|
53
|
+
- **Declined with numbers**, so nobody re-runs the experiment: a shared bounded
|
|
54
|
+
queue under both admission controllers measured −17 lines for a
|
|
55
|
+
hook-parameterised abstraction (#453); Effect as the core effect system
|
|
56
|
+
measured −4% of the core for +75 KB gzip and a second async paradigm (#470).
|
|
57
|
+
|
|
58
|
+
### Removed
|
|
59
|
+
|
|
60
|
+
- The write-only `HealthLog` and its `RegistryView` accessors: `healthFor`,
|
|
61
|
+
`hasObservedSuccess`, `observedSuccessAt`, and `peekTools`. `refreshTools` is
|
|
62
|
+
now private. This was residue from the removed proactive credential-liveness
|
|
63
|
+
design (#179).
|
|
64
|
+
- The v1 persisted-catalog reader, unreachable since 2026-07-28. Its fixtures
|
|
65
|
+
now use the v2 manifest-and-chunks format.
|
|
66
|
+
|
|
67
|
+
## 0.18.3 — 2026-08-25
|
|
68
|
+
|
|
69
|
+
This change gives code mode memory where downstream MCP catalogs are usually
|
|
70
|
+
silent: after a successful call with no declared output schema, Connecta keeps
|
|
71
|
+
only the result's field names and broad JSON types and shows that open shape on
|
|
72
|
+
later discovery. Nothing breaks, no deployment option changes, and providers
|
|
73
|
+
that already declare outputs are untouched. The bounded cache lives only in the
|
|
74
|
+
current process or Worker isolate and forgets each shape after 24 hours.
|
|
75
|
+
Discovery still never executes a tool, and
|
|
76
|
+
the new `outputSchemaSource: "observed"` marker is the warning label that keeps
|
|
77
|
+
one or several real results from masquerading as a provider contract.
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
|
|
81
|
+
- **Passive observed output schemas.** Successful explicitly read-only calls
|
|
82
|
+
whose provider declared no `outputSchema` infer and merge an open
|
|
83
|
+
optional-field schema of names and broad JSON types. The inference retains no
|
|
84
|
+
arguments, scalar values, raw results, code, credentials, or errors. Property
|
|
85
|
+
names may be user-authored. It bounds depth, breadth, names, nodes, definition
|
|
86
|
+
size, and schema bytes; keeps at most 256 runtime entries; ties each entry to
|
|
87
|
+
the exact tool definition; and expires it after 24 hours.
|
|
88
|
+
Search and describe return it with `outputSchemaSource: "observed"`; a
|
|
89
|
+
declared schema always wins. Observation is synchronous, storage-free, and
|
|
90
|
+
unable to fail the call. The design follows new warm-cache evidence and two
|
|
91
|
+
live deployment audits: BePresent had 246/378 tools without output schemas,
|
|
92
|
+
while OneMany's maintained connectors declared all 90 (#442).
|
|
93
|
+
|
|
94
|
+
### Changed
|
|
95
|
+
|
|
96
|
+
- Removed the write-only per-connector call health log. Payload-free call
|
|
97
|
+
outcomes remain available through the activity sink.
|
|
98
|
+
- Persisted catalog reads now require the version 2 manifest-and-chunks shape;
|
|
99
|
+
version 1 single-value catalogs are ignored and refreshed.
|
|
100
|
+
|
|
5
101
|
## 0.18.2 — 2026-08-18
|
|
6
102
|
|
|
7
103
|
This patch closes the gap the RevenueCat rollout exposed on the same day 0.18.1
|
|
@@ -725,7 +821,7 @@ it back byte-identical after each operator mutation.
|
|
|
725
821
|
constructor, schemas, validation path, handlers, and catalog service run, and
|
|
726
822
|
only `fetch` is a probe that records the request (#350).
|
|
727
823
|
- **Five provider audit reports** in
|
|
728
|
-
[`
|
|
824
|
+
[`records/provider-audit.md`](https://github.com/zackbart/connecta/blob/main/records/provider-audit.md), with
|
|
729
825
|
a verdict per convention, the fix for every miss, and every accepted
|
|
730
826
|
exception recorded with its argument (#342).
|
|
731
827
|
- **A convention test over the shipped surface.**
|
|
@@ -1508,7 +1604,7 @@ The channel is additive — a program that never calls `connecta.ui` produces th
|
|
|
1508
1604
|
byte-for-byte prior response, no executor changed to carry it, and there is no
|
|
1509
1605
|
new budget knob. Deployments do gain a `resources` capability and one extension
|
|
1510
1606
|
declaration, both of which the design requires before any host will render.
|
|
1511
|
-
The design record is `
|
|
1607
|
+
The design record is [`records/mcp-ui-design.md`](https://github.com/zackbart/connecta/blob/main/records/mcp-ui-design.md) (#266); the contract is
|
|
1512
1608
|
`code-mode.md`'s "Rendered output" clauses (#277).
|
|
1513
1609
|
|
|
1514
1610
|
### Added
|
|
@@ -1584,7 +1680,7 @@ that cannot be projected, like a screenshot a downstream tool returned. The
|
|
|
1584
1680
|
channel is additive: a program that never emits produces the byte-for-byte
|
|
1585
1681
|
prior response, no executor changed to carry it, and deployments that do
|
|
1586
1682
|
nothing get sensible budgets. The design record is
|
|
1587
|
-
`
|
|
1683
|
+
[`records/rich-output-design.md`](https://github.com/zackbart/connecta/blob/main/records/rich-output-design.md) (#267); the contract is `code-mode.md`'s
|
|
1588
1684
|
"Emitted output" clauses (#270).
|
|
1589
1685
|
|
|
1590
1686
|
Agent recovery is now executable data instead of prose at the remaining local
|
|
@@ -1997,7 +2093,7 @@ tests confirm SDK v2 client close does not make it redundant.
|
|
|
1997
2093
|
advances the existing generation fence before the SDK starts a fresh grant;
|
|
1998
2094
|
pre-upgrade credentials bind in place on their first validated read.
|
|
1999
2095
|
- **A complete in-repo disposition of the revision** in
|
|
2000
|
-
[`
|
|
2096
|
+
[`records/mcp-2026-07-28.md`](https://github.com/zackbart/connecta/blob/main/records/mcp-2026-07-28.md),
|
|
2001
2097
|
including the declined and gated surfaces.
|
|
2002
2098
|
|
|
2003
2099
|
### Changed
|
package/README.md
CHANGED
|
@@ -67,6 +67,10 @@ Fifty issues in, one small object out. Your context window notices.
|
|
|
67
67
|
classifications, imported one at a time.
|
|
68
68
|
- **Let the agent work in code.** Search, chain, filter, join, and reduce
|
|
69
69
|
inside the sandbox instead of round-tripping every call through the model.
|
|
70
|
+
- **Teach undeclared result shapes by using them.** Successful read-only calls
|
|
71
|
+
retain field names and broad types in bounded runtime memory, never scalar
|
|
72
|
+
values, so later programs can project a remote MCP result its provider never
|
|
73
|
+
documented.
|
|
70
74
|
- **Keep writes deliberate.** Only tools marked read-only run in a program.
|
|
71
75
|
Everything else is a separate, visible call your client can gate.
|
|
72
76
|
- **Run it where you like.** Node, a Docker container, or a Cloudflare Worker,
|
|
@@ -49,6 +49,7 @@ interface CatalogSearchEntry {
|
|
|
49
49
|
description?: string;
|
|
50
50
|
inputSchema?: unknown;
|
|
51
51
|
outputSchema?: unknown;
|
|
52
|
+
outputSchemaSource?: "observed";
|
|
52
53
|
inputSchemaTruncated?: true;
|
|
53
54
|
outputSchemaTruncated?: true;
|
|
54
55
|
inputKeys?: string[];
|
|
@@ -112,6 +113,7 @@ export interface CatalogDescription {
|
|
|
112
113
|
guideRequiredReasons?: GuideRequiredReason[];
|
|
113
114
|
inputSchema?: unknown;
|
|
114
115
|
outputSchema?: unknown;
|
|
116
|
+
outputSchemaSource?: "observed";
|
|
115
117
|
annotations?: ToolDef["annotations"];
|
|
116
118
|
error?: string;
|
|
117
119
|
errorDetails?: CatalogDescriptionFailureDetail;
|
|
@@ -149,13 +151,13 @@ export declare class CatalogService {
|
|
|
149
151
|
private readonly loaded;
|
|
150
152
|
private readonly loading;
|
|
151
153
|
constructor(registry: RegistryView, baseUrl: string, options?: {
|
|
152
|
-
requestScope?: object;
|
|
153
|
-
probeTimeoutMs?: number;
|
|
154
|
-
concurrency?: number;
|
|
154
|
+
requestScope?: object | undefined;
|
|
155
|
+
probeTimeoutMs?: number | undefined;
|
|
156
|
+
concurrency?: number | undefined;
|
|
155
157
|
/** The discovery route recovery records name. Default `search_tools`. */
|
|
156
|
-
searchRoute?: SearchRoute;
|
|
158
|
+
searchRoute?: SearchRoute | undefined;
|
|
157
159
|
/** Runtime-owned tail for stale-while-revalidate catalog reads. */
|
|
158
|
-
defer?: DeferredWork;
|
|
160
|
+
defer?: DeferredWork | undefined;
|
|
159
161
|
});
|
|
160
162
|
/**
|
|
161
163
|
* Send a caller back to discovery through the surface it can actually reach.
|
|
@@ -174,6 +176,10 @@ export declare class CatalogService {
|
|
|
174
176
|
}, purpose: string): NonNullable<CallErrorDetails["nextAction"]>;
|
|
175
177
|
loadConnector(id: string, callOptions?: ConnectorOperationOptions): Promise<ToolDef[]>;
|
|
176
178
|
private loadForDiscovery;
|
|
179
|
+
private outputSchema;
|
|
180
|
+
private unknownAddressFailure;
|
|
181
|
+
private catalogLoadFailure;
|
|
182
|
+
private unknownToolFailure;
|
|
177
183
|
resolveTool(address: string, callOptions?: ConnectorOperationOptions): Promise<CatalogResolution>;
|
|
178
184
|
/**
|
|
179
185
|
* Resolve the JavaScript-safe property used by a lazy code-mode namespace
|
|
@@ -203,6 +209,10 @@ export declare function groupedSearchResult(page: CatalogSearchPage): {
|
|
|
203
209
|
};
|
|
204
210
|
matchMode?: "partial";
|
|
205
211
|
nextOffset?: number;
|
|
212
|
+
total: number;
|
|
213
|
+
offset: number;
|
|
214
|
+
limit: number;
|
|
215
|
+
hasMore: boolean;
|
|
206
216
|
connectors: {
|
|
207
217
|
id: string;
|
|
208
218
|
title?: string;
|
|
@@ -210,10 +220,6 @@ export declare function groupedSearchResult(page: CatalogSearchPage): {
|
|
|
210
220
|
guideSummary?: string;
|
|
211
221
|
tools: CatalogSearchEntry["tool"][];
|
|
212
222
|
}[];
|
|
213
|
-
total: number;
|
|
214
|
-
offset: number;
|
|
215
|
-
limit: number;
|
|
216
|
-
hasMore: boolean;
|
|
217
223
|
};
|
|
218
224
|
export declare function flatSearchResult(page: CatalogSearchPage): {
|
|
219
225
|
queryAnalysis?: {
|
|
@@ -234,6 +240,10 @@ export declare function flatSearchResult(page: CatalogSearchPage): {
|
|
|
234
240
|
};
|
|
235
241
|
matchMode?: "partial";
|
|
236
242
|
nextOffset?: number;
|
|
243
|
+
total: number;
|
|
244
|
+
offset: number;
|
|
245
|
+
limit: number;
|
|
246
|
+
hasMore: boolean;
|
|
237
247
|
tools: {
|
|
238
248
|
guideSummary?: string;
|
|
239
249
|
guide?: string;
|
|
@@ -242,6 +252,7 @@ export declare function flatSearchResult(page: CatalogSearchPage): {
|
|
|
242
252
|
description?: string;
|
|
243
253
|
inputSchema?: unknown;
|
|
244
254
|
outputSchema?: unknown;
|
|
255
|
+
outputSchemaSource?: "observed";
|
|
245
256
|
inputSchemaTruncated?: true;
|
|
246
257
|
outputSchemaTruncated?: true;
|
|
247
258
|
inputKeys?: string[];
|
|
@@ -251,9 +262,5 @@ export declare function flatSearchResult(page: CatalogSearchPage): {
|
|
|
251
262
|
guideRequired?: true;
|
|
252
263
|
guideRequiredReasons?: GuideRequiredReason[];
|
|
253
264
|
}[];
|
|
254
|
-
total: number;
|
|
255
|
-
offset: number;
|
|
256
|
-
limit: number;
|
|
257
|
-
hasMore: boolean;
|
|
258
265
|
};
|
|
259
266
|
export {};
|
package/dist/catalog-service.js
CHANGED
|
@@ -2,7 +2,7 @@ import { compactDiscoverySchema, compactSchema, lexicalCorpusStatistics, lexical
|
|
|
2
2
|
import { mapSettledWithConcurrency, resolveDiscoveryConcurrency, } from "./concurrency.js";
|
|
3
3
|
import { boundedEchoText, classifyCallError, framingError, } from "./errors.js";
|
|
4
4
|
import { connectorGuide, connectorGuideRequired, connectorGuideSummary, connectorSkillName, } from "./skills.js";
|
|
5
|
-
import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs,
|
|
5
|
+
import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs, withDeadline, } from "./timeout.js";
|
|
6
6
|
import { isExplicitlyReadOnly } from "./tool-safety.js";
|
|
7
7
|
export const DEFAULT_SEARCH_LIMIT = 8;
|
|
8
8
|
export const MAX_SEARCH_LIMIT = 100;
|
|
@@ -261,22 +261,58 @@ export class CatalogService {
|
|
|
261
261
|
return loading;
|
|
262
262
|
}
|
|
263
263
|
loadForDiscovery(id, label) {
|
|
264
|
-
return
|
|
264
|
+
return withDeadline((signal) => this.loadConnector(id, {
|
|
265
265
|
signal,
|
|
266
266
|
timeoutMs: this.probeTimeoutMs,
|
|
267
|
-
}),
|
|
267
|
+
}), {
|
|
268
|
+
timeoutMs: this.probeTimeoutMs,
|
|
269
|
+
timeoutError: new Error(`${label} timed out after ${this.probeTimeoutMs}ms`),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
outputSchema(connectorId, tool) {
|
|
273
|
+
if (tool.outputSchema)
|
|
274
|
+
return { schema: tool.outputSchema };
|
|
275
|
+
const observed = this.registry.observedOutputSchema(connectorId, tool);
|
|
276
|
+
return observed
|
|
277
|
+
? { schema: observed, source: "observed" }
|
|
278
|
+
: {};
|
|
279
|
+
}
|
|
280
|
+
unknownAddressFailure(address, query) {
|
|
281
|
+
return {
|
|
282
|
+
ok: false,
|
|
283
|
+
error: {
|
|
284
|
+
...framingError("unknown_address", `Unknown address "${boundedEchoText(address)}"`),
|
|
285
|
+
nextAction: this.searchRecovery({ query: recoveryQuery(query) }, "Find the configured canonical address before retrying."),
|
|
286
|
+
},
|
|
287
|
+
catalogMs: 0,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
catalogLoadFailure(cause, started, connector, toolName) {
|
|
291
|
+
return {
|
|
292
|
+
ok: false,
|
|
293
|
+
error: classifyCallError(cause, "catalog_lookup_failed"),
|
|
294
|
+
catalogMs: Date.now() - started,
|
|
295
|
+
connector,
|
|
296
|
+
toolName,
|
|
297
|
+
cause,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
unknownToolFailure(toolName, connector, started) {
|
|
301
|
+
return {
|
|
302
|
+
ok: false,
|
|
303
|
+
error: {
|
|
304
|
+
...framingError("unknown_tool", `Unknown tool "${boundedEchoText(toolName)}" on connector "${connector.id}"`),
|
|
305
|
+
nextAction: this.searchRecovery({ query: recoveryQuery(toolName), connector: connector.id }, "Find the connector's current canonical tool address."),
|
|
306
|
+
},
|
|
307
|
+
catalogMs: Date.now() - started,
|
|
308
|
+
connector,
|
|
309
|
+
toolName,
|
|
310
|
+
};
|
|
268
311
|
}
|
|
269
312
|
async resolveTool(address, callOptions = {}) {
|
|
270
313
|
const resolved = this.registry.resolveAddress(address);
|
|
271
314
|
if (!resolved) {
|
|
272
|
-
return
|
|
273
|
-
ok: false,
|
|
274
|
-
error: {
|
|
275
|
-
...framingError("unknown_address", `Unknown address "${boundedEchoText(address)}"`),
|
|
276
|
-
nextAction: this.searchRecovery({ query: recoveryQuery(address) }, "Find the configured canonical address before retrying."),
|
|
277
|
-
},
|
|
278
|
-
catalogMs: 0,
|
|
279
|
-
};
|
|
315
|
+
return this.unknownAddressFailure(address, address);
|
|
280
316
|
}
|
|
281
317
|
const started = Date.now();
|
|
282
318
|
let tools;
|
|
@@ -284,30 +320,11 @@ export class CatalogService {
|
|
|
284
320
|
tools = await this.loadConnector(resolved.connector.id, callOptions);
|
|
285
321
|
}
|
|
286
322
|
catch (cause) {
|
|
287
|
-
return
|
|
288
|
-
ok: false,
|
|
289
|
-
error: classifyCallError(cause, "catalog_lookup_failed"),
|
|
290
|
-
catalogMs: Date.now() - started,
|
|
291
|
-
connector: resolved.connector,
|
|
292
|
-
toolName: resolved.toolName,
|
|
293
|
-
cause,
|
|
294
|
-
};
|
|
323
|
+
return this.catalogLoadFailure(cause, started, resolved.connector, resolved.toolName);
|
|
295
324
|
}
|
|
296
325
|
const definition = tools.find((tool) => tool.name === resolved.toolName);
|
|
297
326
|
if (!definition) {
|
|
298
|
-
return
|
|
299
|
-
ok: false,
|
|
300
|
-
error: {
|
|
301
|
-
...framingError("unknown_tool", `Unknown tool "${boundedEchoText(resolved.toolName)}" on connector "${resolved.connector.id}"`),
|
|
302
|
-
nextAction: this.searchRecovery({
|
|
303
|
-
query: recoveryQuery(resolved.toolName),
|
|
304
|
-
connector: resolved.connector.id,
|
|
305
|
-
}, "Find the connector's current canonical tool address."),
|
|
306
|
-
},
|
|
307
|
-
catalogMs: Date.now() - started,
|
|
308
|
-
connector: resolved.connector,
|
|
309
|
-
toolName: resolved.toolName,
|
|
310
|
-
};
|
|
327
|
+
return this.unknownToolFailure(resolved.toolName, resolved.connector, started);
|
|
311
328
|
}
|
|
312
329
|
return {
|
|
313
330
|
ok: true,
|
|
@@ -327,14 +344,7 @@ export class CatalogService {
|
|
|
327
344
|
async resolveToolAlias(connectorId, alias, aliasFor, callOptions = {}) {
|
|
328
345
|
const connector = this.registry.getConnector(connectorId);
|
|
329
346
|
if (!connector) {
|
|
330
|
-
return {
|
|
331
|
-
ok: false,
|
|
332
|
-
error: {
|
|
333
|
-
...framingError("unknown_address", `Unknown address "${boundedEchoText(`${connectorId}.${alias}`)}"`),
|
|
334
|
-
nextAction: this.searchRecovery({ query: recoveryQuery(alias) }, "Find the configured canonical address before retrying."),
|
|
335
|
-
},
|
|
336
|
-
catalogMs: 0,
|
|
337
|
-
};
|
|
347
|
+
return this.unknownAddressFailure(`${connectorId}.${alias}`, alias);
|
|
338
348
|
}
|
|
339
349
|
const started = Date.now();
|
|
340
350
|
let tools;
|
|
@@ -342,27 +352,11 @@ export class CatalogService {
|
|
|
342
352
|
tools = await this.loadConnector(connector.id, callOptions);
|
|
343
353
|
}
|
|
344
354
|
catch (cause) {
|
|
345
|
-
return
|
|
346
|
-
ok: false,
|
|
347
|
-
error: classifyCallError(cause, "catalog_lookup_failed"),
|
|
348
|
-
catalogMs: Date.now() - started,
|
|
349
|
-
connector,
|
|
350
|
-
toolName: alias,
|
|
351
|
-
cause,
|
|
352
|
-
};
|
|
355
|
+
return this.catalogLoadFailure(cause, started, connector, alias);
|
|
353
356
|
}
|
|
354
357
|
const [definition, ...collisions] = tools.filter((tool) => aliasFor(tool.name) === alias);
|
|
355
358
|
if (!definition) {
|
|
356
|
-
return
|
|
357
|
-
ok: false,
|
|
358
|
-
error: {
|
|
359
|
-
...framingError("unknown_tool", `Unknown tool "${boundedEchoText(alias)}" on connector "${connector.id}"`),
|
|
360
|
-
nextAction: this.searchRecovery({ query: recoveryQuery(alias), connector: connector.id }, "Find the connector's current canonical tool address."),
|
|
361
|
-
},
|
|
362
|
-
catalogMs: Date.now() - started,
|
|
363
|
-
connector,
|
|
364
|
-
toolName: alias,
|
|
365
|
-
};
|
|
359
|
+
return this.unknownToolFailure(alias, connector, started);
|
|
366
360
|
}
|
|
367
361
|
if (collisions.length > 0) {
|
|
368
362
|
const names = [definition, ...collisions]
|
|
@@ -486,16 +480,20 @@ export class CatalogService {
|
|
|
486
480
|
a.order - b.order);
|
|
487
481
|
});
|
|
488
482
|
const pageMatches = matches.slice(offset, offset + limit);
|
|
483
|
+
const withSchemas = args.includeSchemas;
|
|
489
484
|
const entries = pageMatches.map((match) => {
|
|
485
|
+
const output = withSchemas
|
|
486
|
+
? this.outputSchema(match.connector.id, match.tool)
|
|
487
|
+
: {};
|
|
490
488
|
const input = match.tool.inputSchema ?? { type: "object" };
|
|
491
|
-
const renderedInput =
|
|
492
|
-
? renderSearchSchema(input,
|
|
489
|
+
const renderedInput = withSchemas
|
|
490
|
+
? renderSearchSchema(input, withSchemas)
|
|
493
491
|
: undefined;
|
|
494
|
-
const renderedOutput =
|
|
495
|
-
? renderSearchSchema(
|
|
492
|
+
const renderedOutput = withSchemas && output.schema
|
|
493
|
+
? renderSearchSchema(output.schema, withSchemas)
|
|
496
494
|
: undefined;
|
|
497
|
-
const schemaKeys =
|
|
498
|
-
? schemaKeyMetadata(input,
|
|
495
|
+
const schemaKeys = withSchemas && args.includeSchemaKeys
|
|
496
|
+
? schemaKeyMetadata(input, output.schema)
|
|
499
497
|
: undefined;
|
|
500
498
|
const description = summarizeDiscoveryDescription(match.tool.description, args.fullDescriptions === true);
|
|
501
499
|
const requiredReasons = guideRequiredReasons(match.connector, match.tool, renderedInput?.truncated === true || renderedOutput?.truncated === true);
|
|
@@ -512,7 +510,7 @@ export class CatalogService {
|
|
|
512
510
|
name: match.tool.name,
|
|
513
511
|
address: `${match.connector.id}.${match.tool.name}`,
|
|
514
512
|
...(description !== undefined ? { description } : {}),
|
|
515
|
-
...(
|
|
513
|
+
...(withSchemas
|
|
516
514
|
? {
|
|
517
515
|
inputSchema: renderedInput?.schema,
|
|
518
516
|
}
|
|
@@ -520,11 +518,14 @@ export class CatalogService {
|
|
|
520
518
|
...(renderedInput?.truncated
|
|
521
519
|
? { inputSchemaTruncated: true }
|
|
522
520
|
: {}),
|
|
523
|
-
...(
|
|
521
|
+
...(withSchemas && output.schema
|
|
524
522
|
? {
|
|
525
523
|
outputSchema: renderedOutput?.schema,
|
|
526
524
|
}
|
|
527
525
|
: {}),
|
|
526
|
+
...(withSchemas && output.source
|
|
527
|
+
? { outputSchemaSource: output.source }
|
|
528
|
+
: {}),
|
|
528
529
|
...(renderedOutput?.truncated
|
|
529
530
|
? { outputSchemaTruncated: true }
|
|
530
531
|
: {}),
|
|
@@ -657,41 +658,46 @@ export class CatalogService {
|
|
|
657
658
|
(queryTerms.length > 0
|
|
658
659
|
? matchMode === "partial"
|
|
659
660
|
: unknownConnectorGuidance !== undefined || unavailableCatalogs > 0);
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
661
|
+
const searchGuidance = () => {
|
|
662
|
+
if (unknownConnectorGuidance)
|
|
663
|
+
return unknownConnectorGuidance;
|
|
664
|
+
if (unsearchableQuery) {
|
|
665
|
+
return scopedConnector && unavailableCatalogs > 0
|
|
663
666
|
? `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
664
|
-
: "The query contained no searchable lexical terms. Use 2–4 ASCII action/object terms, or browse with an empty query."
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
667
|
+
: "The query contained no searchable lexical terms. Use 2–4 ASCII action/object terms, or browse with an empty query.";
|
|
668
|
+
}
|
|
669
|
+
if (queryTerms.length === 0) {
|
|
670
|
+
if (unavailableCatalogs === 0)
|
|
671
|
+
return undefined;
|
|
672
|
+
return scopedConnector
|
|
673
|
+
? `Connector "${scopedConnector.id}" could not be browsed because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
674
|
+
: `${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable, so this browse is incomplete. Scope by connector to see the typed reason.`;
|
|
675
|
+
}
|
|
676
|
+
if (matches.length === 0) {
|
|
677
|
+
if (scopedConnector) {
|
|
678
|
+
if (unavailableCatalogs > 0) {
|
|
679
|
+
return `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`;
|
|
680
|
+
}
|
|
681
|
+
return scopedGuide?.required
|
|
682
|
+
? `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}`
|
|
683
|
+
: `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Refine terms or browse it with an empty query.${filterRecovery}`;
|
|
684
|
+
}
|
|
685
|
+
if (identityGuidance)
|
|
686
|
+
return identityGuidance;
|
|
687
|
+
return unavailableCatalogs === 0
|
|
688
|
+
? `No matching ${safetyLabel}capability is configured in this deployment. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`
|
|
689
|
+
: `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}`;
|
|
690
|
+
}
|
|
691
|
+
if (matchMode !== "partial")
|
|
692
|
+
return undefined;
|
|
693
|
+
if (scopedConnector) {
|
|
694
|
+
return `No single tool on connector "${scopedConnector.id}" matched every term. Split distinct intents into separate searches.`;
|
|
695
|
+
}
|
|
696
|
+
return unavailableCatalogs === 0
|
|
697
|
+
? "No single tool matched every term. Split distinct intents into separate searches."
|
|
698
|
+
: "No single tool matched every term in the catalogs that answered. Split distinct intents into separate searches.";
|
|
699
|
+
};
|
|
700
|
+
const guidance = searchGuidance();
|
|
695
701
|
return {
|
|
696
702
|
entries,
|
|
697
703
|
total: matches.length,
|
|
@@ -813,6 +819,7 @@ export class CatalogService {
|
|
|
813
819
|
};
|
|
814
820
|
}
|
|
815
821
|
const input = tool.inputSchema ?? { type: "object" };
|
|
822
|
+
const output = this.outputSchema(addressResolution.connector.id, tool);
|
|
816
823
|
const description = summarizeDescription(tool.description, args.fullDescriptions === true);
|
|
817
824
|
const requiredReasons = guideRequiredReasons(addressResolution.connector, tool, false);
|
|
818
825
|
const guideSummary = connectorGuideSummary(addressResolution.connector);
|
|
@@ -833,16 +840,28 @@ export class CatalogService {
|
|
|
833
840
|
}
|
|
834
841
|
: {}),
|
|
835
842
|
inputSchema: renderSchema(input, format),
|
|
836
|
-
...(
|
|
843
|
+
...(output.schema
|
|
837
844
|
? {
|
|
838
|
-
outputSchema: renderSchema(
|
|
845
|
+
outputSchema: renderSchema(output.schema, format),
|
|
839
846
|
}
|
|
840
847
|
: {}),
|
|
848
|
+
...(output.source ? { outputSchemaSource: output.source } : {}),
|
|
841
849
|
...(tool.annotations ? { annotations: tool.annotations } : {}),
|
|
842
850
|
};
|
|
843
851
|
});
|
|
844
852
|
}
|
|
845
853
|
}
|
|
854
|
+
function pageTail(page) {
|
|
855
|
+
return {
|
|
856
|
+
total: page.total,
|
|
857
|
+
offset: page.offset,
|
|
858
|
+
limit: page.limit,
|
|
859
|
+
hasMore: page.hasMore,
|
|
860
|
+
...(page.nextOffset !== undefined ? { nextOffset: page.nextOffset } : {}),
|
|
861
|
+
...(page.matchMode ? { matchMode: page.matchMode } : {}),
|
|
862
|
+
...(page.queryAnalysis ? { queryAnalysis: page.queryAnalysis } : {}),
|
|
863
|
+
};
|
|
864
|
+
}
|
|
846
865
|
export function groupedSearchResult(page) {
|
|
847
866
|
const groups = [];
|
|
848
867
|
const byConnector = new Map();
|
|
@@ -868,13 +887,7 @@ export function groupedSearchResult(page) {
|
|
|
868
887
|
}
|
|
869
888
|
return {
|
|
870
889
|
connectors: groups,
|
|
871
|
-
|
|
872
|
-
offset: page.offset,
|
|
873
|
-
limit: page.limit,
|
|
874
|
-
hasMore: page.hasMore,
|
|
875
|
-
...(page.nextOffset !== undefined ? { nextOffset: page.nextOffset } : {}),
|
|
876
|
-
...(page.matchMode ? { matchMode: page.matchMode } : {}),
|
|
877
|
-
...(page.queryAnalysis ? { queryAnalysis: page.queryAnalysis } : {}),
|
|
890
|
+
...pageTail(page),
|
|
878
891
|
};
|
|
879
892
|
}
|
|
880
893
|
export function flatSearchResult(page) {
|
|
@@ -886,12 +899,6 @@ export function flatSearchResult(page) {
|
|
|
886
899
|
? { guideSummary: entry.guideSummary }
|
|
887
900
|
: {}),
|
|
888
901
|
})),
|
|
889
|
-
|
|
890
|
-
offset: page.offset,
|
|
891
|
-
limit: page.limit,
|
|
892
|
-
hasMore: page.hasMore,
|
|
893
|
-
...(page.nextOffset !== undefined ? { nextOffset: page.nextOffset } : {}),
|
|
894
|
-
...(page.matchMode ? { matchMode: page.matchMode } : {}),
|
|
895
|
-
...(page.queryAnalysis ? { queryAnalysis: page.queryAnalysis } : {}),
|
|
902
|
+
...pageTail(page),
|
|
896
903
|
};
|
|
897
904
|
}
|