@zackbart/connecta 0.37.1 → 0.39.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 (265) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +5 -4
  3. package/bin/connecta.mjs +1 -39
  4. package/bin/version.mjs +1 -1
  5. package/dist/access-tokens.js +2 -4
  6. package/dist/agent-identity.js +1 -1
  7. package/dist/agent-routes.js +231 -0
  8. package/dist/auth/clerk-sdk/generated.js +2 -1
  9. package/dist/auth/downstream-oauth/consent.js +519 -0
  10. package/dist/auth/downstream-oauth/grant-store.js +439 -0
  11. package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
  12. package/dist/auth/downstream-oauth/refresh-records.js +447 -0
  13. package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
  14. package/dist/auth/downstream-oauth.js +66 -2516
  15. package/dist/auth/downstream-revocation.js +1 -1
  16. package/dist/auth/native.js +27 -0
  17. package/dist/auth/oauth-grant.js +24 -0
  18. package/dist/auth/oauth-token-response.js +1 -1
  19. package/dist/auth/static-oauth.js +153 -131
  20. package/dist/auth/vercel-agent.js +2 -1
  21. package/dist/auth-elicitation.js +9 -3
  22. package/dist/authorization-handoff.js +11 -5
  23. package/dist/branding.d.ts +1 -15
  24. package/dist/branding.js +2 -2
  25. package/dist/bytes.js +42 -0
  26. package/dist/call-admission-context.js +2 -5
  27. package/dist/call-recovery.js +0 -3
  28. package/dist/catalog-cache.js +130 -43
  29. package/dist/catalog-fingerprint.js +2 -18
  30. package/dist/catalog-policy.js +18 -0
  31. package/dist/catalog-service.js +285 -48
  32. package/dist/catalog.js +18 -2
  33. package/dist/config-defaults.d.ts +1 -0
  34. package/dist/config-defaults.js +1 -0
  35. package/dist/config-schema.d.ts +1 -1
  36. package/dist/config-schema.js +6 -2
  37. package/dist/config.d.ts +57 -25
  38. package/dist/config.js +30 -77
  39. package/dist/connector-caller.d.ts +1 -1
  40. package/dist/connector-health.js +59 -0
  41. package/dist/connector-status.js +117 -0
  42. package/dist/connectors/api-connector.d.ts +7 -2
  43. package/dist/connectors/api-connector.js +11 -7
  44. package/dist/connectors/guarded-fetch.d.ts +0 -6
  45. package/dist/connectors/guarded-fetch.js +1 -11
  46. package/dist/connectors/negotiation-cache.js +22 -4
  47. package/dist/connectors/remote-mcp/call.js +164 -0
  48. package/dist/connectors/remote-mcp/catalog.js +278 -0
  49. package/dist/connectors/remote-mcp/config.js +263 -0
  50. package/dist/connectors/remote-mcp/connection.js +671 -0
  51. package/dist/connectors/remote-mcp/errors.js +488 -0
  52. package/dist/connectors/remote-mcp/oauth.js +469 -0
  53. package/dist/connectors/remote-mcp/resources.js +473 -0
  54. package/dist/connectors/remote-mcp/runtime.js +1 -0
  55. package/dist/connectors/remote-mcp/transport.js +544 -0
  56. package/dist/connectors/remote-mcp.js +61 -2898
  57. package/dist/connectors/resource-uri.js +0 -4
  58. package/dist/credential-contract.d.ts +23 -0
  59. package/dist/credential-reads.js +17 -0
  60. package/dist/credential-verification.js +53 -0
  61. package/dist/credentials.d.ts +7 -0
  62. package/dist/credentials.js +60 -25
  63. package/dist/d1.d.ts +0 -89
  64. package/dist/d1.js +0 -34
  65. package/dist/describe-config.d.ts +12 -2
  66. package/dist/describe-config.js +2 -0
  67. package/dist/discovery-health.js +199 -0
  68. package/dist/downstream-input.js +4 -2
  69. package/dist/errors.d.ts +55 -17
  70. package/dist/errors.js +95 -41
  71. package/dist/execute.js +172 -125
  72. package/dist/executor-result.js +5 -2
  73. package/dist/executors/quickjs-runtime.js +2 -25
  74. package/dist/executors/quickjs.js +1 -1
  75. package/dist/guest-functions.js +83 -0
  76. package/dist/guest-runtime.js +63 -20
  77. package/dist/guest-types.d.ts +24 -10
  78. package/dist/http/argument-problems.js +35 -0
  79. package/dist/http/client.js +1 -1
  80. package/dist/http/errors.js +7 -6
  81. package/dist/http/fields.js +192 -13
  82. package/dist/http/hatch-response.js +73 -0
  83. package/dist/http/hatch.js +88 -23
  84. package/dist/http/json-schema.js +1 -1
  85. package/dist/http/node-transport.js +0 -1
  86. package/dist/http/oauth.js +4 -3
  87. package/dist/http/paging.js +6 -9
  88. package/dist/http/provider.js +33 -5
  89. package/dist/http/safety.js +9 -1
  90. package/dist/http/services.js +1 -8
  91. package/dist/http/tools.js +45 -30
  92. package/dist/http/vendor.js +7 -7
  93. package/dist/identity.js +2 -6
  94. package/dist/index.d.ts +1 -1
  95. package/dist/index.js +9 -2
  96. package/dist/invocation-auth.js +2 -1
  97. package/dist/invocation.js +21 -6
  98. package/dist/membership.d.ts +13 -5
  99. package/dist/membership.js +34 -2
  100. package/dist/meta-tools.js +50 -35
  101. package/dist/native-recovery.js +4 -16
  102. package/dist/native-tools.js +6 -2
  103. package/dist/oauth-handoff.js +2 -2
  104. package/dist/oauth-sealing.js +10 -0
  105. package/dist/operation-vocabulary.js +136 -14
  106. package/dist/operator-record.d.ts +2 -2
  107. package/dist/operator-record.js +2 -49
  108. package/dist/operator-ui/contract.d.ts +12 -2
  109. package/dist/operator-ui/generated.js +3 -3
  110. package/dist/operator-ui/model.d.ts +22 -1
  111. package/dist/program-writes.js +3 -8
  112. package/dist/provider-call.js +0 -7
  113. package/dist/provider.d.ts +1 -1
  114. package/dist/providers/_shared/google/kit.js +1 -6
  115. package/dist/providers/_shared/google/service-account.js +1 -3
  116. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  117. package/dist/providers/_shared/guide.js +3 -0
  118. package/dist/providers/_shared/json.js +12 -0
  119. package/dist/providers/_shared/rest/compact-index.js +1 -1
  120. package/dist/providers/_shared/rest/kit.js +1 -3
  121. package/dist/providers/_shared/rest/operation-index.js +49 -53
  122. package/dist/providers/_shared/rest/value-safety.js +106 -28
  123. package/dist/providers/breeze/api.js +9 -8
  124. package/dist/providers/breeze/index.js +4 -2
  125. package/dist/providers/breeze/skill.generated.js +1 -1
  126. package/dist/providers/breeze/tools.js +7 -7
  127. package/dist/providers/ccb/api.js +3 -7
  128. package/dist/providers/ccb/index.js +7 -1
  129. package/dist/providers/ccb/skill.generated.js +1 -1
  130. package/dist/providers/ccb/tools.js +1 -6
  131. package/dist/providers/cloudflare/api.js +10 -3
  132. package/dist/providers/cloudflare/index.js +2 -1
  133. package/dist/providers/cloudflare/safety.js +3 -3
  134. package/dist/providers/cloudflare/skill.generated.js +1 -1
  135. package/dist/providers/docs/lib/schemas.js +24 -22
  136. package/dist/providers/docs/safety.js +2 -0
  137. package/dist/providers/docs/skill.generated.js +1 -1
  138. package/dist/providers/docs/tools.js +4 -14
  139. package/dist/providers/drive/api.js +1 -0
  140. package/dist/providers/drive/lib/contracts.js +8 -5
  141. package/dist/providers/drive/skill.generated.js +1 -1
  142. package/dist/providers/drive/tools.js +23 -41
  143. package/dist/providers/forms/api.js +1 -0
  144. package/dist/providers/forms/skill.generated.js +1 -1
  145. package/dist/providers/forms/tools.js +11 -16
  146. package/dist/providers/github/api.js +1 -0
  147. package/dist/providers/github/lib/discovery.js +1 -1
  148. package/dist/providers/github/skill.generated.js +1 -1
  149. package/dist/providers/github/tools.js +61 -17
  150. package/dist/providers/gmail/lib/schemas.js +60 -50
  151. package/dist/providers/gmail/skill.generated.js +1 -1
  152. package/dist/providers/gmail/tools.js +12 -26
  153. package/dist/providers/granola/index.js +2 -0
  154. package/dist/providers/granola/skill.generated.js +1 -1
  155. package/dist/providers/overflow/api.js +8 -4
  156. package/dist/providers/overflow/index.js +2 -1
  157. package/dist/providers/overflow/skill.generated.js +1 -1
  158. package/dist/providers/overflow/tools.js +3 -7
  159. package/dist/providers/planning-center/api.js +5 -2
  160. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  161. package/dist/providers/planning-center/lib/json-api.js +1 -9
  162. package/dist/providers/planning-center/skill.generated.js +1 -1
  163. package/dist/providers/planning-center/tools.js +2 -1
  164. package/dist/providers/quickbooks/safety.js +80 -39
  165. package/dist/providers/quickbooks/skill.generated.js +1 -1
  166. package/dist/providers/sheets/api.js +1 -0
  167. package/dist/providers/sheets/skill.generated.js +1 -1
  168. package/dist/providers/sheets/tools.js +14 -23
  169. package/dist/providers/slides/skill.generated.js +1 -1
  170. package/dist/providers/slides/tools.js +19 -26
  171. package/dist/providers/stripe/index.js +2 -1
  172. package/dist/providers/stripe/safety.js +2 -2
  173. package/dist/providers/stripe/skill.generated.js +1 -1
  174. package/dist/providers/tithely/api.js +5 -8
  175. package/dist/providers/tithely/index.js +3 -2
  176. package/dist/providers/tithely/skill.generated.js +1 -1
  177. package/dist/providers/tithely/tools.js +7 -5
  178. package/dist/providers/vercel/api.js +2 -7
  179. package/dist/providers/vercel/lib/pin.js +1 -1
  180. package/dist/providers/vercel/safety.js +1 -1
  181. package/dist/providers/vercel/skill.generated.js +1 -1
  182. package/dist/providers/vercel/tools.js +6 -4
  183. package/dist/redaction-marker.js +2 -2
  184. package/dist/registry.d.ts +11 -5
  185. package/dist/registry.js +46 -20
  186. package/dist/request-principal.js +1 -1
  187. package/dist/request-state.js +2 -2
  188. package/dist/routes/access-tokens.js +1 -1
  189. package/dist/routes/activity-disclosure.js +1 -1
  190. package/dist/routes/connect.js +2 -2
  191. package/dist/routes/credentials.js +130 -61
  192. package/dist/routes/dispatch.js +5 -1
  193. package/dist/routes/mcp.js +34 -17
  194. package/dist/routes/oauth-management.js +1 -1
  195. package/dist/routes/operator.js +6 -5
  196. package/dist/routes/shared.d.ts +13 -8
  197. package/dist/routes/shared.js +15 -9
  198. package/dist/routes/ui-config.js +3 -3
  199. package/dist/routes/ui-data.js +126 -20
  200. package/dist/routes/ui.js +17 -1
  201. package/dist/runtime/call-admission.js +1 -1
  202. package/dist/runtime/call.js +8 -3
  203. package/dist/runtime/native-storage.js +9 -0
  204. package/dist/runtime/request-reads.js +182 -0
  205. package/dist/runtime/request-scope.js +16 -0
  206. package/dist/runtime/settlement.js +1 -1
  207. package/dist/runtime/shared-read.js +2 -15
  208. package/dist/runtime/storage.js +12 -0
  209. package/dist/sent-secrets.d.ts +1 -2
  210. package/dist/sent-secrets.js +3 -8
  211. package/dist/server.js +4 -4
  212. package/dist/skills.js +44 -54
  213. package/dist/sqlite.d.ts +0 -19
  214. package/dist/sqlite.js +2 -83
  215. package/dist/storage/keys.js +69 -128
  216. package/dist/storage/memory.js +8 -2
  217. package/dist/storage/sql-native.js +27 -101
  218. package/dist/storage/sql.d.ts +1 -24
  219. package/dist/storage/sql.js +13 -14
  220. package/dist/storage-digest.js +32 -0
  221. package/dist/timeout.js +8 -0
  222. package/dist/tool-error.js +4 -0
  223. package/dist/types.d.ts +33 -0
  224. package/dist/url-safety.js +0 -4
  225. package/dist/usage-guide.js +18 -37
  226. package/dist/validate.d.ts +3 -0
  227. package/dist/validate.js +220 -158
  228. package/dist/version.d.ts +1 -1
  229. package/dist/version.js +1 -1
  230. package/dist/worker.js +1 -1
  231. package/dist/write-outcome.js +0 -3
  232. package/documentation/README.md +19 -55
  233. package/documentation/architecture.md +436 -515
  234. package/documentation/auth.md +229 -88
  235. package/documentation/code-mode.md +201 -94
  236. package/documentation/deploying.md +25 -43
  237. package/documentation/integrating.md +18 -19
  238. package/documentation/meta-tools.md +225 -51
  239. package/documentation/migration-0.33.md +10 -6
  240. package/documentation/migration-0.34.md +5 -5
  241. package/documentation/migration-retired-layouts.md +74 -0
  242. package/documentation/operator-tests.md +20 -120
  243. package/documentation/operator-ui.md +108 -16
  244. package/documentation/pool-membership-upgrade.md +44 -0
  245. package/documentation/provider-auth-migration.md +4 -10
  246. package/documentation/provider-migration-0.29.md +4 -15
  247. package/documentation/providers.md +105 -0
  248. package/examples/worker/AGENTS.md +14 -16
  249. package/examples/worker/README.md +24 -118
  250. package/examples/worker/src/connecta.config.ts +1 -0
  251. package/guides/connecting-agents.md +9 -31
  252. package/guides/how-it-works.md +6 -4
  253. package/guides/safety-and-trust.md +4 -1
  254. package/package.json +19 -19
  255. package/templates/node/AGENTS.md +13 -12
  256. package/templates/node/README.md +9 -26
  257. package/templates/node/package.json +1 -1
  258. package/templates/node/src/connecta.config.ts +1 -0
  259. package/dist/auth/oauth-v2-migration.js +0 -135
  260. package/dist/provider-call-facts.d.ts +0 -115
  261. package/dist/provider-call.d.ts +0 -112
  262. package/dist/storage/d1-copy.js +0 -255
  263. package/dist/storage/sql-schema.d.ts +0 -6
  264. package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
  265. package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
