@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.
- package/CHANGELOG.md +94 -0
- package/README.md +5 -4
- package/bin/connecta.mjs +1 -39
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +2 -4
- package/dist/agent-identity.js +1 -1
- package/dist/agent-routes.js +231 -0
- package/dist/auth/clerk-sdk/generated.js +2 -1
- 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 -2516
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/native.js +27 -0
- package/dist/auth/oauth-grant.js +24 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/static-oauth.js +153 -131
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +9 -3
- package/dist/authorization-handoff.js +11 -5
- package/dist/branding.d.ts +1 -15
- package/dist/branding.js +2 -2
- package/dist/bytes.js +42 -0
- package/dist/call-admission-context.js +2 -5
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-cache.js +130 -43
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-policy.js +18 -0
- package/dist/catalog-service.js +285 -48
- package/dist/catalog.js +18 -2
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.js +6 -2
- package/dist/config.d.ts +57 -25
- package/dist/config.js +30 -77
- 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 +11 -7
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- 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 +671 -0
- package/dist/connectors/remote-mcp/errors.js +488 -0
- package/dist/connectors/remote-mcp/oauth.js +469 -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 +544 -0
- package/dist/connectors/remote-mcp.js +61 -2898
- package/dist/connectors/resource-uri.js +0 -4
- 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/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- 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 +4 -2
- package/dist/errors.d.ts +55 -17
- package/dist/errors.js +95 -41
- package/dist/execute.js +172 -125
- package/dist/executor-result.js +5 -2
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-functions.js +83 -0
- package/dist/guest-runtime.js +63 -20
- package/dist/guest-types.d.ts +24 -10
- package/dist/http/argument-problems.js +35 -0
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +7 -6
- 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/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +4 -3
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +33 -5
- package/dist/http/safety.js +9 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +45 -30
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/invocation-auth.js +2 -1
- package/dist/invocation.js +21 -6
- package/dist/membership.d.ts +13 -5
- package/dist/membership.js +34 -2
- package/dist/meta-tools.js +50 -35
- package/dist/native-recovery.js +4 -16
- package/dist/native-tools.js +6 -2
- package/dist/oauth-handoff.js +2 -2
- package/dist/oauth-sealing.js +10 -0
- package/dist/operation-vocabulary.js +136 -14
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -49
- package/dist/operator-ui/contract.d.ts +12 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +22 -1
- package/dist/program-writes.js +3 -8
- package/dist/provider-call.js +0 -7
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/kit.js +1 -6
- package/dist/providers/_shared/google/service-account.js +1 -3
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/guide.js +3 -0
- package/dist/providers/_shared/json.js +12 -0
- package/dist/providers/_shared/rest/compact-index.js +1 -1
- package/dist/providers/_shared/rest/kit.js +1 -3
- package/dist/providers/_shared/rest/operation-index.js +49 -53
- package/dist/providers/_shared/rest/value-safety.js +106 -28
- package/dist/providers/breeze/api.js +9 -8
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/breeze/tools.js +7 -7
- package/dist/providers/ccb/api.js +3 -7
- package/dist/providers/ccb/index.js +7 -1
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +10 -3
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- 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 +4 -14
- 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 +23 -41
- package/dist/providers/forms/api.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/forms/tools.js +11 -16
- 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 +12 -26
- package/dist/providers/granola/index.js +2 -0
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/api.js +8 -4
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +5 -2
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +1 -9
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/planning-center/tools.js +2 -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 +14 -23
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/slides/tools.js +19 -26
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/api.js +5 -8
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +2 -7
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/tools.js +6 -4
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +11 -5
- package/dist/registry.js +46 -20
- package/dist/request-principal.js +1 -1
- package/dist/request-state.js +2 -2
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/activity-disclosure.js +1 -1
- package/dist/routes/connect.js +2 -2
- package/dist/routes/credentials.js +130 -61
- package/dist/routes/dispatch.js +5 -1
- package/dist/routes/mcp.js +34 -17
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +6 -5
- package/dist/routes/shared.d.ts +13 -8
- package/dist/routes/shared.js +15 -9
- package/dist/routes/ui-config.js +3 -3
- package/dist/routes/ui-data.js +126 -20
- package/dist/routes/ui.js +17 -1
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +8 -3
- package/dist/runtime/native-storage.js +9 -0
- package/dist/runtime/request-reads.js +182 -0
- package/dist/runtime/request-scope.js +16 -0
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/runtime/storage.js +12 -0
- package/dist/sent-secrets.d.ts +1 -2
- package/dist/sent-secrets.js +3 -8
- package/dist/server.js +4 -4
- package/dist/skills.js +44 -54
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +69 -128
- package/dist/storage/memory.js +8 -2
- package/dist/storage/sql-native.js +27 -101
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +13 -14
- package/dist/storage-digest.js +32 -0
- package/dist/timeout.js +8 -0
- package/dist/tool-error.js +4 -0
- package/dist/types.d.ts +33 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +18 -37
- 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/worker.js +1 -1
- package/dist/write-outcome.js +0 -3
- package/documentation/README.md +19 -55
- package/documentation/architecture.md +436 -515
- package/documentation/auth.md +229 -88
- package/documentation/code-mode.md +201 -94
- package/documentation/deploying.md +25 -43
- package/documentation/integrating.md +18 -19
- package/documentation/meta-tools.md +225 -51
- package/documentation/migration-0.33.md +10 -6
- package/documentation/migration-0.34.md +5 -5
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +20 -120
- package/documentation/operator-ui.md +108 -16
- package/documentation/pool-membership-upgrade.md +44 -0
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/documentation/providers.md +105 -0
- package/examples/worker/AGENTS.md +14 -16
- package/examples/worker/README.md +24 -118
- package/examples/worker/src/connecta.config.ts +1 -0
- package/guides/connecting-agents.md +9 -31
- package/guides/how-it-works.md +6 -4
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/AGENTS.md +13 -12
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +1 -0
- package/dist/auth/oauth-v2-migration.js +0 -135
- package/dist/provider-call-facts.d.ts +0 -115
- package/dist/provider-call.d.ts +0 -112
- package/dist/storage/d1-copy.js +0 -255
- package/dist/storage/sql-schema.d.ts +0 -6
- package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
- 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
|
package/dist/program-writes.js
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
-
}
|
package/dist/provider-call.js
CHANGED
|
@@ -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";
|
package/dist/provider.d.ts
CHANGED
|
@@ -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
|
|
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,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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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;
|
|
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
|
|
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
|
|
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) =>
|
|
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
|
|
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:
|
|
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
|
|
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}`,
|
|
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",
|
|
511
|
+
issues.push({ path: "/body", expected: "no body for this operation" });
|
|
512
512
|
}
|
|
513
513
|
else {
|
|
514
|
-
this.#check(bodyRow[1], body, "/body", issues
|
|
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",
|
|
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) =>
|
|
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),
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
680
|
-
this.#check(branch, value, at, attempt
|
|
681
|
-
if (attempt.
|
|
678
|
+
const attempt = [];
|
|
679
|
+
this.#check(branch, value, at, attempt);
|
|
680
|
+
if (attempt.length === 0)
|
|
682
681
|
return;
|
|
683
|
-
if (!fewest || attempt.
|
|
682
|
+
if (!fewest || attempt.length < fewest.length)
|
|
684
683
|
fewest = attempt;
|
|
685
684
|
}
|
|
686
|
-
if (fewest)
|
|
687
|
-
issues.push(...fewest
|
|
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,
|
|
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,
|
|
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
|
|
709
|
+
this.#check(child, item, `${at}/${key}`, issues);
|
|
713
710
|
else if (schema.m)
|
|
714
|
-
this.#check(schema.m, item, `${at}
|
|
711
|
+
this.#check(schema.m, item, `${at}/<key>`, issues);
|
|
715
712
|
else if (schema.p) {
|
|
716
713
|
issues.push({
|
|
717
|
-
path: `${at}
|
|
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}`,
|
|
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,
|
|
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
|
|
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,
|
|
736
|
+
issues.push({ path: at, expected: types.join(" or ") });
|
|
741
737
|
}
|
|
742
738
|
}
|
|
743
739
|
}
|