@zackbart/connecta 0.38.0 → 0.40.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 +91 -0
- package/README.md +3 -2
- package/bin/connecta.mjs +4 -8
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +6 -4
- package/dist/agent-routes.js +231 -0
- package/dist/auth/downstream-oauth/consent.js +519 -0
- package/dist/auth/downstream-oauth/grant-store.js +439 -0
- package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
- package/dist/auth/downstream-oauth/refresh-records.js +447 -0
- package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
- package/dist/auth/downstream-oauth.js +66 -2382
- package/dist/auth/native.js +37 -0
- package/dist/auth/oauth-grant.js +13 -0
- package/dist/auth/static-oauth.js +153 -131
- package/dist/auth-elicitation.js +7 -1
- package/dist/authorization-handoff.js +11 -5
- package/dist/catalog-cache.js +219 -59
- package/dist/catalog-policy.js +18 -0
- package/dist/catalog-service.js +284 -47
- package/dist/catalog.js +18 -2
- package/dist/config.d.ts +16 -11
- package/dist/config.js +9 -6
- package/dist/connector-caller.d.ts +1 -1
- package/dist/connector-health.js +59 -0
- package/dist/connector-status.js +117 -0
- package/dist/connectors/api-connector.d.ts +7 -2
- package/dist/connectors/api-connector.js +7 -4
- package/dist/connectors/negotiation-cache.js +22 -4
- package/dist/connectors/remote-mcp/call.js +164 -0
- package/dist/connectors/remote-mcp/catalog.js +278 -0
- package/dist/connectors/remote-mcp/config.js +263 -0
- package/dist/connectors/remote-mcp/connection.js +682 -0
- package/dist/connectors/remote-mcp/errors.js +488 -0
- package/dist/connectors/remote-mcp/oauth.js +473 -0
- package/dist/connectors/remote-mcp/resources.js +473 -0
- package/dist/connectors/remote-mcp/runtime.js +1 -0
- package/dist/connectors/remote-mcp/transport.js +555 -0
- package/dist/connectors/remote-mcp.js +62 -2863
- package/dist/credential-contract.d.ts +23 -0
- package/dist/credential-reads.js +17 -0
- package/dist/credential-verification.js +53 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.js +60 -25
- package/dist/describe-config.d.ts +12 -2
- package/dist/describe-config.js +2 -0
- package/dist/discovery-health.js +199 -0
- package/dist/downstream-input.js +2 -0
- package/dist/errors.d.ts +55 -17
- package/dist/errors.js +95 -41
- package/dist/execute.js +152 -114
- package/dist/executor-result.js +3 -1
- package/dist/guest-functions.js +83 -0
- package/dist/guest-runtime.js +60 -19
- package/dist/guest-types.d.ts +19 -5
- package/dist/http/argument-problems.js +35 -0
- package/dist/http/errors.js +5 -4
- package/dist/http/fields.js +192 -13
- package/dist/http/hatch-response.js +73 -0
- package/dist/http/hatch.js +88 -23
- package/dist/http/oauth.js +3 -2
- package/dist/http/provider.js +32 -4
- package/dist/http/safety.js +8 -0
- package/dist/http/tools.js +44 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/invocation.js +26 -8
- package/dist/membership.d.ts +13 -5
- package/dist/membership.js +34 -2
- package/dist/meta-tools.js +54 -31
- package/dist/native-recovery.js +4 -16
- package/dist/native-tools.js +5 -2
- package/dist/oauth-sealing.js +10 -0
- package/dist/operation-vocabulary.js +135 -13
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +1 -0
- package/dist/operator-ui/contract.d.ts +10 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +22 -1
- package/dist/provider-call-facts.js +6 -2
- package/dist/providers/_shared/rest/operation-index.js +48 -50
- package/dist/providers/_shared/rest/value-safety.js +80 -0
- package/dist/providers/breeze/api.js +7 -1
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/breeze/tools.js +4 -3
- package/dist/providers/ccb/api.js +2 -1
- package/dist/providers/ccb/index.js +5 -0
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/cloudflare/api.js +8 -1
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/lib/schemas.js +24 -22
- package/dist/providers/docs/safety.js +2 -0
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/docs/tools.js +2 -4
- package/dist/providers/drive/api.js +1 -0
- package/dist/providers/drive/lib/contracts.js +8 -5
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/drive/tools.js +11 -16
- package/dist/providers/forms/api.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/forms/tools.js +10 -7
- package/dist/providers/github/api.js +1 -0
- package/dist/providers/github/lib/discovery.js +1 -1
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +61 -17
- package/dist/providers/gmail/lib/schemas.js +60 -50
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/gmail/tools.js +7 -5
- package/dist/providers/granola/index.js +2 -0
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/api.js +7 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/planning-center/api.js +3 -1
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/quickbooks/safety.js +80 -39
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/api.js +1 -0
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/sheets/tools.js +13 -14
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/slides/tools.js +15 -13
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/api.js +3 -1
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/vercel/api.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/tools.js +4 -3
- package/dist/registry.d.ts +10 -4
- package/dist/registry.js +41 -13
- package/dist/routes/credentials.js +129 -60
- package/dist/routes/dispatch.js +5 -1
- package/dist/routes/mcp.js +30 -14
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +6 -1
- package/dist/routes/shared.js +7 -1
- package/dist/routes/ui-config.js +2 -2
- package/dist/routes/ui-data.js +126 -20
- package/dist/routes/ui.js +16 -0
- package/dist/runtime/call.js +7 -2
- package/dist/runtime/native-storage.js +12 -0
- package/dist/runtime/request-reads.js +207 -0
- package/dist/runtime/request-scope.js +16 -0
- package/dist/runtime/storage.js +12 -0
- package/dist/skills.js +37 -20
- package/dist/storage/keys.js +53 -3
- package/dist/storage/memory.js +8 -2
- package/dist/storage/sql-native.js +53 -1
- package/dist/storage/sql.js +12 -2
- package/dist/storage-digest.js +32 -0
- package/dist/timeout.js +8 -0
- package/dist/types.d.ts +33 -0
- package/dist/usage-guide.js +16 -35
- package/dist/validate.d.ts +3 -0
- package/dist/validate.js +220 -158
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/write-outcome.js +0 -3
- package/documentation/README.md +13 -13
- package/documentation/architecture.md +385 -20
- package/documentation/auth.md +233 -64
- package/documentation/code-mode.md +122 -39
- package/documentation/deploying.md +6 -3
- package/documentation/integrating.md +16 -2
- package/documentation/meta-tools.md +240 -49
- package/documentation/migration-0.33.md +5 -1
- package/documentation/migration-0.34.md +3 -1
- package/documentation/operator-tests.md +1 -1
- package/documentation/operator-ui.md +105 -12
- package/documentation/pool-membership-upgrade.md +44 -0
- package/documentation/providers.md +105 -0
- package/examples/worker/AGENTS.md +10 -10
- package/examples/worker/README.md +4 -3
- package/examples/worker/src/connecta.config.ts +1 -0
- package/guides/connecting-agents.md +2 -1
- package/guides/how-it-works.md +4 -2
- package/package.json +1 -1
- package/templates/node/AGENTS.md +13 -12
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +1 -0
package/dist/validate.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Validator } from "@cfworker/json-schema";
|
|
2
|
-
import {
|
|
2
|
+
import { redactCallArguments } from "./argument-redaction.js";
|
|
3
|
+
import { boundedEchoText, boundText, ConnectorCallError, issueSummary, listed } from "./errors.js";
|
|
3
4
|
import { MAX_ARGUMENT_VALIDATION_ISSUES } from "./errors.js";
|
|
4
5
|
import { failureRecord, logFailure } from "./operator-record.js";
|
|
5
6
|
// Lazy validator cache keyed by the schema object itself; null marks a schema
|
|
@@ -9,6 +10,7 @@ import { failureRecord, logFailure } from "./operator-record.js";
|
|
|
9
10
|
const validators = new WeakMap();
|
|
10
11
|
const evaluationWarnings = new WeakSet();
|
|
11
12
|
const REQUIRED_PROPERTY_RE = /^Instance does not have required property "([^"]+)"\.$/;
|
|
13
|
+
const DEPENDENT_PROPERTY_RE = /^Instance has "([^"]+)" but does not have "([^"]+)"\.$/;
|
|
12
14
|
function plainContainer(value) {
|
|
13
15
|
if (value === null || typeof value !== "object")
|
|
14
16
|
return false;
|
|
@@ -94,11 +96,6 @@ function pointerValue(value, pointer) {
|
|
|
94
96
|
}
|
|
95
97
|
return current;
|
|
96
98
|
}
|
|
97
|
-
function argumentPath(location) {
|
|
98
|
-
if (location === "#")
|
|
99
|
-
return "/";
|
|
100
|
-
return location.startsWith("#") ? location.slice(1) || "/" : "/";
|
|
101
|
-
}
|
|
102
99
|
function validationUnitKey(unit) {
|
|
103
100
|
return JSON.stringify([unit.keyword, unit.keywordLocation, unit.instanceLocation, unit.error]);
|
|
104
101
|
}
|
|
@@ -153,17 +150,24 @@ function isDuplicateAdditionalPropertiesBranch(schema, units, index) {
|
|
|
153
150
|
}
|
|
154
151
|
function normalizedValidationUnits(schema, units) {
|
|
155
152
|
const seen = new Set();
|
|
156
|
-
return units.
|
|
153
|
+
return units.flatMap((unit, index) => {
|
|
157
154
|
if (CONTAINER_VALIDATION_KEYWORDS.has(unit.keyword))
|
|
158
|
-
return
|
|
155
|
+
return [];
|
|
159
156
|
if (isDuplicateAdditionalPropertiesBranch(schema, units, index)) {
|
|
160
|
-
return
|
|
157
|
+
return [];
|
|
161
158
|
}
|
|
162
159
|
const key = validationUnitKey(unit);
|
|
163
160
|
if (seen.has(key))
|
|
164
|
-
return
|
|
161
|
+
return [];
|
|
165
162
|
seen.add(key);
|
|
166
|
-
|
|
163
|
+
// The validator locates a false subschema by instance; its wrapper names the object.
|
|
164
|
+
const wrapper = units[index - 1];
|
|
165
|
+
return unit.keyword === "false" &&
|
|
166
|
+
wrapper?.keyword === "additionalProperties" &&
|
|
167
|
+
wrapper.keywordLocation.endsWith("/additionalProperties") &&
|
|
168
|
+
childPropertyName(wrapper.instanceLocation, unit.instanceLocation) !== undefined
|
|
169
|
+
? [{ ...unit, closedBy: wrapper.keywordLocation.slice(0, -"/additionalProperties".length) || "#" }]
|
|
170
|
+
: [unit];
|
|
167
171
|
});
|
|
168
172
|
}
|
|
169
173
|
const JSON_TYPES = new Set(["string", "number", "integer", "boolean", "object", "array", "null"]);
|
|
@@ -176,70 +180,17 @@ function declaredType(value) {
|
|
|
176
180
|
? types.join(" | ")
|
|
177
181
|
: undefined;
|
|
178
182
|
}
|
|
179
|
-
function expectedType(schema, unit) {
|
|
180
|
-
if (unit.keyword === "type") {
|
|
181
|
-
return declaredType(pointerValue(schema, unit.keywordLocation));
|
|
182
|
-
}
|
|
183
|
-
if (unit.keyword === "required") {
|
|
184
|
-
const missing = REQUIRED_PROPERTY_RE.exec(unit.error)?.[1];
|
|
185
|
-
if (!missing)
|
|
186
|
-
return undefined;
|
|
187
|
-
const parentLocation = unit.keywordLocation.replace(/\/required$/, "");
|
|
188
|
-
return (declaredType(pointerValue(schema, `${parentLocation}/properties/${encodePointerPart(missing)}/type`)) ?? "present");
|
|
189
|
-
}
|
|
190
|
-
const fixed = {
|
|
191
|
-
additionalProperties: "no additional properties",
|
|
192
|
-
enum: "one of the declared values",
|
|
193
|
-
const: "the declared constant",
|
|
194
|
-
minLength: "the declared minimum length",
|
|
195
|
-
maxLength: "the declared maximum length",
|
|
196
|
-
minimum: "the declared minimum",
|
|
197
|
-
maximum: "the declared maximum",
|
|
198
|
-
pattern: "the declared string pattern",
|
|
199
|
-
};
|
|
200
|
-
return fixed[unit.keyword];
|
|
201
|
-
}
|
|
202
|
-
function validationDetails(schema, units) {
|
|
203
|
-
const issues = [];
|
|
204
|
-
for (const unit of units) {
|
|
205
|
-
const missing = unit.keyword === "required" ? REQUIRED_PROPERTY_RE.exec(unit.error)?.[1] : undefined;
|
|
206
|
-
const path = missing !== undefined
|
|
207
|
-
? `${argumentPath(unit.instanceLocation).replace(/\/$/, "")}/${encodePointerPart(missing)}`
|
|
208
|
-
: argumentPath(unit.instanceLocation);
|
|
209
|
-
const code = unit.keyword === "false" ? "additionalProperties" : unit.keyword;
|
|
210
|
-
const expected = expectedType(schema, unit) ??
|
|
211
|
-
(code === "additionalProperties" ? "no additional properties" : "the declared schema constraint");
|
|
212
|
-
const issue = { path, code, expected };
|
|
213
|
-
if (!issues.some((existing) => existing.path === issue.path && existing.code === issue.code && existing.expected === issue.expected)) {
|
|
214
|
-
issues.push(issue);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
issues: issues.slice(0, MAX_ARGUMENT_VALIDATION_ISSUES),
|
|
219
|
-
...(issues.length > MAX_ARGUMENT_VALIDATION_ISSUES ? { truncated: true } : {}),
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
183
|
function schemaObject(value) {
|
|
223
184
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
224
185
|
}
|
|
225
186
|
function receivedType(value) {
|
|
226
187
|
return value === undefined ? "missing" : value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
227
188
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
"multipleOf",
|
|
234
|
-
"minLength",
|
|
235
|
-
"maxLength",
|
|
236
|
-
"minItems",
|
|
237
|
-
"maxItems",
|
|
238
|
-
"minProperties",
|
|
239
|
-
"maxProperties",
|
|
240
|
-
];
|
|
241
|
-
/** No caller values are used to invent an example. A candidate must validate. */
|
|
242
|
-
function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, active: new Set() }) {
|
|
189
|
+
/**
|
|
190
|
+
* No caller values are used to invent an example, and a writeOnly position or
|
|
191
|
+
* anything inside one never renders a schema literal. A candidate must validate.
|
|
192
|
+
*/
|
|
193
|
+
function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, active: new Set() }, secret = false) {
|
|
243
194
|
if (--budget.remaining < 0)
|
|
244
195
|
throw new Error("Example synthesis budget exhausted");
|
|
245
196
|
if (depth > 12 || schema === false)
|
|
@@ -248,22 +199,23 @@ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, ac
|
|
|
248
199
|
if (budget.active.has(node))
|
|
249
200
|
throw new Error("Recursive schema cannot produce a bounded example");
|
|
250
201
|
budget.active.add(node);
|
|
202
|
+
secret ||= node.writeOnly === true;
|
|
251
203
|
try {
|
|
252
204
|
if (typeof node.$ref === "string") {
|
|
253
|
-
return exampleCandidate(pointerValue(root, node.$ref), root, depth + 1, budget);
|
|
205
|
+
return exampleCandidate(pointerValue(root, node.$ref), root, depth + 1, budget, secret);
|
|
254
206
|
}
|
|
255
|
-
if (Object.hasOwn(node, "const"))
|
|
207
|
+
if (!secret && Object.hasOwn(node, "const"))
|
|
256
208
|
return node.const;
|
|
257
|
-
if (Array.isArray(node.enum))
|
|
209
|
+
if (!secret && Array.isArray(node.enum))
|
|
258
210
|
return node.enum[0];
|
|
259
211
|
const type = Array.isArray(node.type) ? node.type[0] : node.type;
|
|
260
212
|
const variants = Array.isArray(node.oneOf) ? node.oneOf : node.anyOf;
|
|
261
213
|
if (Array.isArray(variants) && variants.length) {
|
|
262
|
-
return mergeExample(exampleCandidate({ ...node, oneOf: undefined, anyOf: undefined }, root, depth + 1, budget), exampleCandidate(variants[0], root, depth + 1, budget));
|
|
214
|
+
return mergeExample(exampleCandidate({ ...node, oneOf: undefined, anyOf: undefined }, root, depth + 1, budget, secret), exampleCandidate(variants[0], root, depth + 1, budget, secret));
|
|
263
215
|
}
|
|
264
|
-
if (Array.isArray(node.examples) && node.examples.length)
|
|
216
|
+
if (!secret && Array.isArray(node.examples) && node.examples.length)
|
|
265
217
|
return node.examples[0];
|
|
266
|
-
if (Object.hasOwn(node, "default"))
|
|
218
|
+
if (!secret && Object.hasOwn(node, "default"))
|
|
267
219
|
return node.default;
|
|
268
220
|
let candidate;
|
|
269
221
|
if (type === "object" || node.properties || node.required || node.allOf) {
|
|
@@ -272,10 +224,10 @@ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, ac
|
|
|
272
224
|
candidate = Object.fromEntries(required
|
|
273
225
|
.filter((key) => typeof key === "string")
|
|
274
226
|
.slice(0, 30)
|
|
275
|
-
.map((key) => [key, exampleCandidate(properties[key], root, depth + 1, budget)]));
|
|
227
|
+
.map((key) => [key, exampleCandidate(properties[key], root, depth + 1, budget, secret)]));
|
|
276
228
|
if (Array.isArray(node.allOf)) {
|
|
277
229
|
for (const branch of node.allOf.slice(0, 20)) {
|
|
278
|
-
candidate = mergeExample(candidate, exampleCandidate(branch, root, depth + 1, budget));
|
|
230
|
+
candidate = mergeExample(candidate, exampleCandidate(branch, root, depth + 1, budget, secret));
|
|
279
231
|
}
|
|
280
232
|
}
|
|
281
233
|
const object = schemaObject(candidate);
|
|
@@ -285,7 +237,7 @@ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, ac
|
|
|
285
237
|
for (const need of needs.slice(0, 30)) {
|
|
286
238
|
if (typeof need === "string" && !Object.hasOwn(object, need)) {
|
|
287
239
|
Object.defineProperty(object, need, {
|
|
288
|
-
value: exampleCandidate(properties[need], root, depth + 1, budget),
|
|
240
|
+
value: exampleCandidate(properties[need], root, depth + 1, budget, secret),
|
|
289
241
|
enumerable: true,
|
|
290
242
|
configurable: true,
|
|
291
243
|
writable: true,
|
|
@@ -297,7 +249,7 @@ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, ac
|
|
|
297
249
|
if (node.if) {
|
|
298
250
|
try {
|
|
299
251
|
const matches = new Validator(node.if, "2020-12", false).validate(candidate).valid;
|
|
300
|
-
candidate = mergeExample(candidate, exampleCandidate(matches ? node.then : node.else, root, depth + 1, budget));
|
|
252
|
+
candidate = mergeExample(candidate, exampleCandidate(matches ? node.then : node.else, root, depth + 1, budget, secret));
|
|
301
253
|
}
|
|
302
254
|
catch {
|
|
303
255
|
// An unevaluable branch cannot establish a valid example.
|
|
@@ -306,13 +258,13 @@ function exampleCandidate(schema, root, depth = 0, budget = { remaining: 128, ac
|
|
|
306
258
|
// Branches may declare required keys while their types live on the parent.
|
|
307
259
|
for (const key of Object.keys(schemaObject(candidate))) {
|
|
308
260
|
if (schemaObject(candidate)[key] === undefined) {
|
|
309
|
-
schemaObject(candidate)[key] = exampleCandidate(properties[key], root, depth + 1, budget);
|
|
261
|
+
schemaObject(candidate)[key] = exampleCandidate(properties[key], root, depth + 1, budget, secret);
|
|
310
262
|
}
|
|
311
263
|
}
|
|
312
264
|
}
|
|
313
265
|
else if (type === "array") {
|
|
314
266
|
const count = typeof node.minItems === "number" ? node.minItems : 0;
|
|
315
|
-
candidate = Array.from({ length: Math.min(count, 20) }, (_, index) => exampleCandidate(Array.isArray(node.prefixItems) ? node.prefixItems[index] : node.items, root, depth + 1, budget));
|
|
267
|
+
candidate = Array.from({ length: Math.min(count, 20) }, (_, index) => exampleCandidate(Array.isArray(node.prefixItems) ? node.prefixItems[index] : node.items, root, depth + 1, budget, secret));
|
|
316
268
|
}
|
|
317
269
|
else if (type === "number" || type === "integer") {
|
|
318
270
|
let number = typeof node.minimum === "number" ? node.minimum : 0;
|
|
@@ -400,90 +352,203 @@ function minimizeExample(example, validator) {
|
|
|
400
352
|
};
|
|
401
353
|
visit(example, 0);
|
|
402
354
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
355
|
+
/** The JSON Pointer of the argument a unit names; a missing property gets its own path. */
|
|
356
|
+
function unitLocation(unit) {
|
|
357
|
+
const dependent = DEPENDENT_PROPERTY_RE.exec(unit.error);
|
|
358
|
+
const missing = unit.keyword === "required" ? REQUIRED_PROPERTY_RE.exec(unit.error)?.[1] : dependent?.[2];
|
|
359
|
+
return missing === undefined
|
|
360
|
+
? { location: unit.instanceLocation }
|
|
361
|
+
: {
|
|
362
|
+
location: `${unit.instanceLocation}/${encodePointerPart(missing)}`,
|
|
363
|
+
missing,
|
|
364
|
+
...(dependent ? { with: dependent[1] } : {}),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
/** A schema node with its local references followed. */
|
|
368
|
+
function resolved(root, value) {
|
|
369
|
+
let node = schemaObject(value);
|
|
370
|
+
for (let hops = 0; typeof node.$ref === "string" && hops < 8; hops++)
|
|
371
|
+
node = schemaObject(pointerValue(root, node.$ref));
|
|
372
|
+
return node;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Where a finding may point, and whether that position is sensitive. A path
|
|
376
|
+
* names only properties the schema declares and array indices; the first
|
|
377
|
+
* other key reads `<key>` and ends it, since a map key is caller text. A
|
|
378
|
+
* position is sensitive when it or any ancestor is writeOnly.
|
|
379
|
+
*/
|
|
380
|
+
function publicPosition(root, location) {
|
|
381
|
+
let node = resolved(root, root);
|
|
382
|
+
let path = "";
|
|
383
|
+
let sensitive = node.writeOnly === true;
|
|
384
|
+
for (const part of location === "#" ? [] : location.slice(2).split("/").map(decodePointerPart)) {
|
|
385
|
+
const branches = [
|
|
386
|
+
node,
|
|
387
|
+
...["allOf", "anyOf", "oneOf"].flatMap((key) => (Array.isArray(node[key]) ? node[key] : [])),
|
|
388
|
+
].map((branch) => resolved(root, branch));
|
|
389
|
+
const owner = branches.find((branch) => Object.hasOwn(schemaObject(branch.properties), part));
|
|
390
|
+
const list = branches.find((branch) => branch.items !== undefined || Array.isArray(branch.prefixItems));
|
|
391
|
+
if (owner)
|
|
392
|
+
node = resolved(root, schemaObject(owner.properties)[part]);
|
|
393
|
+
else if (list && /^(?:0|[1-9]\d*)$/.test(part))
|
|
394
|
+
node = resolved(root, Array.isArray(list.prefixItems) ? (list.prefixItems[Number(part)] ?? list.items) : list.items);
|
|
395
|
+
else {
|
|
396
|
+
const values = branches.find((branch) => branch.additionalProperties !== undefined);
|
|
397
|
+
return {
|
|
398
|
+
path: `${path}/<key>`,
|
|
399
|
+
sensitive: sensitive || resolved(root, values?.additionalProperties).writeOnly === true,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
sensitive ||= node.writeOnly === true;
|
|
403
|
+
path += `/${owner ? encodePointerPart(part) : part}`;
|
|
404
|
+
}
|
|
405
|
+
return { path: path || "/", sensitive };
|
|
406
|
+
}
|
|
407
|
+
const patternText = (pattern, sensitive) => `matching ${!sensitive && typeof pattern === "string" && pattern.length <= 64 ? pattern : "the inputSchema pattern"}`;
|
|
408
|
+
/** Every other constraint a schema node declares, so one finding states them all; no literal of a sensitive one. */
|
|
409
|
+
function constraints(node, failed, sensitive) {
|
|
410
|
+
return Object.keys(node).flatMap((key) => key === failed
|
|
411
|
+
? []
|
|
412
|
+
: key === "enum" && Array.isArray(node.enum) && !sensitive
|
|
413
|
+
? `one of ${listed(node.enum.map((item) => JSON.stringify(item)))}`
|
|
414
|
+
: key === "uniqueItems" && node.uniqueItems === true
|
|
415
|
+
? "unique items"
|
|
416
|
+
: key === "pattern"
|
|
417
|
+
? patternText(node.pattern, sensitive)
|
|
418
|
+
: key === "format"
|
|
419
|
+
? `${String(node.format)} format`
|
|
420
|
+
: (boundText(key, node[key]) ?? []));
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* One finding per unit, told from the reviewed schema, never the validator's
|
|
424
|
+
* own sentences or a submitted string or key. A bound violation states the
|
|
425
|
+
* bound and, unless the position is sensitive, the submitted number. A
|
|
426
|
+
* sensitive position never renders a schema literal (enum, const, pattern).
|
|
427
|
+
*/
|
|
428
|
+
function issueFor(schema, args, safeArgs, unit) {
|
|
429
|
+
const { location, missing, with: present } = unitLocation(unit);
|
|
430
|
+
const position = publicPosition(schema, location);
|
|
431
|
+
const keyword = pointerValue(schema, unit.keywordLocation);
|
|
432
|
+
const parent = unit.keywordLocation.replace(/\/[^/]+$/, "");
|
|
433
|
+
const node = schemaObject(pointerValue(schema, missing === undefined ? parent : `${parent}/properties/${encodePointerPart(missing)}`));
|
|
434
|
+
const path = position.path;
|
|
435
|
+
// Unresolved sensitivity (no redacted copy) is treated as sensitive.
|
|
436
|
+
const sensitive = position.sensitive || node.writeOnly === true || safeArgs === undefined;
|
|
437
|
+
let received = receivedType(pointerValue(args, location));
|
|
438
|
+
let expected;
|
|
439
|
+
if (missing !== undefined) {
|
|
440
|
+
expected = `${declaredType(node.type) ?? "a value"} (required${present ? ` with ${present}` : ""})`;
|
|
441
|
+
received = "missing";
|
|
442
|
+
}
|
|
443
|
+
else if (unit.keyword === "false") {
|
|
444
|
+
// A false subschema under additionalProperties names its object's accepted keys.
|
|
445
|
+
if (unit.closedBy === undefined)
|
|
446
|
+
return { path, expected: "absent: the inputSchema forbids this value" };
|
|
447
|
+
const keys = Object.entries(schemaObject(schemaObject(pointerValue(schema, unit.closedBy)).properties))
|
|
448
|
+
.filter(([, child]) => child !== false)
|
|
449
|
+
.map(([key]) => key);
|
|
450
|
+
return { path, expected: keys.length ? `one of the accepted keys: ${listed(keys)}` : "no additional properties" };
|
|
451
|
+
}
|
|
452
|
+
else if (unit.keyword === "const") {
|
|
453
|
+
const constant = JSON.stringify(keyword);
|
|
420
454
|
return {
|
|
421
|
-
path
|
|
422
|
-
|
|
423
|
-
...(acceptedKeys.length ? { acceptedKeys } : {}),
|
|
424
|
-
...(Array.isArray(node.enum) ? { enumValues: node.enum } : {}),
|
|
425
|
-
...(Object.keys(bounds).length ? { bounds } : {}),
|
|
455
|
+
path,
|
|
456
|
+
expected: !sensitive && constant?.length <= 64 ? `the constant ${constant}` : "the declared constant",
|
|
426
457
|
};
|
|
458
|
+
}
|
|
459
|
+
else if (unit.keyword === "type")
|
|
460
|
+
expected = declaredType(keyword) ?? "the declared type";
|
|
461
|
+
else if (unit.keyword === "enum" && Array.isArray(keyword) && !sensitive)
|
|
462
|
+
expected = `one of ${listed(keyword.map((value) => JSON.stringify(value)))}`;
|
|
463
|
+
else if (unit.keyword === "uniqueItems")
|
|
464
|
+
expected = "unique items";
|
|
465
|
+
else if (unit.keyword === "pattern")
|
|
466
|
+
expected = `a string ${patternText(keyword, sensitive)}`;
|
|
467
|
+
else if (unit.keyword === "format")
|
|
468
|
+
expected = `a ${String(keyword)} string`;
|
|
469
|
+
else {
|
|
470
|
+
const bound = boundText(unit.keyword, keyword);
|
|
471
|
+
const safe = pointerValue(safeArgs, location);
|
|
472
|
+
if (bound && typeof safe === "number" && !sensitive)
|
|
473
|
+
received = String(safe);
|
|
474
|
+
expected = bound ?? "a value matching the published inputSchema";
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
path,
|
|
478
|
+
expected: [expected, ...constraints(node, missing === undefined ? unit.keyword : "type", sensitive)].join(", "),
|
|
479
|
+
received,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
/** Every unit as a merged finding, bounded and without duplicates. */
|
|
483
|
+
function validationIssues(schema, args, units) {
|
|
484
|
+
// writeOnly positions are omitted; unresolved sensitivity withholds every value.
|
|
485
|
+
let safeArgs;
|
|
486
|
+
try {
|
|
487
|
+
safeArgs = redactCallArguments(args, schema).value;
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
safeArgs = undefined;
|
|
491
|
+
}
|
|
492
|
+
// One finding per schema position: each already states every constraint it declares.
|
|
493
|
+
const seen = new Set();
|
|
494
|
+
return units.flatMap((unit) => {
|
|
495
|
+
const issue = issueFor(schema, args, safeArgs, unit);
|
|
496
|
+
const key = `${issue.path}\n${unit.keywordLocation.replace(/\/[^/]+$/, "")}\n${unit.keyword === "false"}`;
|
|
497
|
+
return seen.has(key) ? [] : (seen.add(key), [issue]);
|
|
427
498
|
});
|
|
499
|
+
}
|
|
500
|
+
/** One example synthesized from the published schema alone, checked by its validator; never caller values. */
|
|
501
|
+
export function schemaExample(schema) {
|
|
502
|
+
try {
|
|
503
|
+
const validator = validators.get(schema) ?? new Validator(schema, "2020-12", false);
|
|
504
|
+
// Serialization makes the validation cover exactly what the agent receives.
|
|
505
|
+
const example = JSON.parse(JSON.stringify(exampleCandidate(schema, schema)));
|
|
506
|
+
if (validator.validate(example).valid) {
|
|
507
|
+
minimizeExample(example, validator);
|
|
508
|
+
if (validator.validate(example).valid)
|
|
509
|
+
return { example };
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
catch {
|
|
513
|
+
// Unsatisfiable or complex schemas do not get a made-up valid example.
|
|
514
|
+
}
|
|
515
|
+
return { exampleUnavailable: "No valid example could be synthesized. Inspect the published inputSchema." };
|
|
516
|
+
}
|
|
517
|
+
/** Names from the published schema and one synthesized example; no caller values and no schema conditions. */
|
|
518
|
+
function schemaFacts(schema) {
|
|
519
|
+
const root = schemaObject(schema);
|
|
428
520
|
const conditionalRequirements = [];
|
|
429
521
|
const visit = (value, path, depth) => {
|
|
430
522
|
if (depth > 12 || conditionalRequirements.length >= 20)
|
|
431
523
|
return;
|
|
432
524
|
const node = schemaObject(value);
|
|
433
|
-
for (const [key, required] of
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
525
|
+
for (const [key, required] of [
|
|
526
|
+
...Object.entries(schemaObject(node.dependentRequired ?? node.dependencies)),
|
|
527
|
+
...Object.entries(schemaObject(node.dependentSchemas)).map(([key, branch]) => [
|
|
528
|
+
key,
|
|
529
|
+
schemaObject(branch).required,
|
|
530
|
+
]),
|
|
531
|
+
]) {
|
|
440
532
|
if (Array.isArray(required) && required.every((key) => typeof key === "string")) {
|
|
441
533
|
conditionalRequirements.push({ path, condition: { required: [key] }, required });
|
|
442
534
|
}
|
|
443
535
|
}
|
|
444
|
-
for (const [branch, condition] of [
|
|
445
|
-
[node.then, node.if],
|
|
446
|
-
[node.else, node.if ? { not: node.if } : undefined],
|
|
447
|
-
]) {
|
|
448
|
-
const required = schemaObject(branch).required;
|
|
449
|
-
if (condition && Array.isArray(required) && required.every((key) => typeof key === "string")) {
|
|
450
|
-
conditionalRequirements.push({ path, condition, required });
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
536
|
for (const [key, child] of Object.entries(schemaObject(node.properties)))
|
|
454
537
|
visit(child, `${path === "/" ? "" : path}/${encodePointerPart(key)}`, depth + 1);
|
|
455
538
|
// Alternatives can make a dependency branch-specific. Do not state its
|
|
456
539
|
// requirements without proving the complete branch condition.
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
visit(child, path, depth + 1);
|
|
461
|
-
}
|
|
540
|
+
if (Array.isArray(node.allOf))
|
|
541
|
+
for (const child of node.allOf)
|
|
542
|
+
visit(child, path, depth + 1);
|
|
462
543
|
};
|
|
463
544
|
visit(schema, "/", 0);
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
// Serialization makes the validation cover exactly what the agent receives.
|
|
468
|
-
example = JSON.parse(JSON.stringify(exampleCandidate(schema, schema)));
|
|
469
|
-
valid = validator.validate(example).valid;
|
|
470
|
-
if (valid) {
|
|
471
|
-
minimizeExample(example, validator);
|
|
472
|
-
valid = validator.validate(example).valid;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
catch {
|
|
476
|
-
valid = false;
|
|
477
|
-
// Unsatisfiable or complex schemas do not get a made-up valid example.
|
|
478
|
-
}
|
|
545
|
+
const keys = Object.entries(schemaObject(root.properties))
|
|
546
|
+
.filter(([, value]) => value !== false)
|
|
547
|
+
.map(([key]) => key);
|
|
479
548
|
return {
|
|
480
|
-
...(root.properties ? { acceptedKeys: keys
|
|
481
|
-
issues,
|
|
549
|
+
...(root.properties ? { acceptedKeys: keys } : {}),
|
|
482
550
|
...(conditionalRequirements.length ? { conditionalRequirements } : {}),
|
|
483
|
-
...(
|
|
484
|
-
? { example }
|
|
485
|
-
: { exampleUnavailable: "No valid example could be synthesized. Inspect the published inputSchema." }),
|
|
486
|
-
...(units.length > MAX_ARGUMENT_VALIDATION_ISSUES ? { truncated: true } : {}),
|
|
551
|
+
...schemaExample(schema),
|
|
487
552
|
};
|
|
488
553
|
}
|
|
489
554
|
function unevaluableSchema(address) {
|
|
@@ -572,10 +637,11 @@ export function validateCatalogToolInput(schema, args, opts, subject) {
|
|
|
572
637
|
}
|
|
573
638
|
if (result && !result.valid) {
|
|
574
639
|
const units = normalizedValidationUnits(schema, result.errors);
|
|
575
|
-
const
|
|
576
|
-
//
|
|
577
|
-
//
|
|
578
|
-
const
|
|
640
|
+
const nested = (unit) => unit.instanceLocation !== "#" || unitLocation(unit).missing !== undefined;
|
|
641
|
+
// A root-level finding often restates a nested one (an alternative that
|
|
642
|
+
// did not match), so nested findings lead and the message names only them.
|
|
643
|
+
const issues = validationIssues(schema, args, [...units.filter(nested), ...units.filter((unit) => !nested(unit))]);
|
|
644
|
+
const named = issues.some((issue) => issue.path !== "/") ? issues.filter((issue) => issue.path !== "/") : issues;
|
|
579
645
|
const limit = schema.properties?.limit?.enum;
|
|
580
646
|
const supplied = args !== null && typeof args === "object" ? args["limit"] : undefined;
|
|
581
647
|
const discreteLimit = supplied !== undefined &&
|
|
@@ -584,16 +650,12 @@ export function validateCatalogToolInput(schema, args, opts, subject) {
|
|
|
584
650
|
limit.length <= 8 &&
|
|
585
651
|
limit.every((size) => Number.isSafeInteger(size) && Number(size) > 0) &&
|
|
586
652
|
!limit.includes(supplied);
|
|
587
|
-
const
|
|
588
|
-
const
|
|
589
|
-
const shown = discreteLimit
|
|
590
|
-
? validationDetails(schema, otherUnits)
|
|
591
|
-
: nestedUnits.length > 0
|
|
592
|
-
? validationDetails(schema, nestedUnits)
|
|
593
|
-
: validation;
|
|
594
|
-
const detail = boundedEchoText(shown.issues.map((issue) => `${issue.path}: expected ${issue.expected} (${issue.code})`).join("; "), 256);
|
|
653
|
+
const shown = discreteLimit ? named.filter((issue) => issue.path !== "/limit") : named;
|
|
654
|
+
const detail = boundedEchoText(issueSummary(shown.slice(0, MAX_ARGUMENT_VALIDATION_ISSUES)), 512);
|
|
595
655
|
const ordinary = `Invalid arguments for "${opts.address}": ${detail || "input does not match the tool's inputSchema"}`;
|
|
596
|
-
return new ConnectorCallError("invalid_args", discreteLimit ? `The limit must be one of ${limit.join(", ")}.${
|
|
656
|
+
return new ConnectorCallError("invalid_args", discreteLimit ? `The limit must be one of ${limit.join(", ")}.${shown.length ? ` ${ordinary}` : ""}` : ordinary, {
|
|
657
|
+
validation: { issues, ...schemaFacts(schema) },
|
|
658
|
+
});
|
|
597
659
|
}
|
|
598
660
|
return null;
|
|
599
661
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CONNECTA_VERSION = "0.
|
|
1
|
+
export declare const CONNECTA_VERSION = "0.40.0";
|
package/dist/version.js
CHANGED
package/dist/write-outcome.js
CHANGED
|
@@ -332,7 +332,6 @@ function withoutRetry(error) {
|
|
|
332
332
|
writeOutcome: "unknown",
|
|
333
333
|
...(error.details ? { details: error.details } : {}),
|
|
334
334
|
...(error.validation ? { validation: error.validation } : {}),
|
|
335
|
-
...(error.repair ? { repair: error.repair } : {}),
|
|
336
335
|
...(error.current ? { current: error.current } : {}),
|
|
337
336
|
}));
|
|
338
337
|
}
|
|
@@ -376,7 +375,6 @@ export function providerFailureMessage(error, message) {
|
|
|
376
375
|
...(error.retryAfterMs === undefined ? {} : { retryAfterMs: error.retryAfterMs }),
|
|
377
376
|
...(error.details ? { details: error.details } : {}),
|
|
378
377
|
...(error.validation ? { validation: error.validation } : {}),
|
|
379
|
-
...(error.repair ? { repair: error.repair } : {}),
|
|
380
378
|
...(error.current ? { current: error.current } : {}),
|
|
381
379
|
}));
|
|
382
380
|
}
|
|
@@ -412,7 +410,6 @@ function uncertainProviderFailure(vendor, error, ctx) {
|
|
|
412
410
|
? {
|
|
413
411
|
...(error.retryAfterMs === undefined ? {} : { retryAfterMs: error.retryAfterMs }),
|
|
414
412
|
...(error.validation ? { validation: error.validation } : {}),
|
|
415
|
-
...(error.repair ? { repair: error.repair } : {}),
|
|
416
413
|
...(error.current ? { current: error.current } : {}),
|
|
417
414
|
}
|
|
418
415
|
: {}),
|
package/documentation/README.md
CHANGED
|
@@ -8,19 +8,19 @@ For a new deployment, start with [Deploying Connecta](./deploying.md).
|
|
|
8
8
|
|
|
9
9
|
## Operator tasks
|
|
10
10
|
|
|
11
|
-
| Task | Self-contained procedure
|
|
12
|
-
| ------------------------------------------------ |
|
|
13
|
-
| Add or remove a connector | [Change the connector roster](./integrating.md#add-or-remove-a-connector)
|
|
14
|
-
| Set up a maintained provider | [Maintained provider setup](./providers.md)
|
|
15
|
-
| Inspect maintained API sources and tools | [API sources and tool inventory](./providers.md#api-sources-and-tool-inventory)
|
|
16
|
-
| Organize an agent's pools and tiers | [Organizing an agent's pools](./auth.md#organizing-an-agents-pools)
|
|
17
|
-
| Add a person (maintainer or pool member) | [Add a person](./auth.md#add-a-person), [member page](./operator-ui.md#use-the-member-page)
|
|
18
|
-
| Connect Google hosted MCP with a service account | [Google tokens and shared-agent/personal-people setup](./auth.md#google-service-account-tokens-and-hosted-mcp)
|
|
19
|
-
| Connect a shared credential | [Connect a shared credential](./auth.md#connect-a-shared-credential)
|
|
20
|
-
| Issue, rotate, or revoke an agent token | [Manage an agent token](./auth.md#issue-rotate-or-revoke-an-agent-token)
|
|
21
|
-
| Upgrade between versions | [Upgrade an existing deployment](./deploying.md#upgrade-an-existing-deployment), including [migration to 0.33](./migration-0.33.md) |
|
|
22
|
-
| Diagnose with `connecta doctor` | [Verify and diagnose](./deploying.md#verify-and-diagnose)
|
|
23
|
-
| Connect a client with tool search | [Native tool surface](./meta-tools.md#native-tool-surface), [Eve agents](./meta-tools.md#eve-agents)
|
|
11
|
+
| Task | Self-contained procedure |
|
|
12
|
+
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| Add or remove a connector | [Change the connector roster](./integrating.md#add-or-remove-a-connector) |
|
|
14
|
+
| Set up a maintained provider | [Maintained provider setup](./providers.md) |
|
|
15
|
+
| Inspect maintained API sources and tools | [API sources and tool inventory](./providers.md#api-sources-and-tool-inventory) |
|
|
16
|
+
| Organize an agent's pools and tiers | [Organizing an agent's pools](./auth.md#organizing-an-agents-pools) |
|
|
17
|
+
| Add a person (maintainer or pool member) | [Add a person](./auth.md#add-a-person), [member page](./operator-ui.md#use-the-member-page) |
|
|
18
|
+
| Connect Google hosted MCP with a service account | [Google tokens and shared-agent/personal-people setup](./auth.md#google-service-account-tokens-and-hosted-mcp) |
|
|
19
|
+
| Connect a shared credential | [Connect a shared credential](./auth.md#connect-a-shared-credential) |
|
|
20
|
+
| Issue, rotate, or revoke an agent token | [Manage an agent token](./auth.md#issue-rotate-or-revoke-an-agent-token) |
|
|
21
|
+
| Upgrade between versions | [Upgrade an existing deployment](./deploying.md#upgrade-an-existing-deployment), including [splitting mixed pools](./pool-membership-upgrade.md) and [migration to 0.33](./migration-0.33.md) |
|
|
22
|
+
| Diagnose with `connecta doctor` | [Verify and diagnose](./deploying.md#verify-and-diagnose) |
|
|
23
|
+
| Connect a client with tool search | [Native tool surface](./meta-tools.md#native-tool-surface), [Eve agents](./meta-tools.md#eve-agents) |
|
|
24
24
|
|
|
25
25
|
For removed hosted presets and the former hosted modes of Stripe, Vercel,
|
|
26
26
|
Cloudflare, and GitHub, use [Migrate hosted MCP presets](./hosted-mcp-migration.md).
|