@zackbart/connecta 0.24.2 → 0.24.4
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 +18 -20
- package/CHANGELOG.md +205 -1
- package/README.md +5 -6
- package/dist/auth/bearer.js +2 -0
- package/dist/auth/downstream-oauth.d.ts +12 -1
- package/dist/auth/downstream-oauth.js +147 -35
- package/dist/branding.d.ts +31 -2
- package/dist/branding.js +116 -8
- package/dist/call-admission.d.ts +4 -0
- package/dist/call-admission.js +26 -0
- package/dist/catalog-drift.js +9 -4
- package/dist/catalog-service.d.ts +2 -0
- package/dist/catalog-service.js +25 -8
- package/dist/catalog.d.ts +2 -0
- package/dist/catalog.js +246 -121
- package/dist/connectors/api.d.ts +1 -1
- package/dist/connectors/api.js +21 -3
- package/dist/connectors/guarded-fetch.d.ts +6 -2
- package/dist/connectors/guarded-fetch.js +61 -24
- package/dist/connectors/remote-mcp.js +92 -57
- package/dist/errors.d.ts +28 -3
- package/dist/errors.js +60 -1
- package/dist/execute.js +85 -23
- package/dist/executor-result.js +3 -1
- package/dist/executors/quickjs-child.js +5 -1
- package/dist/executors/quickjs-protocol.d.ts +4 -0
- package/dist/executors/quickjs-runtime.d.ts +1 -1
- package/dist/executors/quickjs-runtime.js +38 -21
- package/dist/executors/quickjs.js +68 -27
- package/dist/index.d.ts +15 -1
- package/dist/index.js +36 -4
- package/dist/invocation.js +134 -93
- package/dist/mcp-result.js +3 -2
- package/dist/meta-tools.js +191 -36
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/view.d.ts +38 -1
- package/dist/operator-ui/view.js +71 -0
- package/dist/providers/cloudflare.d.ts +14 -2
- package/dist/providers/cloudflare.js +107 -16
- package/dist/providers/linear.d.ts +26 -4
- package/dist/providers/linear.js +19 -4
- package/dist/providers/mixpanel.d.ts +16 -3
- package/dist/providers/mixpanel.js +13 -2
- package/dist/providers/notion.d.ts +8 -1
- package/dist/providers/notion.js +83 -10
- package/dist/providers/revenuecat.d.ts +30 -4
- package/dist/providers/revenuecat.js +42 -4
- package/dist/providers/stripe.d.ts +7 -1
- package/dist/providers/stripe.js +30 -4
- package/dist/providers/vercel.js +11 -1
- package/dist/registry.d.ts +22 -2
- package/dist/registry.js +101 -13
- package/dist/routes/mcp.d.ts +4 -1
- package/dist/routes/mcp.js +84 -13
- package/dist/routes/oauth.js +4 -0
- package/dist/routes/shared.d.ts +1 -0
- package/dist/routes/shared.js +4 -4
- package/dist/server.js +15 -3
- package/dist/skills.js +6 -5
- package/dist/storage/file.d.ts +6 -2
- package/dist/storage/file.js +312 -34
- package/dist/storage/memory.js +12 -1
- package/dist/types.d.ts +37 -0
- package/dist/ui.js +18 -10
- package/dist/validate.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +193 -165
- package/documentation/auth.md +199 -145
- package/documentation/code-mode.md +433 -316
- package/documentation/meta-tools.md +363 -355
- package/examples/worker/AGENTS.md +2 -1
- package/examples/worker/README.md +12 -10
- package/examples/worker/src/index.ts +12 -15
- package/package.json +1 -2
- package/templates/node/.env.example +3 -3
- package/templates/node/AGENTS.md +5 -4
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +23 -22
- package/documentation/call-admission.md +0 -142
- package/documentation/cloudflare.md +0 -471
- package/documentation/connector-guides.md +0 -176
- package/documentation/connectors.md +0 -389
- package/documentation/linear.md +0 -193
- package/documentation/mixpanel.md +0 -160
- package/documentation/notion.md +0 -308
- package/documentation/operations.md +0 -359
- package/documentation/operator-ui.md +0 -135
- package/documentation/optional-modules-upgrade.md +0 -243
- package/documentation/provider-conventions.md +0 -722
- package/documentation/request-admission.md +0 -170
- package/documentation/revenuecat.md +0 -305
- package/documentation/storage-and-credentials.md +0 -201
- package/documentation/stripe.md +0 -262
- package/documentation/upgrading.md +0 -754
- package/documentation/vercel.md +0 -241
package/dist/branding.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import type { ConnectaBranding, UiAuthConfig } from "./types.js";
|
|
1
|
+
import type { ConnectaBranding, ConnectaTheme, UiAuthConfig } from "./types.js";
|
|
2
2
|
/** Connecta's default monochrome "C" mark. */
|
|
3
3
|
export declare const CONNECTA_FAVICON_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\">\n <style>\n .fg { fill: #000 }\n @media (prefers-color-scheme: dark) { .fg { fill: #fff } }\n </style>\n <path class=\"fg\" d=\"M27 9.4A13 13 0 1 0 27 22.6l-4.4-2.5a8 8 0 1 1 0-8.2z\"/>\n</svg>";
|
|
4
|
+
export interface ResolvedTheme {
|
|
5
|
+
accent?: string;
|
|
6
|
+
radius?: string;
|
|
7
|
+
fontFamily?: string;
|
|
8
|
+
monoFamily?: string;
|
|
9
|
+
colorScheme: "system" | "light" | "dark";
|
|
10
|
+
}
|
|
4
11
|
interface ResolvedBranding {
|
|
5
12
|
productName: string;
|
|
6
13
|
productUrl?: string;
|
|
@@ -12,8 +19,30 @@ interface ResolvedBranding {
|
|
|
12
19
|
/** href for the page's icon link. */
|
|
13
20
|
faviconHref: string;
|
|
14
21
|
themeColor: string;
|
|
22
|
+
/** Only the tokens that survived their gate; the stylesheet owns the rest. */
|
|
23
|
+
theme: ResolvedTheme;
|
|
15
24
|
}
|
|
16
25
|
export declare function resolveBranding(branding?: ConnectaBranding): ResolvedBranding;
|
|
26
|
+
/**
|
|
27
|
+
* Read a theme the way branding URLs are read: gate every field, drop what
|
|
28
|
+
* fails, never throw. This runs during `createConnecta`, so a malformed value
|
|
29
|
+
* has to fall back to the stylesheet default instead of refusing to serve the
|
|
30
|
+
* page. `droppedThemeTokens` names the drops for the startup warning.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveTheme(theme?: ConnectaTheme): ResolvedTheme;
|
|
33
|
+
/**
|
|
34
|
+
* Names of the theme tokens the operator set that failed their gate. Same
|
|
35
|
+
* contract as `droppedBrandingUrls`: rendering falls back silently, so this is
|
|
36
|
+
* the only place an operator learns their value never reached the page.
|
|
37
|
+
*/
|
|
38
|
+
export declare function droppedThemeTokens(theme?: ConnectaTheme): string[];
|
|
39
|
+
/**
|
|
40
|
+
* The resolved theme as a `:root` block, or "" when a deployment configured
|
|
41
|
+
* nothing. It is emitted after the stylesheet so it overrides the defaults.
|
|
42
|
+
* There is no escaping here: every value has already passed a gate above, and
|
|
43
|
+
* anything that would need escaping is dropped rather than rewritten.
|
|
44
|
+
*/
|
|
45
|
+
export declare function themeCss(theme: ResolvedTheme): string;
|
|
17
46
|
/**
|
|
18
47
|
* Names of the branding URLs the operator set that failed their gate and were
|
|
19
48
|
* replaced by a default. Lives beside the gates so the startup warning cannot
|
|
@@ -55,7 +84,7 @@ export declare function isSafeHttpsUrl(url: unknown): boolean;
|
|
|
55
84
|
* `frontendApiUrl` is required, so anything that fails its gate is a drop.
|
|
56
85
|
* `signInUrl` and `signUpUrl` are optional, so only a value the operator
|
|
57
86
|
* *supplied* and the gate then rejected is worth a warning — an unset field
|
|
58
|
-
* took no default away from anyone. `
|
|
87
|
+
* took no default away from anyone. `isSetValue` decides that, the same way
|
|
59
88
|
* and for the same reasons it decides it for the branding URLs: a warning that
|
|
60
89
|
* fires for one and not the other would be reporting on the field rather than
|
|
61
90
|
* on the operator's intent. Rendering is not consulted for this: it drops on
|
package/dist/branding.js
CHANGED
|
@@ -38,17 +38,125 @@ export function resolveBranding(branding) {
|
|
|
38
38
|
? faviconHref
|
|
39
39
|
: DEFAULT_FAVICON_HREF,
|
|
40
40
|
themeColor: trimmedString(branding?.themeColor) ?? "#ffffff",
|
|
41
|
+
theme: resolveTheme(branding?.theme),
|
|
41
42
|
};
|
|
42
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Hex colors only: `#rgb`, `#rrggbb`, `#rrggbbaa`. A hex value cannot carry a
|
|
46
|
+
* `url()`, a `var()`, or a closing brace into the `:root` block it is written
|
|
47
|
+
* into, which is the whole reason the gate is this narrow.
|
|
48
|
+
*/
|
|
49
|
+
const HEX_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
50
|
+
/** A single non-negative CSS length, or a bare number the caller reads as px. */
|
|
51
|
+
const CSS_LENGTH = /^(?:0|[0-9]{1,3}(?:\.[0-9]{1,3})?)(px|rem|em)?$/;
|
|
52
|
+
/**
|
|
53
|
+
* One family name: bare, or wrapped in matching quotes. The character class
|
|
54
|
+
* excludes everything CSS needs to end a declaration or open a function (`;`,
|
|
55
|
+
* `{`, `}`, `(`, `)`, backslash, `<`, `>`, `@`, `*`, `/`, `:`), and matched
|
|
56
|
+
* quotes mean the value cannot leave an open string that swallows the CSS
|
|
57
|
+
* after it.
|
|
58
|
+
*/
|
|
59
|
+
const FONT_NAME = /^(?:"[a-z0-9 ._-]+"|'[a-z0-9 ._-]+'|[a-z][a-z0-9 ._-]*)$/i;
|
|
60
|
+
/** A font-family list: comma-separated names and nothing else. */
|
|
61
|
+
function isFontStack(value) {
|
|
62
|
+
if (value.length > 200)
|
|
63
|
+
return false;
|
|
64
|
+
const names = value.split(",");
|
|
65
|
+
return names.length <= 12 &&
|
|
66
|
+
names.every((name) => FONT_NAME.test(name.trim()));
|
|
67
|
+
}
|
|
68
|
+
const COLOR_SCHEMES = ["system", "light", "dark"];
|
|
69
|
+
/**
|
|
70
|
+
* Read a theme the way branding URLs are read: gate every field, drop what
|
|
71
|
+
* fails, never throw. This runs during `createConnecta`, so a malformed value
|
|
72
|
+
* has to fall back to the stylesheet default instead of refusing to serve the
|
|
73
|
+
* page. `droppedThemeTokens` names the drops for the startup warning.
|
|
74
|
+
*/
|
|
75
|
+
export function resolveTheme(theme) {
|
|
76
|
+
const accent = trimmedString(theme?.accent);
|
|
77
|
+
const fontFamily = trimmedString(theme?.fontFamily);
|
|
78
|
+
const monoFamily = trimmedString(theme?.monoFamily);
|
|
79
|
+
const scheme = trimmedString(theme?.colorScheme);
|
|
80
|
+
const radius = radiusLength(theme?.radius);
|
|
81
|
+
return {
|
|
82
|
+
...(accent && HEX_COLOR.test(accent) ? { accent } : {}),
|
|
83
|
+
...(radius !== undefined ? { radius } : {}),
|
|
84
|
+
...(fontFamily && isFontStack(fontFamily) ? { fontFamily } : {}),
|
|
85
|
+
...(monoFamily && isFontStack(monoFamily) ? { monoFamily } : {}),
|
|
86
|
+
colorScheme: COLOR_SCHEMES.includes(scheme)
|
|
87
|
+
? scheme
|
|
88
|
+
: "system",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* `radius` accepts a number as well as a string, since a config file is more
|
|
93
|
+
* likely to say `10` than `"10px"`. A bare number means pixels; a string must
|
|
94
|
+
* carry its own unit or be zero.
|
|
95
|
+
*/
|
|
96
|
+
function radiusLength(radius) {
|
|
97
|
+
if (typeof radius === "number") {
|
|
98
|
+
return Number.isFinite(radius) && radius >= 0 && radius <= 999
|
|
99
|
+
? `${radius}px`
|
|
100
|
+
: undefined;
|
|
101
|
+
}
|
|
102
|
+
const value = trimmedString(radius);
|
|
103
|
+
if (!value || !CSS_LENGTH.test(value))
|
|
104
|
+
return undefined;
|
|
105
|
+
return /[a-z]$/i.test(value) || value === "0" ? value : `${value}px`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Names of the theme tokens the operator set that failed their gate. Same
|
|
109
|
+
* contract as `droppedBrandingUrls`: rendering falls back silently, so this is
|
|
110
|
+
* the only place an operator learns their value never reached the page.
|
|
111
|
+
*/
|
|
112
|
+
export function droppedThemeTokens(theme) {
|
|
113
|
+
if (!theme)
|
|
114
|
+
return [];
|
|
115
|
+
const resolved = resolveTheme(theme);
|
|
116
|
+
const dropped = [];
|
|
117
|
+
if (isSetValue(theme.accent) && !resolved.accent)
|
|
118
|
+
dropped.push("accent");
|
|
119
|
+
if (isSetValue(theme.radius) && resolved.radius === undefined) {
|
|
120
|
+
dropped.push("radius");
|
|
121
|
+
}
|
|
122
|
+
if (isSetValue(theme.fontFamily) && !resolved.fontFamily) {
|
|
123
|
+
dropped.push("fontFamily");
|
|
124
|
+
}
|
|
125
|
+
if (isSetValue(theme.monoFamily) && !resolved.monoFamily) {
|
|
126
|
+
dropped.push("monoFamily");
|
|
127
|
+
}
|
|
128
|
+
// Compared against the trimmed value the resolver reads, so `" dark "` is
|
|
129
|
+
// not reported as dropped when it was applied.
|
|
130
|
+
if (isSetValue(theme.colorScheme) &&
|
|
131
|
+
trimmedString(theme.colorScheme) !== resolved.colorScheme) {
|
|
132
|
+
dropped.push("colorScheme");
|
|
133
|
+
}
|
|
134
|
+
return dropped.map((token) => `theme.${token}`);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The resolved theme as a `:root` block, or "" when a deployment configured
|
|
138
|
+
* nothing. It is emitted after the stylesheet so it overrides the defaults.
|
|
139
|
+
* There is no escaping here: every value has already passed a gate above, and
|
|
140
|
+
* anything that would need escaping is dropped rather than rewritten.
|
|
141
|
+
*/
|
|
142
|
+
export function themeCss(theme) {
|
|
143
|
+
const declarations = [
|
|
144
|
+
theme.accent ? `--accent:${theme.accent}` : "",
|
|
145
|
+
theme.radius !== undefined ? `--radius:${theme.radius}` : "",
|
|
146
|
+
theme.fontFamily ? `--sans:${theme.fontFamily}` : "",
|
|
147
|
+
theme.monoFamily ? `--mono:${theme.monoFamily}` : "",
|
|
148
|
+
].filter(Boolean);
|
|
149
|
+
return declarations.length ? `:root{${declarations.join(";")}}` : "";
|
|
150
|
+
}
|
|
43
151
|
/**
|
|
44
152
|
* Whether the operator meant to supply a value here — the question every
|
|
45
|
-
* dropped-
|
|
153
|
+
* dropped-value warning asks before naming a field, and one definition so the
|
|
46
154
|
* branding and `uiAuth` warnings cannot answer it differently. A non-string
|
|
47
155
|
* counts as set: the intent was there and is exactly what the warning reports
|
|
48
156
|
* on. A blank or whitespace-only string does not; that is indistinguishable
|
|
49
157
|
* from leaving the field alone, and both take the default silently.
|
|
50
158
|
*/
|
|
51
|
-
function
|
|
159
|
+
function isSetValue(value) {
|
|
52
160
|
return typeof value === "string"
|
|
53
161
|
? trimmedString(value) !== undefined
|
|
54
162
|
: value !== undefined && value !== null;
|
|
@@ -65,13 +173,13 @@ export function droppedBrandingUrls(branding) {
|
|
|
65
173
|
const resolved = resolveBranding(branding);
|
|
66
174
|
const faviconHref = branding.favicon?.href;
|
|
67
175
|
return [
|
|
68
|
-
...(
|
|
176
|
+
...(isSetValue(branding.productUrl) && !resolved.productUrl
|
|
69
177
|
? ["productUrl"]
|
|
70
178
|
: []),
|
|
71
|
-
...(
|
|
179
|
+
...(isSetValue(branding.ownerUrl) && !resolved.ownerUrl
|
|
72
180
|
? ["ownerUrl"]
|
|
73
181
|
: []),
|
|
74
|
-
...(
|
|
182
|
+
...(isSetValue(faviconHref) &&
|
|
75
183
|
trimmedString(faviconHref) !== resolved.faviconHref
|
|
76
184
|
? ["favicon.href"]
|
|
77
185
|
: []),
|
|
@@ -153,7 +261,7 @@ export function isSafeHttpsUrl(url) {
|
|
|
153
261
|
* `frontendApiUrl` is required, so anything that fails its gate is a drop.
|
|
154
262
|
* `signInUrl` and `signUpUrl` are optional, so only a value the operator
|
|
155
263
|
* *supplied* and the gate then rejected is worth a warning — an unset field
|
|
156
|
-
* took no default away from anyone. `
|
|
264
|
+
* took no default away from anyone. `isSetValue` decides that, the same way
|
|
157
265
|
* and for the same reasons it decides it for the branding URLs: a warning that
|
|
158
266
|
* fires for one and not the other would be reporting on the field rather than
|
|
159
267
|
* on the operator's intent. Rendering is not consulted for this: it drops on
|
|
@@ -166,10 +274,10 @@ export function droppedUiAuthUrls(uiAuth) {
|
|
|
166
274
|
return [];
|
|
167
275
|
return [
|
|
168
276
|
...(isSafeHttpsUrl(uiAuth.frontendApiUrl) ? [] : ["uiAuth.frontendApiUrl"]),
|
|
169
|
-
...(
|
|
277
|
+
...(isSetValue(uiAuth.signInUrl) && !isSafeHttpsUrl(uiAuth.signInUrl)
|
|
170
278
|
? ["uiAuth.signInUrl"]
|
|
171
279
|
: []),
|
|
172
|
-
...(
|
|
280
|
+
...(isSetValue(uiAuth.signUpUrl) && !isSafeHttpsUrl(uiAuth.signUpUrl)
|
|
173
281
|
? ["uiAuth.signUpUrl"]
|
|
174
282
|
: []),
|
|
175
283
|
];
|
package/dist/call-admission.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export interface ConnectorCallAdmissionSnapshot {
|
|
|
36
36
|
max: number;
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
+
/** Sum gauges and counters without publishing connector or principal keys. */
|
|
40
|
+
export declare function aggregateCallAdmissionSnapshots(snapshots: readonly ConnectorCallAdmissionSnapshot[]): ConnectorCallAdmissionSnapshot;
|
|
39
41
|
/**
|
|
40
42
|
* Per-runtime, per-connector call admission. State contains only bounded
|
|
41
43
|
* partition keys, counters, timestamps, signals, and promise continuations;
|
|
@@ -64,6 +66,8 @@ export declare class ConnectorCallAdmissionController {
|
|
|
64
66
|
acquire(input: Readonly<ConnectorCallAdmissionInput> & {
|
|
65
67
|
signal?: AbortSignal;
|
|
66
68
|
}): Promise<CallAdmissionPermit>;
|
|
69
|
+
/** A principal registry may be evicted only after calls and budgets drain. */
|
|
70
|
+
isIdle(): boolean;
|
|
67
71
|
snapshot(): ConnectorCallAdmissionSnapshot;
|
|
68
72
|
close(): void;
|
|
69
73
|
private admit;
|
package/dist/call-admission.js
CHANGED
|
@@ -22,6 +22,27 @@ export class CallAdmissionError extends ConnectorCallError {
|
|
|
22
22
|
export function isCallAdmissionError(error) {
|
|
23
23
|
return error instanceof CallAdmissionError;
|
|
24
24
|
}
|
|
25
|
+
/** Sum gauges and counters without publishing connector or principal keys. */
|
|
26
|
+
export function aggregateCallAdmissionSnapshots(snapshots) {
|
|
27
|
+
const aggregate = {
|
|
28
|
+
rules: 0, partitions: 0, active: 0, queued: 0, closed: snapshots.length > 0,
|
|
29
|
+
totals: { admitted: 0, queued: 0, rejected: 0, rateLimited: 0, cancelled: 0 },
|
|
30
|
+
queueWaitMs: { count: 0, total: 0, max: 0 },
|
|
31
|
+
};
|
|
32
|
+
for (const snapshot of snapshots) {
|
|
33
|
+
for (const key of ["rules", "partitions", "active", "queued"]) {
|
|
34
|
+
aggregate[key] += snapshot[key];
|
|
35
|
+
}
|
|
36
|
+
aggregate.closed &&= snapshot.closed;
|
|
37
|
+
for (const key of ["admitted", "queued", "rejected", "rateLimited", "cancelled"]) {
|
|
38
|
+
aggregate.totals[key] += snapshot.totals[key];
|
|
39
|
+
}
|
|
40
|
+
aggregate.queueWaitMs.count += snapshot.queueWaitMs.count;
|
|
41
|
+
aggregate.queueWaitMs.total += snapshot.queueWaitMs.total;
|
|
42
|
+
aggregate.queueWaitMs.max = Math.max(aggregate.queueWaitMs.max, snapshot.queueWaitMs.max);
|
|
43
|
+
}
|
|
44
|
+
return aggregate;
|
|
45
|
+
}
|
|
25
46
|
function positiveWhole(value, name) {
|
|
26
47
|
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
|
|
27
48
|
throw new TypeError(`${name} must be a positive whole number.`);
|
|
@@ -194,6 +215,11 @@ export class ConnectorCallAdmissionController {
|
|
|
194
215
|
waiter.onAbort();
|
|
195
216
|
});
|
|
196
217
|
}
|
|
218
|
+
/** A principal registry may be evicted only after calls and budgets drain. */
|
|
219
|
+
isIdle() {
|
|
220
|
+
this.evictIdlePartitions(Date.now());
|
|
221
|
+
return this.partitions.size === 0;
|
|
222
|
+
}
|
|
197
223
|
snapshot() {
|
|
198
224
|
let active = 0;
|
|
199
225
|
let queued = 0;
|
package/dist/catalog-drift.js
CHANGED
|
@@ -36,15 +36,20 @@ export function boundedCatalogDrift(report) {
|
|
|
36
36
|
}
|
|
37
37
|
const encoder = new TextEncoder();
|
|
38
38
|
/** Deterministic JSON: object keys sorted, so key order is not a schema change. */
|
|
39
|
-
function canonicalize(value) {
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
function canonicalize(value, depth = 0) {
|
|
40
|
+
// Drift is advisory. Beyond this bound compare an explicit marker instead
|
|
41
|
+
// of letting a downstream schema exhaust the host stack.
|
|
42
|
+
if (depth > 64)
|
|
43
|
+
return "[schema depth truncated]";
|
|
44
|
+
if (Array.isArray(value)) {
|
|
45
|
+
return value.map((item) => canonicalize(item, depth + 1));
|
|
46
|
+
}
|
|
42
47
|
if (value === null || typeof value !== "object")
|
|
43
48
|
return value;
|
|
44
49
|
const entries = Object.entries(value)
|
|
45
50
|
.filter(([, item]) => item !== undefined)
|
|
46
51
|
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
47
|
-
return Object.fromEntries(entries.map(([key, item]) => [key, canonicalize(item)]));
|
|
52
|
+
return Object.fromEntries(entries.map(([key, item]) => [key, canonicalize(item, depth + 1)]));
|
|
48
53
|
}
|
|
49
54
|
/**
|
|
50
55
|
* Digest the schemas of one downstream tool.
|
|
@@ -114,6 +114,8 @@ export interface CatalogDescription {
|
|
|
114
114
|
inputSchema?: unknown;
|
|
115
115
|
outputSchema?: unknown;
|
|
116
116
|
outputSchemaSource?: "observed";
|
|
117
|
+
inputSchemaTruncated?: true;
|
|
118
|
+
outputSchemaTruncated?: true;
|
|
117
119
|
annotations?: ToolDef["annotations"];
|
|
118
120
|
error?: string;
|
|
119
121
|
errorDetails?: CatalogDescriptionFailureDetail;
|
package/dist/catalog-service.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { compactDiscoverySchema,
|
|
1
|
+
import { compactDiscoverySchema, compactDescriptionSchema, lexicalCorpusStatistics, lexicalQueryTerms, lexicalSearchQuery, matchesLexicalTerm, rankTools, schemaObjectKeys, summarizeDiscoveryDescription, summarizeDescription, } from "./catalog.js";
|
|
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";
|
|
@@ -180,9 +180,6 @@ function schemaKeyMetadata(input, output) {
|
|
|
180
180
|
: {}),
|
|
181
181
|
};
|
|
182
182
|
}
|
|
183
|
-
function renderSchema(schema, format) {
|
|
184
|
-
return format === "json" ? schema : compactSchema(schema);
|
|
185
|
-
}
|
|
186
183
|
function renderSearchSchema(schema, format) {
|
|
187
184
|
if (format === "json")
|
|
188
185
|
return { schema, truncated: false };
|
|
@@ -337,11 +334,25 @@ export class CatalogService {
|
|
|
337
334
|
};
|
|
338
335
|
}
|
|
339
336
|
async search(args) {
|
|
337
|
+
if (args.query !== undefined && typeof args.query !== "string") {
|
|
338
|
+
throw new DiscoveryPolicyError("invalid_args", "query must be a string. Omit it or use an empty string to browse the catalog.");
|
|
339
|
+
}
|
|
340
|
+
if (args.connector !== undefined &&
|
|
341
|
+
boundedEchoText(args.connector) !== args.connector) {
|
|
342
|
+
// The scope is echoed back as `queryAnalysis.connectorScope`; a clipped
|
|
343
|
+
// copy could name a different connector, so refuse instead of clamping.
|
|
344
|
+
throw new DiscoveryPolicyError("invalid_args", "connector must be at most 512 UTF-8 bytes.");
|
|
345
|
+
}
|
|
340
346
|
const query = args.query ?? "";
|
|
341
347
|
const retrievalQuery = lexicalSearchQuery(query);
|
|
342
348
|
const safety = discoverySafety(args.safety);
|
|
343
349
|
const limit = discoverySearchLimit(args.limit);
|
|
344
|
-
|
|
350
|
+
if (args.offset !== undefined && (typeof args.offset !== "number" ||
|
|
351
|
+
!Number.isInteger(args.offset) ||
|
|
352
|
+
args.offset < 0)) {
|
|
353
|
+
throw new DiscoveryPolicyError("invalid_args", "offset must be a non-negative whole number. Start at 0 or use the previous page's nextOffset.");
|
|
354
|
+
}
|
|
355
|
+
const offset = args.offset ?? 0;
|
|
345
356
|
const scopedConnector = args.connector
|
|
346
357
|
? this.registry.getConnector(args.connector)
|
|
347
358
|
: undefined;
|
|
@@ -768,7 +779,11 @@ export class CatalogService {
|
|
|
768
779
|
const input = tool.inputSchema ?? { type: "object" };
|
|
769
780
|
const output = this.outputSchema(addressResolution.connector.id, tool);
|
|
770
781
|
const description = summarizeDescription(tool.description, args.fullDescriptions === true);
|
|
771
|
-
const
|
|
782
|
+
const compactInput = format === "compact"
|
|
783
|
+
? compactDescriptionSchema(input) : undefined;
|
|
784
|
+
const compactOutput = format === "compact" && output.schema
|
|
785
|
+
? compactDescriptionSchema(output.schema) : undefined;
|
|
786
|
+
const requiredReasons = guideRequiredReasons(addressResolution.connector, tool, compactInput?.truncated === true || compactOutput?.truncated === true);
|
|
772
787
|
const guideSummary = connectorGuideSummary(addressResolution.connector);
|
|
773
788
|
return {
|
|
774
789
|
address,
|
|
@@ -786,10 +801,12 @@ export class CatalogService {
|
|
|
786
801
|
guideRequiredReasons: requiredReasons,
|
|
787
802
|
}
|
|
788
803
|
: {}),
|
|
789
|
-
inputSchema:
|
|
804
|
+
inputSchema: compactInput?.text ?? input,
|
|
805
|
+
...(compactInput?.truncated ? { inputSchemaTruncated: true } : {}),
|
|
806
|
+
...(compactOutput?.truncated ? { outputSchemaTruncated: true } : {}),
|
|
790
807
|
...(output.schema
|
|
791
808
|
? {
|
|
792
|
-
outputSchema:
|
|
809
|
+
outputSchema: compactOutput?.text ?? output.schema,
|
|
793
810
|
}
|
|
794
811
|
: {}),
|
|
795
812
|
...(output.source ? { outputSchemaSource: output.source } : {}),
|
package/dist/catalog.d.ts
CHANGED
|
@@ -50,6 +50,8 @@ export declare function lexicalCorpusStatistics(toolSets: ToolDef[][], query: st
|
|
|
50
50
|
export declare function rankTools(tools: ToolDef[], query: string, mode?: LexicalMatchMode, statistics?: LexicalCorpusStatistics, exactNameQuery?: string): RankedTool[];
|
|
51
51
|
/** Render and cache a compact TypeScript-like representation of JSON Schema. */
|
|
52
52
|
export declare function compactSchema(schema: JsonSchema): string;
|
|
53
|
+
/** Describe allows 8 KiB for property prose, with the same work cap as search. */
|
|
54
|
+
export declare function compactDescriptionSchema(schema: JsonSchema): CompactDiscoverySchema;
|
|
53
55
|
export interface CompactDiscoverySchema {
|
|
54
56
|
text: string;
|
|
55
57
|
truncated: boolean;
|