@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.
Files changed (180) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +3 -2
  3. package/bin/connecta.mjs +4 -8
  4. package/bin/version.mjs +1 -1
  5. package/dist/access-tokens.js +6 -4
  6. package/dist/agent-routes.js +231 -0
  7. package/dist/auth/downstream-oauth/consent.js +519 -0
  8. package/dist/auth/downstream-oauth/grant-store.js +439 -0
  9. package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
  10. package/dist/auth/downstream-oauth/refresh-records.js +447 -0
  11. package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
  12. package/dist/auth/downstream-oauth.js +66 -2382
  13. package/dist/auth/native.js +37 -0
  14. package/dist/auth/oauth-grant.js +13 -0
  15. package/dist/auth/static-oauth.js +153 -131
  16. package/dist/auth-elicitation.js +7 -1
  17. package/dist/authorization-handoff.js +11 -5
  18. package/dist/catalog-cache.js +219 -59
  19. package/dist/catalog-policy.js +18 -0
  20. package/dist/catalog-service.js +284 -47
  21. package/dist/catalog.js +18 -2
  22. package/dist/config.d.ts +16 -11
  23. package/dist/config.js +9 -6
  24. package/dist/connector-caller.d.ts +1 -1
  25. package/dist/connector-health.js +59 -0
  26. package/dist/connector-status.js +117 -0
  27. package/dist/connectors/api-connector.d.ts +7 -2
  28. package/dist/connectors/api-connector.js +7 -4
  29. package/dist/connectors/negotiation-cache.js +22 -4
  30. package/dist/connectors/remote-mcp/call.js +164 -0
  31. package/dist/connectors/remote-mcp/catalog.js +278 -0
  32. package/dist/connectors/remote-mcp/config.js +263 -0
  33. package/dist/connectors/remote-mcp/connection.js +682 -0
  34. package/dist/connectors/remote-mcp/errors.js +488 -0
  35. package/dist/connectors/remote-mcp/oauth.js +473 -0
  36. package/dist/connectors/remote-mcp/resources.js +473 -0
  37. package/dist/connectors/remote-mcp/runtime.js +1 -0
  38. package/dist/connectors/remote-mcp/transport.js +555 -0
  39. package/dist/connectors/remote-mcp.js +62 -2863
  40. package/dist/credential-contract.d.ts +23 -0
  41. package/dist/credential-reads.js +17 -0
  42. package/dist/credential-verification.js +53 -0
  43. package/dist/credentials.d.ts +7 -0
  44. package/dist/credentials.js +60 -25
  45. package/dist/describe-config.d.ts +12 -2
  46. package/dist/describe-config.js +2 -0
  47. package/dist/discovery-health.js +199 -0
  48. package/dist/downstream-input.js +2 -0
  49. package/dist/errors.d.ts +55 -17
  50. package/dist/errors.js +95 -41
  51. package/dist/execute.js +152 -114
  52. package/dist/executor-result.js +3 -1
  53. package/dist/guest-functions.js +83 -0
  54. package/dist/guest-runtime.js +60 -19
  55. package/dist/guest-types.d.ts +19 -5
  56. package/dist/http/argument-problems.js +35 -0
  57. package/dist/http/errors.js +5 -4
  58. package/dist/http/fields.js +192 -13
  59. package/dist/http/hatch-response.js +73 -0
  60. package/dist/http/hatch.js +88 -23
  61. package/dist/http/oauth.js +3 -2
  62. package/dist/http/provider.js +32 -4
  63. package/dist/http/safety.js +8 -0
  64. package/dist/http/tools.js +44 -23
  65. package/dist/index.d.ts +1 -1
  66. package/dist/index.js +9 -2
  67. package/dist/invocation.js +26 -8
  68. package/dist/membership.d.ts +13 -5
  69. package/dist/membership.js +34 -2
  70. package/dist/meta-tools.js +54 -31
  71. package/dist/native-recovery.js +4 -16
  72. package/dist/native-tools.js +5 -2
  73. package/dist/oauth-sealing.js +10 -0
  74. package/dist/operation-vocabulary.js +135 -13
  75. package/dist/operator-record.d.ts +2 -2
  76. package/dist/operator-record.js +1 -0
  77. package/dist/operator-ui/contract.d.ts +10 -0
  78. package/dist/operator-ui/generated.js +2 -2
  79. package/dist/operator-ui/model.d.ts +22 -1
  80. package/dist/provider-call-facts.js +6 -2
  81. package/dist/providers/_shared/rest/operation-index.js +48 -50
  82. package/dist/providers/_shared/rest/value-safety.js +80 -0
  83. package/dist/providers/breeze/api.js +7 -1
  84. package/dist/providers/breeze/skill.generated.js +1 -1
  85. package/dist/providers/breeze/tools.js +4 -3
  86. package/dist/providers/ccb/api.js +2 -1
  87. package/dist/providers/ccb/index.js +5 -0
  88. package/dist/providers/ccb/skill.generated.js +1 -1
  89. package/dist/providers/cloudflare/api.js +8 -1
  90. package/dist/providers/cloudflare/skill.generated.js +1 -1
  91. package/dist/providers/docs/lib/schemas.js +24 -22
  92. package/dist/providers/docs/safety.js +2 -0
  93. package/dist/providers/docs/skill.generated.js +1 -1
  94. package/dist/providers/docs/tools.js +2 -4
  95. package/dist/providers/drive/api.js +1 -0
  96. package/dist/providers/drive/lib/contracts.js +8 -5
  97. package/dist/providers/drive/skill.generated.js +1 -1
  98. package/dist/providers/drive/tools.js +11 -16
  99. package/dist/providers/forms/api.js +1 -0
  100. package/dist/providers/forms/skill.generated.js +1 -1
  101. package/dist/providers/forms/tools.js +10 -7
  102. package/dist/providers/github/api.js +1 -0
  103. package/dist/providers/github/lib/discovery.js +1 -1
  104. package/dist/providers/github/skill.generated.js +1 -1
  105. package/dist/providers/github/tools.js +61 -17
  106. package/dist/providers/gmail/lib/schemas.js +60 -50
  107. package/dist/providers/gmail/skill.generated.js +1 -1
  108. package/dist/providers/gmail/tools.js +7 -5
  109. package/dist/providers/granola/index.js +2 -0
  110. package/dist/providers/granola/skill.generated.js +1 -1
  111. package/dist/providers/overflow/api.js +7 -1
  112. package/dist/providers/overflow/skill.generated.js +1 -1
  113. package/dist/providers/planning-center/api.js +3 -1
  114. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  115. package/dist/providers/planning-center/skill.generated.js +1 -1
  116. package/dist/providers/quickbooks/safety.js +80 -39
  117. package/dist/providers/quickbooks/skill.generated.js +1 -1
  118. package/dist/providers/sheets/api.js +1 -0
  119. package/dist/providers/sheets/skill.generated.js +1 -1
  120. package/dist/providers/sheets/tools.js +13 -14
  121. package/dist/providers/slides/skill.generated.js +1 -1
  122. package/dist/providers/slides/tools.js +15 -13
  123. package/dist/providers/stripe/skill.generated.js +1 -1
  124. package/dist/providers/tithely/api.js +3 -1
  125. package/dist/providers/tithely/skill.generated.js +1 -1
  126. package/dist/providers/vercel/api.js +1 -1
  127. package/dist/providers/vercel/skill.generated.js +1 -1
  128. package/dist/providers/vercel/tools.js +4 -3
  129. package/dist/registry.d.ts +10 -4
  130. package/dist/registry.js +41 -13
  131. package/dist/routes/credentials.js +129 -60
  132. package/dist/routes/dispatch.js +5 -1
  133. package/dist/routes/mcp.js +30 -14
  134. package/dist/routes/operator.js +2 -2
  135. package/dist/routes/shared.d.ts +6 -1
  136. package/dist/routes/shared.js +7 -1
  137. package/dist/routes/ui-config.js +2 -2
  138. package/dist/routes/ui-data.js +126 -20
  139. package/dist/routes/ui.js +16 -0
  140. package/dist/runtime/call.js +7 -2
  141. package/dist/runtime/native-storage.js +12 -0
  142. package/dist/runtime/request-reads.js +207 -0
  143. package/dist/runtime/request-scope.js +16 -0
  144. package/dist/runtime/storage.js +12 -0
  145. package/dist/skills.js +37 -20
  146. package/dist/storage/keys.js +53 -3
  147. package/dist/storage/memory.js +8 -2
  148. package/dist/storage/sql-native.js +53 -1
  149. package/dist/storage/sql.js +12 -2
  150. package/dist/storage-digest.js +32 -0
  151. package/dist/timeout.js +8 -0
  152. package/dist/types.d.ts +33 -0
  153. package/dist/usage-guide.js +16 -35
  154. package/dist/validate.d.ts +3 -0
  155. package/dist/validate.js +220 -158
  156. package/dist/version.d.ts +1 -1
  157. package/dist/version.js +1 -1
  158. package/dist/write-outcome.js +0 -3
  159. package/documentation/README.md +13 -13
  160. package/documentation/architecture.md +385 -20
  161. package/documentation/auth.md +233 -64
  162. package/documentation/code-mode.md +122 -39
  163. package/documentation/deploying.md +6 -3
  164. package/documentation/integrating.md +16 -2
  165. package/documentation/meta-tools.md +240 -49
  166. package/documentation/migration-0.33.md +5 -1
  167. package/documentation/migration-0.34.md +3 -1
  168. package/documentation/operator-tests.md +1 -1
  169. package/documentation/operator-ui.md +105 -12
  170. package/documentation/pool-membership-upgrade.md +44 -0
  171. package/documentation/providers.md +105 -0
  172. package/examples/worker/AGENTS.md +10 -10
  173. package/examples/worker/README.md +4 -3
  174. package/examples/worker/src/connecta.config.ts +1 -0
  175. package/guides/connecting-agents.md +2 -1
  176. package/guides/how-it-works.md +4 -2
  177. package/package.json +1 -1
  178. package/templates/node/AGENTS.md +13 -12
  179. package/templates/node/package.json +1 -1
  180. 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 { boundedEchoText, ConnectorCallError } from "./errors.js";
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.filter((unit, index) => {
153
+ return units.flatMap((unit, index) => {
157
154
  if (CONTAINER_VALIDATION_KEYWORDS.has(unit.keyword))
158
- return false;
155
+ return [];
159
156
  if (isDuplicateAdditionalPropertiesBranch(schema, units, index)) {
160
- return false;
157
+ return [];
161
158
  }
162
159
  const key = validationUnitKey(unit);
163
160
  if (seen.has(key))
164
- return false;
161
+ return [];
165
162
  seen.add(key);
166
- return true;
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
- const BOUND_KEYWORDS = [
229
- "minimum",
230
- "maximum",
231
- "exclusiveMinimum",
232
- "exclusiveMaximum",
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
- function repairDetails(schema, args, units, validator) {
404
- const root = schemaObject(schema);
405
- const keys = (node) => Object.entries(schemaObject(node.properties))
406
- .filter(([, value]) => value !== false)
407
- .map(([key]) => key);
408
- const issues = units.slice(0, MAX_ARGUMENT_VALIDATION_ISSUES).map((unit) => {
409
- const missing = unit.keyword === "required" ? REQUIRED_PROPERTY_RE.exec(unit.error)?.[1] : undefined;
410
- const location = missing === undefined ? unit.instanceLocation : `${unit.instanceLocation}/${encodePointerPart(missing)}`;
411
- const parentLocation = unit.keywordLocation.replace(/\/[^/]+$/, "");
412
- const parent = schemaObject(pointerValue(schema, parentLocation));
413
- const node = missing === undefined ? parent : schemaObject(schemaObject(parent.properties)[missing]);
414
- const bounds = Object.fromEntries(BOUND_KEYWORDS.flatMap((key) => (typeof node[key] === "number" ? [[key, node[key]]] : [])));
415
- // A false subschema under additionalProperties names its parent object's keys.
416
- const closedParent = unit.keyword === "false" && unit.keywordLocation.endsWith("/additionalProperties")
417
- ? schemaObject(pointerValue(schema, unit.keywordLocation.slice(0, -"/additionalProperties".length)))
418
- : node;
419
- const acceptedKeys = keys(closedParent);
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: argumentPath(location),
422
- receivedType: receivedType(pointerValue(args, location)),
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 Object.entries(schemaObject(node.dependentRequired ?? node.dependencies))) {
434
- if (Array.isArray(required) && required.every((key) => typeof key === "string")) {
435
- conditionalRequirements.push({ path, condition: { required: [key] }, required });
436
- }
437
- }
438
- for (const [key, branch] of Object.entries(schemaObject(node.dependentSchemas))) {
439
- const required = schemaObject(branch).required;
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
- for (const key of ["allOf"]) {
458
- if (Array.isArray(node[key]))
459
- for (const child of node[key])
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
- let example;
465
- let valid = false;
466
- try {
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(root) } : {}),
481
- issues,
549
+ ...(root.properties ? { acceptedKeys: keys } : {}),
482
550
  ...(conditionalRequirements.length ? { conditionalRequirements } : {}),
483
- ...(valid
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 nestedUnits = units.filter((unit) => unit.instanceLocation !== "#");
576
- // Told from the reviewed findings, never the validator's own sentences,
577
- // which quote the schema's types, enums, and patterns.
578
- const validation = validationDetails(schema, units);
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 otherUnits = units.filter((unit) => unit.instanceLocation !== "#/limit");
588
- const otherIssues = otherUnits.length > 0;
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(", ")}.${otherIssues ? ` ${ordinary}` : ""}` : ordinary, { validation, repair: repairDetails(schema, args, units, validator) });
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.38.0";
1
+ export declare const CONNECTA_VERSION = "0.40.0";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated from package.json by scripts/generate-version.mjs.
2
2
  // Workers-safe: no runtime filesystem read or deployment override.
3
- export const CONNECTA_VERSION = "0.38.0";
3
+ export const CONNECTA_VERSION = "0.40.0";
@@ -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
  : {}),
@@ -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).