@@ -20,8 +20,12 @@ export interface UiTool extends Pick<OperatorTool, "name" | "address" | "descrip
20
20
  * (`credential_required`), or a secret that lives in deployment configuration
21
21
  * (`auth_required`). Principal forwarding instead reports `caller_refused`,
22
22
  * which has no secret-repair prompt.
23
+ *
24
+ * `connector_timeout` is a downstream that did not answer before the operator
25
+ * page's deadline: slow or unreachable, not known to be broken. The page
26
+ * stops retrying it on its own until someone asks with Refresh.
23
27
  */
24
- export type UiProblem = "connector_unavailable" | "oauth_required" | "credential_required" | "auth_required" | "caller_refused" | "credential_mismatch" | "catalog_failed";
28
+ export type UiProblem = "connector_unavailable" | "connector_timeout" | "oauth_required" | "credential_required" | "auth_required" | "caller_refused" | "credential_mismatch" | "catalog_failed";
25
29
  /** A stored credential that cannot be used, by cause. */
26
30
  export type UiCredentialProblem = "credential_mismatch" | "credential_unreadable";
27
31
  interface UiCredentialField {
@@ -35,6 +39,13 @@ interface UiCredentialField {
35
39
  lastFour?: string;
36
40
  updatedAt?: string;
37
41
  }
42
+ /** Mirrors `CredentialVerification` in src/credential-verification.ts. */
43
+ export interface UiVerification {
44
+ outcome: "ok" | "unverified" | UiCredentialFailure;
45
+ observedAt: string;
46
+ }
47
+ /** Why a credential test failed, as the route reports it. */
48
+ export type UiCredentialFailure = "rejected" | "scope" | "unreachable" | "timeout" | "failed";
38
49
  interface UiCredential {
39
50
  label: string;
40
51
  description?: string;
@@ -83,11 +94,21 @@ export interface UiConnector {
83
94
  authorizationUrl?: string;
84
95
  toolCount: number;
85
96
  tools: UiTool[];
97
+ /** Age of the stored status observation this row shows; absent on a live read. */
98
+ observedAgeMs?: number;
99
+ /** Browser-only: the stored observation's time, fixed as the row lands. */
100
+ observedAt?: number;
86
101
  /** This connector exposes manageable downstream OAuth lifecycle hooks. */
87
102
  oauth?: boolean;
88
103
  /** Selected downstream client identity mechanism; never a client ID or secret. */
89
104
  registrationPath?: "cimd" | "dcr" | "static";
90
105
  credential?: UiCredential;
106
+ /**
107
+ * The stored last verification of this connector's operator-managed
108
+ * credential. Absent when nothing has verified the save stored now. A
109
+ * typed outcome and a time; never vendor text.
110
+ */
111
+ verification?: UiVerification;
91
112
  /**
92
113
  * Drift the last catalog refresh saw *in this runtime*
93
114
  * ([#343](https://github.com/zackbart/connecta/issues/343)). Four counts and
@@ -1,8 +1,8 @@
1
+ import { toolError } from "./tool-error.js";
1
2
  // Budget and outcome accounting for writes dispatched by programs on writable endpoints.
2
3
  // Await each dispatched write once; never conceal an unknown outcome.
3
4
  import { CONFIG_DEFAULTS } from "./config-defaults.js";
4
5
  import { Effect } from "effect";
5
- import { jsonResult } from "./meta-tools.js";
6
6
  import { classifyWriteOutcome } from "./write-outcome.js";
7
7
  /** Writes one program may send (`execute.maxWrites`), unless configured. */
8
8
  export const DEFAULT_MAX_WRITES = CONFIG_DEFAULTS.execute.maxWrites;
@@ -125,7 +125,7 @@ export class ProgramWrites {
125
125
  // the result, as a direct call reports it: its code and handoff, never
126
126
  // retryable, with the write's outcome unknown.
127
127
  const handoff = this.handoff;
128
- return errorEnvelope({
128
+ return toolError({
129
129
  code: handoff?.code ?? "write_outcome_unknown",
130
130
  message: handoff
131
131
  ? "A write this program sent failed authorization after it left, so whether it took effect is unknown, and that is the result rather than what the program returned. It will not be sent again. Check its target before doing anything that depends on it, and give nextAction to the operator to recover the connector. Argument echoes may be partial or absent; use the original arguments if reconciliation requires another call."
@@ -153,7 +153,7 @@ export class ProgramWrites {
153
153
  function withWrites(result, writes) {
154
154
  const structured = result.structuredContent;
155
155
  const error = structured?.error;
156
- const annotated = errorEnvelope({
156
+ const annotated = toolError({
157
157
  ...(error !== null && typeof error === "object"
158
158
  ? error
159
159
  : {
@@ -171,8 +171,3 @@ function withWrites(result, writes) {
171
171
  }
172
172
  return annotated;
173
173
  }
174
- function errorEnvelope(error) {
175
- const result = jsonResult({ error });
176
- result.isError = true;
177
- return result;
178
- }
@@ -261,11 +261,4 @@ export function providerFetch(ctx, provider, auxiliary, send) {
261
261
  export function inLogicalCall(ctx) {
262
262
  return recordedService(ctx, LogicalLedger) !== undefined;
263
263
  }
264
- /**
265
- * Every resource request dispatched under `ctx`'s logical call, from any
266
- * ledger, nested ones included; undefined outside one.
267
- */
268
- export function logicalCallRequests(ctx) {
269
- return ctx ? recordedService(ctx, LogicalLedger)?.sent : undefined;
270
- }
271
264
  export { recordProviderResponse, tracksEveryRequest, tracksRequests, delegatesEachCall, providerBoundary, refusedStatus, vendorRefusal, authorizationFailure, unauthorizedAnswer, renewedAfterUnauthorized, refusalRequest, knownProviderFailure, isKnownProviderFailure, forgetProviderFailure, explainedUnauthorizedVerdict, explainsUnauthorized, settledWrite, isSettledWrite, } from "./provider-call-facts.js";
@@ -120,7 +120,7 @@ export interface ProviderGuideBlocks<O> {
120
120
  select(options: Readonly<O>): readonly string[];
121
121
  }
122
122
  /**
123
- * Facts the provider kit (decision 0010) declares for `providers:generate`:
123
+ * Facts the provider kit declares for `providers:generate`:
124
124
  * `drift.json`'s `generated.operations` and the guide's Conventions. Plain
125
125
  * data, never read at runtime. The kit's `defineApiProvider` sets them.
126
126
  */
@@ -1,3 +1,4 @@
1
+ import { asRecord, asArray } from "../json.js";
1
2
  // Google Workspace on the provider kit (decision 0010; C.7 step 1 under 0011).
2
3
  // Shared by every Workspace product; never its own export. `workspace.ts`
3
4
  // keeps shared options, connection parsing and subject resolution;
@@ -17,12 +18,6 @@ import { delegatedToken, forgetDelegatedToken } from "./service-account.js";
17
18
  import { subjectFor, workspaceConnection } from "./workspace.js";
18
19
  import { tagWorkspaceSubject } from "../../../workspace-subject.js";
19
20
  import { googleNamedOperation, requireGoogleIndex } from "./hatch.js";
20
- function asRecord(value) {
21
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
22
- }
23
- function asArray(value) {
24
- return Array.isArray(value) ? value : [];
25
- }
26
21
  function reasonsOf(tokens) {
27
22
  const error = asRecord(asRecord(tokens)["error"]);
28
23
  const reasons = new Set();
@@ -1,3 +1,4 @@
1
+ import { asRecord } from "../json.js";
1
2
  /**
2
3
  * A Google service account acting as itself or for Workspace users through
3
4
  * domain-wide delegation: the key, signed JWT-bearer assertions and short-lived
@@ -266,9 +267,6 @@ const tokenTransport = guardedFetch({
266
267
  // The assertion in the body is the authentication; there is no header.
267
268
  authenticate: () => ({}),
268
269
  });
269
- function asRecord(value) {
270
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
271
- }
272
270
  /** The token endpoint's OAuth `error` codes this module names back. */
273
271
  const TOKEN_ERROR_CODES = new Set([
274
272
  "invalid_request",
@@ -92,10 +92,10 @@ export declare const GOOGLE_WORKSPACE_OPTIONS: import("../../../config-schema.js
92
92
  };
93
93
  instructions: import("../../../config-schema.js").Field<unknown, unknown, true>;
94
94
  title: import("../../../config-schema.js").Field<unknown, unknown, true>;
95
+ baseUrl: import("../../../config-schema.js").Field<unknown, unknown, true>;
95
96
  purpose: import("../../../config-schema.js").Field<unknown, unknown, true>;
96
97
  maxResultBytes: import("../../../config-schema.js").Field<unknown, unknown, true>;
97
98
  readOnly: import("../../../config-schema.js").Field<unknown, unknown, true>;
98
- baseUrl: import("../../../config-schema.js").Field<unknown, unknown, true>;
99
99
  subject: import("../../../config-schema.js").Field<unknown, unknown, true>;
100
100
  };
101
101
  };
@@ -0,0 +1,3 @@
1
+ export function readOnlyNotice(readOnly) {
2
+ return readOnly ? ["This connection is read-only: every write is refused."] : [];
3
+ }
@@ -0,0 +1,12 @@
1
+ export function isRecord(value) {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3
+ }
4
+ export function asRecord(value) {
5
+ return isRecord(value) ? value : {};
6
+ }
7
+ export function asArray(value) {
8
+ return Array.isArray(value) ? value : [];
9
+ }
10
+ export function compact(value) {
11
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
12
+ }
@@ -23,7 +23,7 @@ function rowText(packed, row) {
23
23
  return `[[${parameters.join(",")}],${body}]`;
24
24
  }
25
25
  /** Recreate the exact old JSON string, for semantic identity/cursor binding. */
26
- export function expandIndexDetails(packed) {
26
+ function expandIndexDetails(packed) {
27
27
  limit(packed.length);
28
28
  let length = packed.definitions.length + 13; // {"d":defs,"o":[]}
29
29
  limit(length);
@@ -1,12 +1,10 @@
1
+ import { isRecord } from "../json.js";
1
2
  // Shared REST call shapes, pinned-schema conversion and indexed vendor declarations.
2
3
  import { Effect, Schema } from "effect";
3
4
  import { ConnectorCallError } from "../../../errors.js";
4
5
  import { Refusal } from "../../../http/errors.js";
5
6
  import { Integer, isMinChars, isMaxChars } from "../../../http/json-schema.js";
6
7
  import { vendor as declareVendor, get, head, post, put, patch, del, } from "../../../http/vendor.js";
7
- function isRecord(value) {
8
- return !!value && typeof value === "object" && !Array.isArray(value);
9
- }
10
8
  function invalid(message) {
11
9
  throw new ConnectorCallError("invalid_args", message);
12
10
  }
@@ -1,3 +1,4 @@
1
+ import { isRecord } from "../json.js";
1
2
  import { canonicalJson } from "../../../canonical-json.js";
2
3
  import { parseIndexDetails } from "./compact-index.js";
3
4
  // The pinned operation index a REST connector validates against before it
@@ -10,7 +11,7 @@ import { parseIndexDetails } from "./compact-index.js";
10
11
  // are refused with the accepted names, because a guessed argument is the most
11
12
  // common way an agent wastes a call. Drift between the pin and the vendor is
12
13
  // caught by `providers:check` and refreshed by `providers:spec`.
13
- import { ConnectorCallError } from "../../../errors.js";
14
+ import { ConnectorCallError, issueSummary } from "../../../errors.js";
14
15
  const METHODS = new Set(["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"]);
15
16
  const MAX_ISSUES = 20;
16
17
  function words(text) {
@@ -20,14 +21,16 @@ function words(text) {
20
21
  .split(/[^a-z0-9]+/)
21
22
  .filter(Boolean);
22
23
  }
23
- function isRecord(value) {
24
- return typeof value === "object" && value !== null && !Array.isArray(value);
25
- }
26
- function invalid(message, issues, repair) {
27
- throw new ConnectorCallError("invalid_args", message, {
28
- ...(issues ? { validation: { issues } } : {}),
29
- ...(repair ? { repair } : {}),
30
- });
24
+ /** Index findings name no example: the hatch schema cannot prove an operation's arguments safe to echo. */
25
+ function invalid(message, issues, details) {
26
+ throw new ConnectorCallError("invalid_args", message, issues
27
+ ? {
28
+ validation: {
29
+ issues,
30
+ exampleUnavailable: `Examples are withheld for API operations; read ${details} for the contract.`,
31
+ },
32
+ }
33
+ : {});
31
34
  }
32
35
  function typesOf(node) {
33
36
  return node.t === undefined ? [] : typeof node.t === "string" ? [node.t] : node.t;
@@ -44,7 +47,7 @@ function distance(a, b) {
44
47
  }
45
48
  return previous[b.length];
46
49
  }
47
- /** The accepted names, bounded and led by the likeliest intended one; repair details carry the full list. */
50
+ /** The accepted names, bounded and led by the likeliest intended one; the full list is in the tool schema. */
48
51
  function oneOf(names, given) {
49
52
  const close = given === undefined
50
53
  ? undefined
@@ -247,7 +250,7 @@ export class OperationIndex {
247
250
  best.rows.push(row);
248
251
  });
249
252
  if (best.rows.length === 0) {
250
- invalid(`${this.#title} has no ${path} operation in the pinned API index (${this.revision}). ` +
253
+ invalid(`${this.#title} has no operation at that path in the pinned API index (${this.revision}). ` +
251
254
  `Nearest: ${this.nearest(method, segments).join("; ")}. Find operations with ${this.#vendorTool("search")}.`);
252
255
  }
253
256
  const templates = new Set(best.rows.map((row) => this.ops[row].path));
@@ -281,17 +284,16 @@ export class OperationIndex {
281
284
  }
282
285
  });
283
286
  const issues = [];
284
- const repair = [];
285
287
  for (const [name, at, , schema] of this.#row(op)?.[0] ?? []) {
286
288
  if (at === "path" && params[name] !== undefined)
287
- this.#check(schema, decodeURIComponent(params[name]), `/path/${name}`, issues, repair);
289
+ this.#check(schema, decodeURIComponent(params[name]), `/path/${name}`, issues);
288
290
  }
289
291
  if (issues.length) {
290
292
  const shown = issues
291
293
  .slice(0, 5)
292
- .map((issue) => `${issue.path} (${issue.code}: ${issue.expected})`)
294
+ .map((issue) => issueSummary([issue]))
293
295
  .join("; ");
294
- invalid(`Not sent: the path does not match ${op.method} ${op.path} in the pinned API index: ${shown}. Substitute real ids matching ${this.#vendorTool("details")}.${issues.some((issue) => issue.expected?.includes("safe integer")) ? " Integer ids use canonical ASCII decimal notation within the safe-integer range." : ""}`, issues.slice(0, MAX_ISSUES));
296
+ invalid(`Not sent: the path does not match ${op.method} ${op.path} in the pinned API index: ${shown}. Substitute real ids matching ${this.#vendorTool("details")}.${issues.some((issue) => issue.expected?.includes("safe integer")) ? " Integer ids use canonical ASCII decimal notation within the safe-integer range." : ""}`, issues.slice(0, MAX_ISSUES), this.#vendorTool("details"));
295
297
  }
296
298
  return { op, params };
297
299
  }
@@ -460,7 +462,7 @@ export class OperationIndex {
460
462
  }
461
463
  }
462
464
  if (only !== undefined && parameters.length === 0 && body === undefined) {
463
- invalid(`${op.method} ${op.path} has no parameter named ${only}.`);
465
+ invalid(`${op.method} ${op.path} has no parameter by that name.`);
464
466
  }
465
467
  return {
466
468
  method: op.method,
@@ -482,7 +484,6 @@ export class OperationIndex {
482
484
  check(op, query, body) {
483
485
  const row = this.#row(op);
484
486
  const issues = [];
485
- const repair = [];
486
487
  const declared = (row ? row[0] : []).filter(([, at]) => at === "query");
487
488
  const accepted = declared.map(([name]) => name);
488
489
  for (const [name, value] of Object.entries(query)) {
@@ -491,40 +492,39 @@ export class OperationIndex {
491
492
  const param = declared.find(([key]) => key === name);
492
493
  if (!param) {
493
494
  issues.push({
494
- path: `/query/${name}`,
495
- code: "additionalProperties",
495
+ path: "/query/<key>",
496
496
  expected: accepted.length ? oneOf(accepted, name) : "no query parameters",
497
+ received: valueType(value),
497
498
  });
498
- repair.push({ path: `/query/${name}`, receivedType: valueType(value), acceptedKeys: accepted });
499
499
  continue;
500
500
  }
501
- this.#check(param[3], value, `/query/${name}`, issues, repair);
501
+ this.#check(param[3], value, `/query/${name}`, issues);
502
502
  }
503
503
  for (const [name, , required] of declared) {
504
504
  if (required === 1 && query[name] === undefined) {
505
- issues.push({ path: `/query/${name}`, code: "required", expected: "a value" });
505
+ issues.push({ path: `/query/${name}`, expected: "a value (required)", received: "missing" });
506
506
  }
507
507
  }
508
508
  const bodyRow = row && row[1] !== 0 ? row[1] : undefined;
509
509
  if (body !== undefined) {
510
510
  if (!bodyRow) {
511
- issues.push({ path: "/body", code: "false", expected: "no body for this operation" });
511
+ issues.push({ path: "/body", expected: "no body for this operation" });
512
512
  }
513
513
  else {
514
- this.#check(bodyRow[1], body, "/body", issues, repair);
514
+ this.#check(bodyRow[1], body, "/body", issues);
515
515
  }
516
516
  }
517
517
  else if (bodyRow?.[2] === 1) {
518
518
  // An optional body may be omitted whole; a required one may not.
519
- issues.push({ path: "/body", code: "required", expected: `a ${bodyRow[0]} body` });
519
+ issues.push({ path: "/body", expected: `a ${bodyRow[0]} body (required)`, received: "missing" });
520
520
  }
521
521
  if (issues.length > 0) {
522
522
  const shown = issues
523
523
  .slice(0, 5)
524
- .map((issue) => `${issue.path} (${issue.code}: ${issue.expected})`)
524
+ .map((issue) => issueSummary([issue]))
525
525
  .join("; ");
526
526
  invalid(`Not sent: the arguments do not match ${op.method} ${op.path} in the pinned API index: ${shown}` +
527
- `${issues.length > 5 ? `; ${issues.length - 5} more` : ""}. Read ${this.#vendorTool("details")} for the contract.`, issues.slice(0, MAX_ISSUES), repair.length ? { issues: repair.slice(0, 5) } : undefined);
527
+ `${issues.length > 5 ? `; ${issues.length - 5} more` : ""}. Read ${this.#vendorTool("details")} for the contract.`, issues.slice(0, MAX_ISSUES), this.#vendorTool("details"));
528
528
  }
529
529
  }
530
530
  #vendorTool(kind) {
@@ -608,7 +608,7 @@ export class OperationIndex {
608
608
  out["truncated"] = true;
609
609
  return out;
610
610
  }
611
- #check(node, value, at, issues, repair) {
611
+ #check(node, value, at, issues) {
612
612
  if (issues.length >= MAX_ISSUES)
613
613
  return;
614
614
  const schema = this.#deref(node);
@@ -626,7 +626,6 @@ export class OperationIndex {
626
626
  (integer && !Number.isSafeInteger(numeric))) {
627
627
  issues.push({
628
628
  path: at,
629
- code: "type",
630
629
  expected: integer ? "a canonical ASCII decimal safe integer" : "a canonical ASCII decimal number",
631
630
  });
632
631
  return;
@@ -637,7 +636,7 @@ export class OperationIndex {
637
636
  if (types.includes("boolean") && (value === "true" || value === "false"))
638
637
  value = value === "true";
639
638
  else {
640
- issues.push({ path: at, code: "type", expected: types.join(" or ") });
639
+ issues.push({ path: at, expected: types.join(" or ") });
641
640
  return;
642
641
  }
643
642
  }
@@ -646,7 +645,7 @@ export class OperationIndex {
646
645
  // Conflicting composed constraints (disjoint enums or types) admit nothing,
647
646
  // not even the null or "" that otherwise unsets a field.
648
647
  if (schema.e?.length === 0) {
649
- issues.push({ path: at, code: "enum", expected: "no value: this field's combined constraints admit none" });
648
+ issues.push({ path: at, expected: "no value: this field's combined constraints admit none" });
650
649
  return;
651
650
  }
652
651
  // Only newly opted-in bounded schemas enforce scalar numeric values;
@@ -663,42 +662,40 @@ export class OperationIndex {
663
662
  ? Number(value)
664
663
  : NaN;
665
664
  if (!Number.isFinite(numeric) || (integer && !Number.isSafeInteger(numeric))) {
666
- issues.push({ path: at, code: "type", expected: integer ? "integer" : "finite number" });
665
+ issues.push({ path: at, expected: integer ? "integer" : "finite number" });
667
666
  return;
668
667
  }
669
668
  if (schema.l !== undefined && numeric < schema.l)
670
- issues.push({ path: at, code: "minimum", expected: `at least ${schema.l}` });
669
+ issues.push({ path: at, expected: `at least ${schema.l}`, received: "number" });
671
670
  if (schema.u !== undefined && numeric > schema.u)
672
- issues.push({ path: at, code: "maximum", expected: `at most ${schema.u}` });
671
+ issues.push({ path: at, expected: `at most ${schema.u}`, received: "number" });
673
672
  }
674
673
  if (value === null || value === "")
675
674
  return;
676
675
  if (schema.a) {
677
676
  let fewest;
678
677
  for (const branch of schema.a) {
679
- const attempt = { issues: [], repair: [] };
680
- this.#check(branch, value, at, attempt.issues, attempt.repair);
681
- if (attempt.issues.length === 0)
678
+ const attempt = [];
679
+ this.#check(branch, value, at, attempt);
680
+ if (attempt.length === 0)
682
681
  return;
683
- if (!fewest || attempt.issues.length < fewest.issues.length)
682
+ if (!fewest || attempt.length < fewest.length)
684
683
  fewest = attempt;
685
684
  }
686
- if (fewest) {
687
- issues.push(...fewest.issues);
688
- repair.push(...fewest.repair);
689
- }
685
+ if (fewest)
686
+ issues.push(...fewest);
690
687
  return;
691
688
  }
692
689
  if (schema.e && (typeof value === "string" || typeof value === "number" || typeof value === "boolean")) {
693
690
  if (!schema.e.some((option) => option === value || String(option) === String(value))) {
694
- issues.push({ path: at, code: "enum", expected: oneOf(schema.e.map(String)) });
691
+ issues.push({ path: at, expected: oneOf(schema.e.map(String)) });
695
692
  }
696
693
  return;
697
694
  }
698
695
  if (schema.p || types.includes("object")) {
699
696
  if (!isRecord(value)) {
700
697
  if (types.length === 0 || types.includes("object")) {
701
- issues.push({ path: at, code: "type", expected: "object" });
698
+ issues.push({ path: at, expected: "object" });
702
699
  }
703
700
  return;
704
701
  }
@@ -709,35 +706,34 @@ export class OperationIndex {
709
706
  continue;
710
707
  const child = properties[key];
711
708
  if (child)
712
- this.#check(child, item, `${at}/${key}`, issues, repair);
709
+ this.#check(child, item, `${at}/${key}`, issues);
713
710
  else if (schema.m)
714
- this.#check(schema.m, item, `${at}/${key}`, issues, repair);
711
+ this.#check(schema.m, item, `${at}/<key>`, issues);
715
712
  else if (schema.p) {
716
713
  issues.push({
717
- path: `${at}/${key}`,
718
- code: "additionalProperties",
714
+ path: `${at}/<key>`,
719
715
  expected: accepted.length ? oneOf(accepted, key) : "no properties",
716
+ received: valueType(item),
720
717
  });
721
- repair.push({ path: `${at}/${key}`, receivedType: valueType(item), acceptedKeys: accepted });
722
718
  }
723
719
  }
724
720
  for (const key of schema.r ?? []) {
725
721
  if (value[key] === undefined)
726
- issues.push({ path: `${at}/${key}`, code: "required", expected: "a value" });
722
+ issues.push({ path: `${at}/${key}`, expected: "a value (required)", received: "missing" });
727
723
  }
728
724
  return;
729
725
  }
730
726
  if (schema.i || types.includes("array")) {
731
727
  if (!Array.isArray(value)) {
732
- issues.push({ path: at, code: "type", expected: "array" });
728
+ issues.push({ path: at, expected: "array" });
733
729
  return;
734
730
  }
735
731
  if (schema.i)
736
- value.forEach((item, index) => this.#check(schema.i, item, `${at}/${index}`, issues, repair));
732
+ value.forEach((item, index) => this.#check(schema.i, item, `${at}/${index}`, issues));
737
733
  return;
738
734
  }
739
735
  if (types.length > 0 && (isRecord(value) || Array.isArray(value))) {
740
- issues.push({ path: at, code: "type", expected: types.join(" or ") });
736
+ issues.push({ path: at, expected: types.join(" or ") });
741
737
  }
742
738
  }
743
739
  }