@zackbart/connecta 0.34.1 → 0.35.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 +363 -0
- package/README.md +62 -106
- package/SECURITY.md +3 -2
- package/bin/connecta.mjs +41 -16
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +2 -1
- package/dist/access-tokens.js +66 -30
- package/dist/activity.d.ts +1 -1
- package/dist/activity.js +2 -0
- package/dist/agent-config.d.ts +23 -0
- package/dist/agent-config.js +59 -0
- package/dist/agent-description.js +16 -0
- package/dist/agent-identity.js +4 -4
- package/dist/agent-observation.js +39 -0
- package/dist/auth/clerk-sdk/client.d.ts +2 -0
- package/dist/auth/clerk-sdk/generated.js +554 -563
- package/dist/auth/clerk-transport.js +70 -3
- package/dist/auth/clerk.d.ts +13 -0
- package/dist/auth/clerk.js +653 -246
- package/dist/auth/cloudflare-access.js +46 -42
- package/dist/auth/downstream-client-metadata.js +2 -2
- package/dist/auth/downstream-oauth.js +2051 -1160
- package/dist/auth/downstream-revocation.js +88 -88
- package/dist/auth/native-call.js +20 -0
- package/dist/auth/native.js +184 -0
- package/dist/auth/oauth-start-reset.js +11 -5
- package/dist/auth/oauth-token-response.js +121 -102
- package/dist/auth/oauth-v2-migration.js +97 -78
- package/dist/auth/renewable-grant.js +10 -0
- package/dist/auth/static-oauth.js +663 -271
- package/dist/auth/vercel-agent.js +266 -0
- package/dist/auth-admission.js +204 -0
- package/dist/auth-elicitation.js +350 -241
- package/dist/authorization-handoff.js +164 -90
- package/dist/call-admission-context.js +3 -10
- package/dist/call-admission.js +2 -0
- package/dist/call-classification.js +9 -0
- package/dist/call-recovery.js +32 -13
- package/dist/canonical-json.js +8 -0
- package/dist/catalog-cache.js +369 -213
- package/dist/catalog-service.js +659 -530
- package/dist/catalog.js +5 -1
- package/dist/concurrency.js +0 -25
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +3 -1
- package/dist/config-schema.js +13 -3
- package/dist/config.d.ts +40 -6
- package/dist/config.js +40 -26
- package/dist/connector-caller.d.ts +4 -0
- package/dist/connector-caller.js +17 -4
- package/dist/connectors/api-connector.d.ts +12 -7
- package/dist/connectors/api-connector.js +142 -56
- package/dist/connectors/guarded-fetch.d.ts +51 -17
- package/dist/connectors/guarded-fetch.js +215 -121
- package/dist/connectors/negotiation-cache.js +20 -10
- package/dist/connectors/remote-mcp.d.ts +10 -30
- package/dist/connectors/remote-mcp.js +955 -563
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +3 -1
- package/dist/describe-config.js +2 -0
- package/dist/downstream-input.js +347 -260
- package/dist/errors.d.ts +87 -6
- package/dist/errors.js +114 -4
- package/dist/execute.js +87 -50
- package/dist/executor-admission.js +3 -0
- package/dist/executors/quickjs.js +163 -112
- package/dist/guarded-transport.js +50 -0
- package/dist/guest-runtime.js +1 -1
- package/dist/http/client.js +439 -0
- package/dist/http/errors.js +558 -0
- package/dist/http/fields.js +49 -0
- package/dist/http/hatch-paging.js +318 -0
- package/dist/http/hatch.js +1279 -0
- package/dist/http/json-schema.js +267 -0
- package/dist/http/node-transport.js +241 -0
- package/dist/http/oauth.js +162 -0
- package/dist/http/paging.js +228 -0
- package/dist/http/provider.js +221 -0
- package/dist/http/safety.js +82 -0
- package/dist/http/services.js +79 -0
- package/dist/http/tools.js +383 -0
- package/dist/http/vendor-index.js +138 -0
- package/dist/http/vendor.js +448 -0
- package/dist/http/write-request.js +66 -0
- package/dist/http-policy.d.ts +11 -0
- package/dist/http-policy.js +45 -0
- package/dist/inbound-credential.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/invocation-auth.js +30 -19
- package/dist/invocation.js +218 -85
- package/dist/mcp-result.js +0 -4
- package/dist/membership.d.ts +4 -0
- package/dist/meta-output.js +1 -1
- package/dist/meta-tools.js +531 -406
- package/dist/native-recovery.js +52 -0
- package/dist/native-tools.js +382 -0
- package/dist/node.d.ts +4 -3
- package/dist/node.js +45 -8
- package/dist/oauth-handoff.js +177 -104
- package/dist/oauth-partition.js +6 -20
- package/dist/oauth-sealing.js +6 -7
- package/dist/operation-vocabulary.js +101 -0
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +17 -4
- package/dist/operator-ui/agents.d.ts +5 -4
- package/dist/operator-ui/agents.js +17 -7
- package/dist/operator-ui/config-summary.js +5 -4
- package/dist/operator-ui/connection-map.js +7 -5
- package/dist/operator-ui/contract.d.ts +7 -3
- package/dist/operator-ui/endpoints.js +4 -13
- package/dist/operator-ui/fix-prompts.d.ts +1 -1
- package/dist/operator-ui/fix-prompts.js +0 -8
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/operator-ui/overview.js +5 -2
- package/dist/operator-ui/setup-commands.js +0 -4
- package/dist/operator-ui/view.d.ts +2 -25
- package/dist/operator-ui/view.js +2 -76
- package/dist/program-writes.js +47 -26
- package/dist/provider-call-facts.d.ts +115 -0
- package/dist/provider-call-facts.js +182 -0
- package/dist/provider-call.d.ts +112 -0
- package/dist/provider-call.js +271 -0
- package/dist/provider.d.ts +65 -31
- package/dist/provider.js +116 -39
- package/dist/providers/_shared/google/hatch.js +91 -0
- package/dist/providers/_shared/google/kit.js +537 -0
- package/dist/providers/_shared/google/result-size.js +15 -2
- package/dist/providers/_shared/google/service-account.d.ts +2 -1
- package/dist/providers/_shared/google/service-account.js +8 -4
- package/dist/providers/_shared/google/workspace.d.ts +6 -210
- package/dist/providers/_shared/google/workspace.js +6 -425
- package/dist/providers/_shared/rest/compact-index.js +86 -0
- package/dist/providers/_shared/rest/dispatch.js +3 -23
- package/dist/providers/_shared/rest/kit.js +546 -0
- package/dist/providers/_shared/rest/operation-index.js +230 -14
- package/dist/providers/_shared/rest/response-media.js +11 -0
- package/dist/providers/_shared/rest/tools.js +101 -78
- package/dist/providers/_shared/rest/value-safety.js +353 -195
- package/dist/providers/_shared/sensitive-fields.js +27 -0
- package/dist/providers/breeze/api.js +584 -0
- package/dist/providers/breeze/index.d.ts +1 -3
- package/dist/providers/breeze/index.js +67 -1420
- package/dist/providers/breeze/openapi.generated.js +60 -0
- package/dist/providers/breeze/skill.generated.js +2 -6
- package/dist/providers/breeze/tools.js +696 -0
- package/dist/providers/ccb/api.js +496 -0
- package/dist/providers/ccb/index.d.ts +15 -37
- package/dist/providers/ccb/index.js +77 -1702
- package/dist/providers/ccb/lib/constants.d.ts +34 -0
- package/dist/providers/ccb/lib/constants.js +74 -0
- package/dist/providers/ccb/lib/guide.js +9 -0
- package/dist/providers/ccb/lib/verdicts.js +387 -0
- package/dist/providers/ccb/openapi.generated.js +397 -0
- package/dist/providers/ccb/safety.js +295 -0
- package/dist/providers/ccb/skill.generated.js +2 -6
- package/dist/providers/ccb/tools.js +853 -0
- package/dist/providers/cloudflare/api.js +1066 -0
- package/dist/providers/cloudflare/index.d.ts +5 -22
- package/dist/providers/cloudflare/index.js +79 -197
- package/dist/providers/cloudflare/lib/config.d.ts +16 -0
- package/dist/providers/cloudflare/lib/config.js +2 -0
- package/dist/providers/cloudflare/lib/discovery.js +29 -0
- package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
- package/dist/providers/cloudflare/lib/hatch.js +103 -0
- package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
- package/dist/providers/cloudflare/lib/response-media.js +75 -0
- package/dist/providers/cloudflare/openapi.generated.js +4 -3
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
- package/dist/providers/cloudflare/skill.generated.js +2 -10
- package/dist/providers/cloudflare/tools.js +360 -0
- package/dist/providers/docs/api.js +51 -0
- package/dist/providers/docs/index.d.ts +1 -12
- package/dist/providers/docs/index.js +53 -1165
- package/dist/providers/docs/lib/constants.d.ts +18 -0
- package/dist/providers/docs/lib/constants.js +18 -0
- package/dist/providers/docs/lib/discovery.js +26 -0
- package/dist/providers/docs/lib/requests.js +48 -0
- package/dist/providers/docs/lib/schemas.js +168 -0
- package/dist/providers/docs/openapi.generated.js +16 -0
- package/dist/providers/docs/safety.js +54 -0
- package/dist/providers/docs/skill.generated.js +2 -6
- package/dist/providers/docs/tools.js +802 -0
- package/dist/providers/drive/api.js +177 -0
- package/dist/providers/drive/index.d.ts +1 -12
- package/dist/providers/drive/index.js +38 -1448
- package/dist/providers/drive/lib/constants.d.ts +6 -0
- package/dist/providers/drive/lib/constants.js +6 -0
- package/dist/providers/drive/lib/contracts.js +532 -0
- package/dist/providers/drive/lib/hatch.js +197 -0
- package/dist/providers/drive/openapi.generated.js +77 -0
- package/dist/providers/drive/safety.js +90 -0
- package/dist/providers/drive/skill.generated.js +2 -6
- package/dist/providers/drive/tools.js +1290 -0
- package/dist/providers/forms/api.js +71 -0
- package/dist/providers/forms/index.d.ts +1 -12
- package/dist/providers/forms/index.js +49 -1145
- package/dist/providers/forms/lib/constants.d.ts +14 -0
- package/dist/providers/forms/lib/constants.js +19 -0
- package/dist/providers/forms/lib/hatch.js +72 -0
- package/dist/providers/forms/openapi.generated.js +23 -0
- package/dist/providers/forms/safety.js +34 -0
- package/dist/providers/forms/skill.generated.js +2 -8
- package/dist/providers/forms/tools.js +1024 -0
- package/dist/providers/github/api.js +150 -0
- package/dist/providers/github/index.d.ts +4 -5
- package/dist/providers/github/index.js +30 -87
- package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
- package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
- package/dist/providers/github/lib/discovery.js +132 -0
- package/dist/providers/github/lib/operations.js +58 -0
- package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
- package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
- package/dist/providers/github/openapi.generated.js +1216 -0
- package/dist/providers/github/safety.js +216 -0
- package/dist/providers/github/skill.generated.js +2 -5
- package/dist/providers/github/tools.js +506 -0
- package/dist/providers/gmail/api.js +114 -0
- package/dist/providers/gmail/index.d.ts +1 -12
- package/dist/providers/gmail/index.js +73 -1621
- package/dist/providers/gmail/lib/constants.d.ts +13 -0
- package/dist/providers/gmail/lib/constants.js +16 -0
- package/dist/providers/gmail/lib/index.js +173 -0
- package/dist/providers/gmail/lib/schemas.js +176 -0
- package/dist/providers/gmail/lib/threading.js +120 -0
- package/dist/providers/gmail/openapi.generated.js +92 -0
- package/dist/providers/gmail/safety.js +21 -0
- package/dist/providers/gmail/skill.generated.js +2 -6
- package/dist/providers/gmail/tools.js +1435 -0
- package/dist/providers/overflow/api.js +1008 -0
- package/dist/providers/overflow/index.d.ts +1 -17
- package/dist/providers/overflow/index.js +60 -1488
- package/dist/providers/overflow/skill.generated.js +2 -5
- package/dist/providers/overflow/tools.js +873 -0
- package/dist/providers/planning-center/api.js +467 -0
- package/dist/providers/planning-center/index.d.ts +4 -35
- package/dist/providers/planning-center/index.js +69 -2445
- package/dist/providers/planning-center/lib/constants.d.ts +58 -0
- package/dist/providers/planning-center/lib/constants.js +83 -0
- package/dist/providers/planning-center/lib/discovery.js +40 -0
- package/dist/providers/planning-center/lib/guide.js +39 -0
- package/dist/providers/planning-center/lib/json-api.js +83 -0
- package/dist/providers/planning-center/lib/verdicts.js +1758 -0
- package/dist/providers/planning-center/openapi.generated.js +1764 -0
- package/dist/providers/planning-center/safety.js +188 -0
- package/dist/providers/planning-center/skill.generated.js +2 -27
- package/dist/providers/planning-center/tools.js +1010 -0
- package/dist/providers/sheets/api.js +133 -0
- package/dist/providers/sheets/index.d.ts +4 -16
- package/dist/providers/sheets/index.js +61 -1172
- package/dist/providers/sheets/lib/constants.d.ts +14 -0
- package/dist/providers/sheets/lib/constants.js +14 -0
- package/dist/providers/sheets/lib/hatch.js +163 -0
- package/dist/providers/sheets/lib/requests.js +105 -0
- package/dist/providers/sheets/lib/schemas.js +138 -0
- package/dist/providers/sheets/openapi.generated.js +30 -0
- package/dist/providers/sheets/safety.js +27 -0
- package/dist/providers/sheets/skill.generated.js +2 -7
- package/dist/providers/sheets/tools.js +734 -0
- package/dist/providers/slides/api.js +147 -0
- package/dist/providers/slides/index.d.ts +2 -10
- package/dist/providers/slides/index.js +48 -2555
- package/dist/providers/slides/lib/constants.d.ts +11 -0
- package/dist/providers/slides/lib/constants.js +11 -0
- package/dist/providers/slides/lib/requests.js +60 -0
- package/dist/providers/slides/lib/schemas.js +565 -0
- package/dist/providers/slides/openapi.generated.js +18 -0
- package/dist/providers/slides/skill.generated.js +2 -6
- package/dist/providers/slides/tools.js +1968 -0
- package/dist/providers/stripe/api.js +401 -0
- package/dist/providers/stripe/index.d.ts +5 -27
- package/dist/providers/stripe/index.js +74 -210
- package/dist/providers/stripe/lib/config.d.ts +4 -0
- package/dist/providers/stripe/lib/config.js +2 -0
- package/dist/providers/stripe/openapi.generated.js +4 -3
- package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
- package/dist/providers/stripe/skill.generated.js +2 -11
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +17 -0
- package/dist/providers/tithely/index.js +86 -965
- package/dist/providers/tithely/skill.generated.js +2 -5
- package/dist/providers/tithely/tools.js +426 -0
- package/dist/providers/vercel/api.js +551 -0
- package/dist/providers/vercel/index.d.ts +9 -25
- package/dist/providers/vercel/index.js +66 -260
- package/dist/providers/vercel/lib/config.d.ts +2 -0
- package/dist/providers/vercel/lib/config.js +2 -0
- package/dist/providers/vercel/lib/index.js +117 -0
- package/dist/providers/vercel/lib/logs.js +100 -0
- package/dist/providers/vercel/lib/pin.js +270 -0
- package/dist/providers/vercel/openapi.generated.js +10 -5
- package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
- package/dist/providers/vercel/skill.generated.js +2 -7
- package/dist/providers/vercel/tools.js +642 -0
- package/dist/redaction-identity.js +47 -0
- package/dist/redaction-marker.js +125 -0
- package/dist/registry.d.ts +21 -15
- package/dist/registry.js +409 -376
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/actor-labels.js +1 -1
- package/dist/routes/connect.js +1 -1
- package/dist/routes/dispatch.js +103 -0
- package/dist/routes/mcp.js +170 -79
- package/dist/routes/oauth.js +3 -1
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +5 -4
- package/dist/routes/shared.js +244 -177
- package/dist/routes/skills.js +10 -8
- package/dist/routes/ui-config.js +27 -3
- package/dist/routes/ui-data.js +21 -20
- package/dist/routes/ui.js +1 -1
- package/dist/runtime/call-admission.js +0 -26
- package/dist/runtime/call-entry.js +30 -0
- package/dist/runtime/call.js +556 -0
- package/dist/runtime/connector-scope.js +7 -0
- package/dist/runtime/credential-probe.js +34 -0
- package/dist/runtime/executor.js +40 -0
- package/dist/runtime/native-storage.js +63 -0
- package/dist/runtime/registry.js +27 -0
- package/dist/runtime/request-scope.js +72 -0
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/shared-read.js +34 -5
- package/dist/runtime/storage.js +34 -21
- package/dist/sent-secrets.d.ts +194 -37
- package/dist/sent-secrets.js +1743 -340
- package/dist/server.js +5 -56
- package/dist/skills.js +322 -222
- package/dist/sqlite.js +15 -14
- package/dist/storage/d1-copy.js +255 -0
- package/dist/storage/d1-driver.js +15 -0
- package/dist/storage/keys.js +28 -0
- package/dist/storage/sql-driver.js +88 -0
- package/dist/storage/sql-native.js +544 -0
- package/dist/storage/sql-schema.d.ts +6 -0
- package/dist/storage/sql-schema.js +15 -0
- package/dist/storage/sql.d.ts +14 -20
- package/dist/storage/sql.js +29 -535
- package/dist/timeout.js +0 -26
- package/dist/tool-safety.js +20 -10
- package/dist/types.d.ts +7 -109
- package/dist/upward.js +3 -0
- package/dist/usage-guide.js +3 -1
- package/dist/validate.d.ts +4 -2
- package/dist/validate.js +86 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +312 -292
- package/dist/workspace-subject.js +5 -1
- package/dist/write-outcome.js +454 -34
- package/documentation/README.md +27 -7
- package/documentation/architecture.md +2547 -226
- package/documentation/auth.md +821 -98
- package/documentation/code-mode.md +49 -16
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +129 -144
- package/documentation/meta-tools.md +409 -32
- package/documentation/operating.md +27 -3
- package/documentation/operator-tests.md +3 -0
- package/documentation/operator-ui.md +68 -45
- package/documentation/provider-auth-migration.md +16 -63
- package/documentation/provider-migration-0.29.md +10 -5
- package/documentation/provider-migration-0.35.md +975 -0
- package/documentation/providers.md +965 -0
- package/examples/worker/AGENTS.md +23 -9
- package/examples/worker/README.md +254 -135
- package/examples/worker/alchemy.run.ts +116 -0
- package/examples/worker/gitignore +10 -0
- package/examples/worker/src/connecta.config.ts +23 -21
- package/examples/worker/wrangler.jsonc +14 -5
- package/guides/connecting-agents.md +157 -0
- package/guides/how-it-works.md +156 -0
- package/guides/safety-and-trust.md +153 -0
- package/package.json +27 -46
- package/templates/node/.env.example +3 -0
- package/templates/node/README.md +22 -2
- package/templates/node/docker-compose.yml +1 -0
- package/templates/node/package.json +3 -2
- package/templates/node/src/connecta.config.ts +8 -0
- package/dist/catalog-drift.d.ts +0 -138
- package/dist/catalog-drift.js +0 -578
- package/dist/providers/_shared/request-token.js +0 -83
- package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
- package/dist/providers/_shared/rest/tools.d.ts +0 -141
- package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
- package/dist/providers/basecamp/index.d.ts +0 -45
- package/dist/providers/basecamp/index.js +0 -997
- package/dist/providers/basecamp/skill.generated.js +0 -9
- package/dist/providers/cloudflare/rest.d.ts +0 -37
- package/dist/providers/cloudflare/rest.js +0 -1097
- package/dist/providers/github/hosted.js +0 -218
- package/dist/providers/github/rest.js +0 -321
- package/dist/providers/infisical/index.d.ts +0 -7
- package/dist/providers/infisical/index.js +0 -713
- package/dist/providers/infisical/skill.generated.js +0 -8
- package/dist/providers/intercom/index.d.ts +0 -24
- package/dist/providers/intercom/index.js +0 -121
- package/dist/providers/intercom/skill.generated.js +0 -8
- package/dist/providers/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -537
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -59
- package/dist/providers/mixpanel/index.js +0 -472
- package/dist/providers/mixpanel/operations.js +0 -20
- package/dist/providers/mixpanel/rest.js +0 -603
- package/dist/providers/mixpanel/skill.generated.js +0 -10
- package/dist/providers/mixpanel/value-safety.js +0 -16
- package/dist/providers/notion/index.d.ts +0 -67
- package/dist/providers/notion/index.js +0 -368
- package/dist/providers/notion/openapi.generated.js +0 -75
- package/dist/providers/notion/rest.d.ts +0 -24
- package/dist/providers/notion/rest.js +0 -1226
- package/dist/providers/notion/skill.generated.js +0 -11
- package/dist/providers/notion/value-safety.js +0 -56
- package/dist/providers/revenuecat/index.d.ts +0 -51
- package/dist/providers/revenuecat/index.js +0 -697
- package/dist/providers/revenuecat/skill.generated.js +0 -11
- package/dist/providers/stripe/rest.d.ts +0 -14
- package/dist/providers/stripe/rest.js +0 -402
- package/dist/providers/vercel/rest.d.ts +0 -20
- package/dist/providers/vercel/rest.js +0 -1020
- package/dist/providers/wispr-flow/index.d.ts +0 -14
- package/dist/providers/wispr-flow/index.js +0 -117
- package/dist/providers/wispr-flow/skill.generated.js +0 -8
- /package/dist/providers/github/{key.js → lib/key.js} +0 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
// Vercel's pinned contract, authentication, failures and value-safety boundary.
|
|
2
|
+
import { Effect, Schema } from "effect";
|
|
3
|
+
import { ConnectorCallError } from "../../errors.js";
|
|
4
|
+
import { Refusal, StatusFailure, UnreadableBody } from "../../http/errors.js";
|
|
5
|
+
import { restSafety } from "../../http/safety.js";
|
|
6
|
+
import { Credentials, RequestSecrets } from "../../http/services.js";
|
|
7
|
+
import { get, post, patch } from "../../http/vendor.js";
|
|
8
|
+
import { checked, indexedVendor } from "../_shared/rest/kit.js";
|
|
9
|
+
import { OperationIndex } from "../_shared/rest/operation-index.js";
|
|
10
|
+
import { valueSafety, withholdEmbeddedUrlsIn, refuseWithheldLinks, refuseWithheldBase64, } from "../_shared/rest/value-safety.js";
|
|
11
|
+
import { openapi } from "./openapi.generated.js";
|
|
12
|
+
import { discoveryIndex } from "./lib/index.js";
|
|
13
|
+
import { VERCEL_VALUE_SAFETY } from "./safety.js";
|
|
14
|
+
import { vercelPage as timestampPage } from "../../http/hatch-paging.js";
|
|
15
|
+
import { completeRows } from "./lib/logs.js";
|
|
16
|
+
import { pinAdmission, pinOptions, pinQuery, pinRoute, readPinnedTeam } from "./lib/pin.js";
|
|
17
|
+
export const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
18
|
+
export const READ_CAP_MS = 60_000;
|
|
19
|
+
/** POSTs that only read, admitted by `vercel_api_read`. */
|
|
20
|
+
export const READ_POSTS = [
|
|
21
|
+
["POST", "/v2/observability/query", "Runs an Observability metrics query; Vercel persists nothing."],
|
|
22
|
+
["POST", "/v1/registrar/domains/availability", "Checks availability for a list of domains; buys nothing."],
|
|
23
|
+
["POST", "/v1/registrar/domains/price", "Quotes registrar prices for a list of domains; buys nothing."],
|
|
24
|
+
["POST", "/v1/registrar/domains/search", "Searches domain availability and pricing; buys nothing."],
|
|
25
|
+
["POST", "/v8/artifacts", "Reads Remote Cache metadata for a list of artifact hashes; uploads nothing."],
|
|
26
|
+
];
|
|
27
|
+
export function isRecord(value) {
|
|
28
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29
|
+
}
|
|
30
|
+
export function record(value) {
|
|
31
|
+
return isRecord(value) ? value : {};
|
|
32
|
+
}
|
|
33
|
+
export function text(value) {
|
|
34
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
35
|
+
}
|
|
36
|
+
export function invalid(message) {
|
|
37
|
+
throw new ConnectorCallError("invalid_args", message);
|
|
38
|
+
}
|
|
39
|
+
export function compact(value) {
|
|
40
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
41
|
+
}
|
|
42
|
+
export function truthy(value) {
|
|
43
|
+
return value === true || value === 1 || (typeof value === "string" && /^(?:1|true)$/i.test(value.trim()));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Error codes a secret-family failure may name: Vercel's documented generic
|
|
47
|
+
* codes and its environment-variable conflicts. Anything else is withheld,
|
|
48
|
+
* since a code is vendor text too.
|
|
49
|
+
*/
|
|
50
|
+
const REVIEWED_CODES = new Set([
|
|
51
|
+
"bad_request",
|
|
52
|
+
"forbidden",
|
|
53
|
+
"not_found",
|
|
54
|
+
"rate_limited",
|
|
55
|
+
"conflict",
|
|
56
|
+
"unauthorized",
|
|
57
|
+
"invalid_token",
|
|
58
|
+
"internal_server_error",
|
|
59
|
+
"ENV_ALREADY_EXISTS",
|
|
60
|
+
"ENV_CONFLICT",
|
|
61
|
+
"ENV_KEY_RESERVED",
|
|
62
|
+
"ENV_NOT_FOUND",
|
|
63
|
+
"ENV_SIZE_LIMIT_EXCEEDED",
|
|
64
|
+
]);
|
|
65
|
+
/** A reviewed code, or nothing. */
|
|
66
|
+
export function reviewedCode(value) {
|
|
67
|
+
return typeof value === "string" && REVIEWED_CODES.has(value) ? value : undefined;
|
|
68
|
+
}
|
|
69
|
+
export const vercelErrors = {
|
|
70
|
+
meanings: { unknownId: "not_found", provesAbsence: true, conflicts: [] },
|
|
71
|
+
factVocabulary: { code: [...REVIEWED_CODES], envRejected: [true, false] },
|
|
72
|
+
numericFacts: {
|
|
73
|
+
retryAfterMs: {
|
|
74
|
+
header: "retry-after",
|
|
75
|
+
format: "retry-after",
|
|
76
|
+
review: "HTTP Retry-After delay for failure recovery.",
|
|
77
|
+
},
|
|
78
|
+
resetAfterMs: {
|
|
79
|
+
header: "x-ratelimit-reset",
|
|
80
|
+
format: "epoch-seconds",
|
|
81
|
+
review: "Vercel endpoint rate-limit reset time in epoch seconds.",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
provider: "Vercel",
|
|
85
|
+
facts: (body) => ({
|
|
86
|
+
// Shape-only fact: no variable names, values or vendor prose survive here.
|
|
87
|
+
envRejected: Array.isArray(record(body)["failed"]) &&
|
|
88
|
+
record(body)["failed"].length > 0 &&
|
|
89
|
+
!(Array.isArray(record(body)["created"])
|
|
90
|
+
? record(body)["created"].length
|
|
91
|
+
: record(body)["created"]),
|
|
92
|
+
code: reviewedCode(record(record(body)["error"])["code"]) ??
|
|
93
|
+
reviewedCode(record(record((Array.isArray(record(body)["failed"]) ? record(body)["failed"] : [])[0])["error"])["code"]),
|
|
94
|
+
}),
|
|
95
|
+
detail: ({ body, status, withheld, facts }) => {
|
|
96
|
+
const root = record(withholdEmbeddedUrlsIn(body));
|
|
97
|
+
const error = record(root["error"]);
|
|
98
|
+
const code = text(error["code"]);
|
|
99
|
+
const message = text(error["message"])?.trim() ?? text(root["message"])?.trim() ?? `Vercel returned HTTP ${status}.`;
|
|
100
|
+
const reviewed = withheld ? facts["code"] : reviewedCode(code);
|
|
101
|
+
const detail = (code ? `Vercel ${code}: ${message}` : message).slice(0, 1_000);
|
|
102
|
+
if (status >= 200 && status <= 299 && facts["envRejected"] === true)
|
|
103
|
+
return `Vercel rejected the environment-variable write${reviewed ? ` (${reviewed})` : ""}; its message is withheld because this operation handles secret values.`;
|
|
104
|
+
return withheld
|
|
105
|
+
? `Vercel answered HTTP ${status}${reviewed ? ` (${reviewed})` : ""}; its message is withheld because this operation handles secret values.`
|
|
106
|
+
: /[.!?]$/.test(detail)
|
|
107
|
+
? detail
|
|
108
|
+
: `${detail}.`;
|
|
109
|
+
},
|
|
110
|
+
classify: [
|
|
111
|
+
({ status, facts, operation }) => status >= 200 &&
|
|
112
|
+
status <= 299 &&
|
|
113
|
+
facts["envRejected"] === true &&
|
|
114
|
+
operation.method === "POST" &&
|
|
115
|
+
canonicalTemplate(operation.template) === "/v10/projects/{idOrName}/env"
|
|
116
|
+
? {
|
|
117
|
+
code: "invalid_args",
|
|
118
|
+
retryable: false,
|
|
119
|
+
words: (detail) => `${detail}${facts["code"] === "ENV_ALREADY_EXISTS" || facts["code"] === "ENV_CONFLICT"
|
|
120
|
+
? " Pass upsert: true to replace it, or update it by id with update_project_env_var."
|
|
121
|
+
: ""} Check the target before repeating the write.`,
|
|
122
|
+
}
|
|
123
|
+
: undefined,
|
|
124
|
+
],
|
|
125
|
+
statuses: {
|
|
126
|
+
429: ({ numbers, retryAfterMs }) => ({
|
|
127
|
+
code: "rate_limited",
|
|
128
|
+
retryAfterMs: retryAfterMs ?? numbers["resetAfterMs"],
|
|
129
|
+
words: (detail) => `${detail} Vercel meters endpoints separately.`,
|
|
130
|
+
advice: "; wait for the reported reset before retrying this operation",
|
|
131
|
+
}),
|
|
132
|
+
401: () => ({
|
|
133
|
+
code: "auth_required",
|
|
134
|
+
words: (detail) => `${detail} The configured access token is invalid, expired, outside this team, or lacks the required scope. An operator must replace it or widen its Vercel scope.`,
|
|
135
|
+
}),
|
|
136
|
+
403: () => ({
|
|
137
|
+
code: "provider_permission_denied",
|
|
138
|
+
words: (detail) => `${detail} Vercel refused access to this team or resource. Ask the team administrator to grant access and an operator to widen the configured token's Vercel scope.`,
|
|
139
|
+
}),
|
|
140
|
+
404: () => ({
|
|
141
|
+
code: "not_found",
|
|
142
|
+
words: (detail) => `${detail} Confirm the team, project, deployment, domain, or environment-variable id with a list read.`,
|
|
143
|
+
}),
|
|
144
|
+
},
|
|
145
|
+
unreadable: "Vercel returned malformed JSON.",
|
|
146
|
+
};
|
|
147
|
+
class VercelIndex extends OperationIndex {
|
|
148
|
+
search(query, options) {
|
|
149
|
+
// A custom environment is a container, not a variable. Only variable-specific
|
|
150
|
+
// summaries participate in a variable-phrase search; other query words keep
|
|
151
|
+
// the shared partial-match behavior.
|
|
152
|
+
const operations = super.search(query, options);
|
|
153
|
+
const variablePhrase = /\b(?:environment variables?|env vars?)\b/i;
|
|
154
|
+
return variablePhrase.test(query) ? operations.filter((op) => variablePhrase.test(op.summary)) : operations;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export const vercelIndex = new VercelIndex(discoveryIndex(openapi), { vendor: "vercel", title: "Vercel" });
|
|
158
|
+
export const vercelSafety = valueSafety(VERCEL_VALUE_SAFETY, () => vercelIndex);
|
|
159
|
+
const Query = Schema.Record(Schema.String, Schema.Union([Schema.String, Schema.Array(Schema.String)]));
|
|
160
|
+
const params = (...names) => Schema.Struct(Object.fromEntries(names.map((name) => [name, Schema.String])));
|
|
161
|
+
export const currentUser = get("currentUser", "/v2/user", { success: Schema.Unknown });
|
|
162
|
+
export const teams = get("teams", "/v2/teams", { query: Query, success: Schema.Unknown });
|
|
163
|
+
export const projectEnv = get("projectEnv", "/v10/projects/:idOrName/env", {
|
|
164
|
+
params: params("idOrName"),
|
|
165
|
+
query: Query,
|
|
166
|
+
success: Schema.Unknown,
|
|
167
|
+
});
|
|
168
|
+
export const upsertEnv = post("upsertEnv", "/v10/projects/:idOrName/env", {
|
|
169
|
+
params: params("idOrName"),
|
|
170
|
+
query: Query,
|
|
171
|
+
payload: Schema.Unknown,
|
|
172
|
+
success: Schema.Unknown,
|
|
173
|
+
});
|
|
174
|
+
export const updateEnv = patch("updateEnv", "/v9/projects/:idOrName/env/:id", {
|
|
175
|
+
params: params("idOrName", "id"),
|
|
176
|
+
query: Query,
|
|
177
|
+
payload: Schema.Unknown,
|
|
178
|
+
success: Schema.Unknown,
|
|
179
|
+
});
|
|
180
|
+
export const buildLogs = get("buildLogs", "/v3/deployments/:idOrUrl/events", {
|
|
181
|
+
params: params("idOrUrl"),
|
|
182
|
+
query: Query,
|
|
183
|
+
success: Schema.Unknown,
|
|
184
|
+
});
|
|
185
|
+
export const runtimeLogs = get("runtimeLogs", "/v1/projects/:projectId/deployments/:deploymentId/runtime-logs", {
|
|
186
|
+
params: params("projectId", "deploymentId"),
|
|
187
|
+
query: Query,
|
|
188
|
+
success: Schema.Unknown,
|
|
189
|
+
});
|
|
190
|
+
export const named = [currentUser, teams, projectEnv, upsertEnv, updateEnv, buildLogs, runtimeLogs];
|
|
191
|
+
const canonicalTemplate = (path) => path.replace(/:([^/]+)/g, "{$1}");
|
|
192
|
+
/** Exact operation/media pairs from the pin; named logs also accept bounded equivalents. */
|
|
193
|
+
const LOG_PATHS = [
|
|
194
|
+
"/v3/deployments/{idOrUrl}/events",
|
|
195
|
+
"/v1/projects/{projectId}/deployments/{deploymentId}/runtime-logs",
|
|
196
|
+
];
|
|
197
|
+
const FRAMED_MEDIA = {
|
|
198
|
+
"GET /v1/billing/charges": ["application/jsonl"],
|
|
199
|
+
"GET /v1/billing/contract-commitments": ["application/jsonl"],
|
|
200
|
+
"GET /v2/sandboxes/sessions/{sessionId}/cmd/{cmdId}/logs": ["application/x-ndjson"],
|
|
201
|
+
"POST /v2/sandboxes/sessions/{sessionId}/cmd": ["application/x-ndjson"],
|
|
202
|
+
"GET /v3/deployments/{idOrUrl}/events": ["application/stream+json"],
|
|
203
|
+
"GET /v1/projects/{projectId}/deployments/{deploymentId}/runtime-logs": ["application/stream+json"],
|
|
204
|
+
};
|
|
205
|
+
export const VercelApi = {
|
|
206
|
+
...indexedVendor("vercel", vercelIndex, named),
|
|
207
|
+
textBodies: {
|
|
208
|
+
review: "Exact pinned jsonl/ndjson billing and sandbox command operations, plus build/runtime stream+json logs. Bounded named log readers also accept JSON arrays/NDJSON equivalents. JSON-only writes never accept HTML/text (#1000).",
|
|
209
|
+
forResponse: (status, operation, type = "") => {
|
|
210
|
+
const path = canonicalTemplate(operation.template);
|
|
211
|
+
const media = type.split(";", 1)[0].trim().toLowerCase();
|
|
212
|
+
return status >= 200 &&
|
|
213
|
+
status <= 299 &&
|
|
214
|
+
(FRAMED_MEDIA[`${operation.method} ${path}`]?.includes(media) ||
|
|
215
|
+
(operation.method === "GET" &&
|
|
216
|
+
operation.flags?.["log"] &&
|
|
217
|
+
LOG_PATHS.includes(path) &&
|
|
218
|
+
/^(?:application\/(?:json|[^/]+\+json|x-ndjson|jsonl))$/.test(media)))
|
|
219
|
+
? "text"
|
|
220
|
+
: undefined;
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
binaryBodies: {
|
|
224
|
+
review: "Pinned sandbox fs/read alone declares application/octet-stream success. No other operation may acknowledge a JSON write as binary.",
|
|
225
|
+
forResponse: (status, operation, type) => status >= 200 &&
|
|
226
|
+
status <= 299 &&
|
|
227
|
+
canonicalTemplate(operation.template) === "/v2/sandboxes/sessions/{sessionId}/fs/read" &&
|
|
228
|
+
type.split(";", 1)[0].trim().toLowerCase() === "application/octet-stream",
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
export const result = (body, answer) => {
|
|
232
|
+
if (answer.operation.method === "HEAD")
|
|
233
|
+
return Effect.succeed(Object.fromEntries([...answer.headers].filter(([name]) => name !== "set-cookie")));
|
|
234
|
+
if (canonicalTemplate(answer.operation.template) === "/v10/projects/{idOrName}/env" &&
|
|
235
|
+
answer.operation.method === "POST") {
|
|
236
|
+
const payload = record(body);
|
|
237
|
+
if (Array.isArray(payload["failed"]) &&
|
|
238
|
+
payload["failed"].length &&
|
|
239
|
+
!(Array.isArray(payload["created"]) ? payload["created"].length : payload["created"]))
|
|
240
|
+
return Effect.fail(new StatusFailure({ status: answer.status, headers: answer.headers, body, operation: answer.operation }));
|
|
241
|
+
const created = Array.isArray(payload["created"]) ? payload["created"][0] : payload["created"];
|
|
242
|
+
const variable = record(created ?? payload);
|
|
243
|
+
if (!variable["id"] || !variable["key"] || !variable["type"])
|
|
244
|
+
return Effect.fail(new UnreadableBody());
|
|
245
|
+
}
|
|
246
|
+
if (answer.operation.flags?.["log"])
|
|
247
|
+
return Effect.succeed(typeof body === "string" ? completeRows(body) : []);
|
|
248
|
+
if (answer.bodyFormat === "text" && typeof body === "string")
|
|
249
|
+
return Effect.succeed(completeRows(body));
|
|
250
|
+
return Effect.succeed(body);
|
|
251
|
+
};
|
|
252
|
+
export const authenticate = Effect.gen(function* () {
|
|
253
|
+
const values = yield* (yield* Credentials).values.pipe(Effect.mapError((error) => new Refusal({ error })));
|
|
254
|
+
const token = typeof values?.value === "string" ? values.value.trim() : undefined;
|
|
255
|
+
if (!token)
|
|
256
|
+
return yield* Effect.fail(new Refusal({
|
|
257
|
+
error: new ConnectorCallError("auth_required", "No Vercel access token is configured for this connector. An operator must add one in this connection in the operator UI."),
|
|
258
|
+
}));
|
|
259
|
+
return { Authorization: `Bearer ${token}` };
|
|
260
|
+
});
|
|
261
|
+
export function safety() {
|
|
262
|
+
return { reviewed: restSafety(vercelSafety, vercelIndex), sensitiveFields: { names: [], choice: "drop" } };
|
|
263
|
+
}
|
|
264
|
+
export function defaultTeam(options) {
|
|
265
|
+
return typeof options.teamId === "string" ? options.teamId.trim() || undefined : undefined;
|
|
266
|
+
}
|
|
267
|
+
/** Query parameter names each operation declares, by index row. */
|
|
268
|
+
const queryNames = new Map();
|
|
269
|
+
function queryNamesOf(operations, op) {
|
|
270
|
+
let names = queryNames.get(op.row);
|
|
271
|
+
if (!names) {
|
|
272
|
+
names = new Set(operations
|
|
273
|
+
.contract(op)
|
|
274
|
+
.parameters.filter((parameter) => parameter.in === "query")
|
|
275
|
+
.map((parameter) => parameter.name));
|
|
276
|
+
queryNames.set(op.row, names);
|
|
277
|
+
}
|
|
278
|
+
return names;
|
|
279
|
+
}
|
|
280
|
+
export const refuse = (call) => {
|
|
281
|
+
if (call.method === "PATCH" &&
|
|
282
|
+
call.op.path === "/v3/domains/{domain}" &&
|
|
283
|
+
String(record(call.body)["op"]).trim().toLowerCase() === "move-out") {
|
|
284
|
+
return "Connecta refuses a domain move-out: Vercel answers it with a transfer token that lets the destination account claim the domain. Move domains in the Vercel dashboard.";
|
|
285
|
+
}
|
|
286
|
+
if (truthy(call.query["decrypt"])) {
|
|
287
|
+
return "Connecta never asks Vercel to decrypt environment values. Use list_project_env_vars for metadata.";
|
|
288
|
+
}
|
|
289
|
+
if (call.op.path === "/v1/projects/{projectId}/deployments/{deploymentId}/runtime-logs") {
|
|
290
|
+
return "Runtime logs are a live stream that never ends on its own. Read them with get_runtime_logs, which stops after waitMs or limit.";
|
|
291
|
+
}
|
|
292
|
+
if (truthy(call.query["follow"])) {
|
|
293
|
+
return "follow streams live events until the connection drops. Omit it (or pass 0); read build events with get_deployment_build_logs.";
|
|
294
|
+
}
|
|
295
|
+
return undefined;
|
|
296
|
+
};
|
|
297
|
+
export function scope(call, teamId, pin) {
|
|
298
|
+
if (pin)
|
|
299
|
+
return { ...call, query: pinQuery(vercelIndex, call, pin) };
|
|
300
|
+
const query = { ...call.query };
|
|
301
|
+
if (query["teamId"] === null) {
|
|
302
|
+
delete query["teamId"];
|
|
303
|
+
return { ...call, query };
|
|
304
|
+
}
|
|
305
|
+
if (teamId &&
|
|
306
|
+
query["teamId"] === undefined &&
|
|
307
|
+
query["slug"] === undefined &&
|
|
308
|
+
queryNamesOf(vercelIndex, call.op).has("teamId"))
|
|
309
|
+
query["teamId"] = teamId;
|
|
310
|
+
return { ...call, query };
|
|
311
|
+
}
|
|
312
|
+
export function restCall(method, path, input = {}, teamId, pin) {
|
|
313
|
+
const { op, params } = vercelIndex.resolve(method, path);
|
|
314
|
+
return scope({ method, path, op, params, query: { ...input.query }, body: input.body }, teamId, pin);
|
|
315
|
+
}
|
|
316
|
+
export function checkServer(server) {
|
|
317
|
+
return server === undefined
|
|
318
|
+
? undefined
|
|
319
|
+
: `This operation is served from ${server}, which this connector does not reach.`;
|
|
320
|
+
}
|
|
321
|
+
export function admit(call, connection, options) {
|
|
322
|
+
return Effect.tryPromise({
|
|
323
|
+
try: async () => {
|
|
324
|
+
if (call.method === "GET" ||
|
|
325
|
+
call.method === "HEAD" ||
|
|
326
|
+
READ_POSTS.some(([method, path]) => method === call.method && path === call.op.path))
|
|
327
|
+
return;
|
|
328
|
+
refuseWithheldLinks([call.body, call.query, call.path], "Vercel");
|
|
329
|
+
if (call.method === "POST" && call.op.path === "/v13/deployments") {
|
|
330
|
+
const files = record(call.body)["files"];
|
|
331
|
+
if (Array.isArray(files))
|
|
332
|
+
for (const file of files)
|
|
333
|
+
if (record(file)["encoding"] === "base64")
|
|
334
|
+
await refuseWithheldBase64(record(file)["data"], "Vercel");
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
catch: (error) => error,
|
|
338
|
+
}).pipe(Effect.tap(() => Effect.gen(function* () {
|
|
339
|
+
if (call.method === "POST" && call.op.path === "/v11/projects") {
|
|
340
|
+
(yield* RequestSecrets).arguments(record(call.body)["environmentVariables"], {
|
|
341
|
+
items: { properties: { value: { writeOnly: true } } },
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
if (call.method !== "GET" &&
|
|
345
|
+
call.method !== "HEAD" &&
|
|
346
|
+
(call.op.path === "/v1/env" || /^\/v(?:9|10)\/projects\/\{idOrName\}\/env(?:\/\{id\})?$/.test(call.op.path))) {
|
|
347
|
+
const value = { properties: { value: { writeOnly: true } } };
|
|
348
|
+
const body = call.op.path === "/v1/env"
|
|
349
|
+
? call.method === "POST"
|
|
350
|
+
? record(call.body)["evs"]
|
|
351
|
+
: call.method === "PATCH"
|
|
352
|
+
? Object.values(record(record(call.body)["updates"]))
|
|
353
|
+
: []
|
|
354
|
+
: call.body;
|
|
355
|
+
(yield* RequestSecrets).arguments(body, { ...value, items: value });
|
|
356
|
+
}
|
|
357
|
+
})), Effect.catch((error) => error instanceof ConnectorCallError ? Effect.fail(new Refusal({ error })) : Effect.die(error)), Effect.flatMap(() => pinAdmission(connection, vercelIndex, call, options.pin)));
|
|
358
|
+
}
|
|
359
|
+
export function queryOf(call) {
|
|
360
|
+
const out = {};
|
|
361
|
+
const scalar = (v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean";
|
|
362
|
+
for (const [key, value] of Object.entries(call.query)) {
|
|
363
|
+
if (value === undefined)
|
|
364
|
+
continue;
|
|
365
|
+
if (scalar(value))
|
|
366
|
+
out[key] = String(value);
|
|
367
|
+
else if (Array.isArray(value) && value.every(scalar))
|
|
368
|
+
out[key] = value.map(String);
|
|
369
|
+
else
|
|
370
|
+
invalid(`Vercel query parameter ${key} takes a string, number, boolean, or a list of them.`);
|
|
371
|
+
}
|
|
372
|
+
return out;
|
|
373
|
+
}
|
|
374
|
+
export function queryPairs(call) {
|
|
375
|
+
return Object.entries(queryOf(call)).flatMap(([key, value]) => (Array.isArray(value) ? value : [value]).map((value) => [key, value]));
|
|
376
|
+
}
|
|
377
|
+
export function sendVercel(connection, call, options, sendOptions) {
|
|
378
|
+
return checked(() => {
|
|
379
|
+
const server = checkServer(call.op.server);
|
|
380
|
+
if (server)
|
|
381
|
+
invalid(server);
|
|
382
|
+
return queryOf(call);
|
|
383
|
+
}).pipe(Effect.flatMap((query) => admit(call, connection, options).pipe(Effect.flatMap(() => connection.sendPath({
|
|
384
|
+
method: call.method,
|
|
385
|
+
path: call.path,
|
|
386
|
+
template: call.op.path,
|
|
387
|
+
query: Object.entries(query).flatMap(([key, value]) => (Array.isArray(value) ? value : [value]).map((value) => [key, value])),
|
|
388
|
+
...(call.body === undefined ? {} : { json: JSON.stringify(call.body) }),
|
|
389
|
+
}, sendOptions)))));
|
|
390
|
+
}
|
|
391
|
+
export const probe = {
|
|
392
|
+
run: (connection, options) => {
|
|
393
|
+
const pin = pinOptions(options).pin;
|
|
394
|
+
return pin
|
|
395
|
+
? readPinnedTeam(connection, pin)
|
|
396
|
+
: connection.send(currentUser, {}).pipe(Effect.map((answer) => answer.data));
|
|
397
|
+
},
|
|
398
|
+
describe: (data, options) => {
|
|
399
|
+
const pin = pinOptions(options).pin;
|
|
400
|
+
if (pin)
|
|
401
|
+
return `Authenticated for pinned team ${pin.teamId}.`;
|
|
402
|
+
const payload = record(data);
|
|
403
|
+
const user = record(payload["user"] ?? payload);
|
|
404
|
+
const identity = text(user["username"]) ?? text(user["email"]) ?? text(user["name"]) ?? text(user["id"]);
|
|
405
|
+
return `Authenticated as ${identity ?? "a Vercel user"}.`;
|
|
406
|
+
},
|
|
407
|
+
failure: (error) => ({
|
|
408
|
+
ok: false,
|
|
409
|
+
message: error.code === "auth_required"
|
|
410
|
+
? "Vercel rejected the token: it is invalid, expired, or revoked."
|
|
411
|
+
: error.code === "provider_permission_denied"
|
|
412
|
+
? "Vercel accepted the token but refused to read the current user; check the token's scope."
|
|
413
|
+
: error.code === "rate_limited" || error.code === "unavailable"
|
|
414
|
+
? "Vercel could not check the token right now; try again shortly."
|
|
415
|
+
: "Vercel did not accept the token.",
|
|
416
|
+
}),
|
|
417
|
+
};
|
|
418
|
+
export function usageGuide(options) {
|
|
419
|
+
options = pinOptions(options);
|
|
420
|
+
const team = defaultTeam(options);
|
|
421
|
+
return {
|
|
422
|
+
context: [
|
|
423
|
+
`Vercel REST access token is already configured. Account purpose: ${options.purpose}`,
|
|
424
|
+
options.pin
|
|
425
|
+
? `Deployment team pin: ${options.pin.teamId}. Every call stays within this team; foreign or personal selectors and unprovable global routes are refused. Team/project/deployment checks may spend additional reads. Token permissions still apply.`
|
|
426
|
+
: team
|
|
427
|
+
? `Routing defaults to team ${team}. This is not a restriction or tenant pin: an explicit teamId or slug overrides it, and teamId: null selects personal scope. The token's Vercel permissions enforce access.`
|
|
428
|
+
: "Routing defaults to the token owner's personal account. Explicit teamId or slug selects a team within the token's permissions.",
|
|
429
|
+
...(options.readOnly ? ["This connection is read-only."] : []),
|
|
430
|
+
],
|
|
431
|
+
required: true,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function operationRefusal(op) {
|
|
435
|
+
return ((op.method === "GET" && op.path === "/v8/artifacts/{hash}"
|
|
436
|
+
? "Artifact downloads serve binary bytes despite this pin's JSON contract; this connector does not support artifact downloads."
|
|
437
|
+
: undefined) ??
|
|
438
|
+
vercelSafety.refusal(op.method, op.path) ??
|
|
439
|
+
checkServer(op.server) ??
|
|
440
|
+
(/octet-stream|^multipart\//.test(vercelIndex.bodyType(op) ?? "")
|
|
441
|
+
? "This operation takes explicit bytes; use vercel_api_upload."
|
|
442
|
+
: undefined));
|
|
443
|
+
}
|
|
444
|
+
export const VERCEL_VERDICTS = Object.fromEntries(vercelIndex.ops.map((op) => [
|
|
445
|
+
`${op.method} ${op.path}`,
|
|
446
|
+
operationRefusal(op)
|
|
447
|
+
? "refuse"
|
|
448
|
+
: op.method === "GET" ||
|
|
449
|
+
op.method === "HEAD" ||
|
|
450
|
+
READ_POSTS.some(([method, path]) => method === op.method && path === op.path)
|
|
451
|
+
? "read"
|
|
452
|
+
: "destructive",
|
|
453
|
+
]));
|
|
454
|
+
const PIN_DISCOVERY_REFUSALS = Object.freeze(Object.fromEntries(vercelIndex.ops
|
|
455
|
+
.filter((op) => pinRoute(vercelIndex, op) === "denied")
|
|
456
|
+
.map((op) => [`${op.method} ${op.path}`, "This operation has no reviewed deployment-team confinement."])));
|
|
457
|
+
/** Change only the generated Hatch count, after the shared guide renderer selects deployment blocks. */
|
|
458
|
+
export function transformGuide(guide, options) {
|
|
459
|
+
if (!pinOptions(options).pin)
|
|
460
|
+
return guide;
|
|
461
|
+
const base = Object.values(VERCEL_VERDICTS).filter((verdict) => verdict === "refuse").length;
|
|
462
|
+
const total = vercelIndex.ops.filter((op) => VERCEL_VERDICTS[`${op.method} ${op.path}`] === "refuse" ||
|
|
463
|
+
PIN_DISCOVERY_REFUSALS[`${op.method} ${op.path}`] !== undefined).length;
|
|
464
|
+
const sentence = `${base} operations are refused by design; use \`vercel_api_details\` with the method and path to inspect the original operation summary and its separate refusal.`;
|
|
465
|
+
const lines = guide.content.split("\n");
|
|
466
|
+
const targets = lines.flatMap((line, position) => line.startsWith("- **Hatch:** `vercel_api_search` ") && line.includes(sentence) ? [position] : []);
|
|
467
|
+
const position = targets[0];
|
|
468
|
+
const line = position === undefined ? undefined : lines[position];
|
|
469
|
+
if (targets.length !== 1 || line === undefined || line.indexOf(sentence) !== line.lastIndexOf(sentence))
|
|
470
|
+
throw new Error("Vercel's generated guide needs exactly one Hatch refusal-count sentence.");
|
|
471
|
+
const at = line.indexOf(sentence);
|
|
472
|
+
lines[position] = `${line.slice(0, at)}${total}${line.slice(at + String(base).length)}`;
|
|
473
|
+
return { ...guide, content: lines.join("\n") };
|
|
474
|
+
}
|
|
475
|
+
const cursorInputMessage = "Pass the signed page.nextCursor unchanged; omit it for the first page (numeric until may anchor its time window).";
|
|
476
|
+
const continuation = (next, call) => typeof next === "string" && /^(?:[a-z][a-z0-9+.-]*:\/\/|\/\/)/i.test(next.trim())
|
|
477
|
+
? { hasMore: true }
|
|
478
|
+
: timestampPage.next({ pagination: { next } }, call);
|
|
479
|
+
/** Only indexed parameters and documented response continuation names opt in. */
|
|
480
|
+
const paginationPage = (param, timestamp = false) => ({
|
|
481
|
+
...timestampPage,
|
|
482
|
+
param,
|
|
483
|
+
cursorInputMessage,
|
|
484
|
+
...(timestamp ? { initialAnchor: true } : {}),
|
|
485
|
+
next(data, call) {
|
|
486
|
+
const pagination = record(record(data)["pagination"]);
|
|
487
|
+
if (!Object.keys(pagination).length)
|
|
488
|
+
return undefined;
|
|
489
|
+
if (pagination["hasNext"] === false || pagination["hasMore"] === false)
|
|
490
|
+
return null;
|
|
491
|
+
const next = pagination["next"] ?? pagination["nextCursor"];
|
|
492
|
+
if (next === undefined) {
|
|
493
|
+
if (pagination["hasNext"] === true || pagination["hasMore"] === true)
|
|
494
|
+
throw new ConnectorCallError("connector_call_failed", "Vercel returned invalid paging metadata.", {
|
|
495
|
+
retryable: false,
|
|
496
|
+
});
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
return continuation(next, call);
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
export const VERCEL_PAGES = Object.fromEntries(vercelIndex.ops.flatMap((op) => {
|
|
503
|
+
if (VERCEL_VERDICTS[`${op.method} ${op.path}`] !== "read")
|
|
504
|
+
return [];
|
|
505
|
+
const names = queryNamesOf(vercelIndex, op), fields = vercelIndex
|
|
506
|
+
.fields(op)
|
|
507
|
+
.filter((f) => f.in === "response")
|
|
508
|
+
.map((f) => f.path);
|
|
509
|
+
if (fields.includes("pagination")) {
|
|
510
|
+
if (names.has("until"))
|
|
511
|
+
return [[`${op.method} ${op.path}`, paginationPage("until", true)]];
|
|
512
|
+
if (names.has("next"))
|
|
513
|
+
return [[`${op.method} ${op.path}`, paginationPage("next")]];
|
|
514
|
+
if (names.has("cursor"))
|
|
515
|
+
return [[`${op.method} ${op.path}`, paginationPage("cursor")]];
|
|
516
|
+
}
|
|
517
|
+
for (const key of ["nextCursor", "cursor", "next"])
|
|
518
|
+
if (fields.includes(key) && (names.has(key) || (key === "nextCursor" && names.has("cursor"))))
|
|
519
|
+
return [
|
|
520
|
+
[
|
|
521
|
+
`${op.method} ${op.path}`,
|
|
522
|
+
{
|
|
523
|
+
...timestampPage,
|
|
524
|
+
param: names.has(key) ? key : "cursor",
|
|
525
|
+
cursorInputMessage,
|
|
526
|
+
next: (data) => {
|
|
527
|
+
const value = record(data)[key];
|
|
528
|
+
return value === null || value === undefined
|
|
529
|
+
? null
|
|
530
|
+
: continuation(value, { path: "", query: {}, headers: {} });
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
],
|
|
534
|
+
];
|
|
535
|
+
return [];
|
|
536
|
+
}));
|
|
537
|
+
export function vercelBinding({ connection, options, }) {
|
|
538
|
+
options = pinOptions(options);
|
|
539
|
+
return {
|
|
540
|
+
...(options.pin ? { cursorScope: Object.freeze({ vercelTeamPin: options.pin.teamId }) } : {}),
|
|
541
|
+
...(options.pin ? { discoveryRefusal: (op) => PIN_DISCOVERY_REFUSALS[`${op.method} ${op.path}`] } : {}),
|
|
542
|
+
review: "The same teamId/slug/null routing used by named tools and uploads, resolved before request validation and signed cursor binding. Token permissions remain the boundary. JSON framing through the existing connection.",
|
|
543
|
+
query: (call) => scope(call, defaultTeam(options), options.pin).query,
|
|
544
|
+
refusal: (op) => operationRefusal(op) ??
|
|
545
|
+
(options.pin && pinRoute(vercelIndex, op) === "denied"
|
|
546
|
+
? "This operation has no reviewed deployment-team confinement."
|
|
547
|
+
: undefined),
|
|
548
|
+
refuse: (call) => operationRefusal(call.op) ?? refuse(call),
|
|
549
|
+
send: (call, sendOptions) => sendVercel(connection, call, options, sendOptions),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { ConnectorCallAdmissionPolicy } from "../../types.js";
|
|
2
|
-
export { VERCEL_API_BASE_URL } from "./
|
|
3
|
-
/** Vercel's official hosted MCP endpoint. */
|
|
4
|
-
export declare const VERCEL_MCP_ENDPOINT = "https://mcp.vercel.com";
|
|
2
|
+
export { VERCEL_API_BASE_URL } from "./lib/config.js";
|
|
5
3
|
interface VercelCommonOptions {
|
|
6
4
|
/** Human-readable display name; defaults identify the implementation. */
|
|
7
5
|
title?: string;
|
|
@@ -18,38 +16,24 @@ interface VercelCommonOptions {
|
|
|
18
16
|
/** Serve only read-classified tools on every endpoint. Defaults to false. */
|
|
19
17
|
readOnly?: boolean;
|
|
20
18
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Vercel's hosted MCP server over OAuth. Vercel admits only reviewed and
|
|
23
|
-
* approved MCP clients, an operational gate outside Connecta.
|
|
24
|
-
*/
|
|
25
|
-
export interface VercelOAuthOptions extends VercelCommonOptions {
|
|
26
|
-
auth: {
|
|
27
|
-
type: "oauth";
|
|
28
|
-
};
|
|
29
|
-
/** Refused: the hosted server scopes by the teams the grant can reach. */
|
|
30
|
-
teamId?: never;
|
|
31
|
-
/** Refused: the hosted server has one endpoint. */
|
|
32
|
-
baseUrl?: never;
|
|
33
|
-
}
|
|
34
19
|
/**
|
|
35
20
|
* Connecta's REST connector to `api.vercel.com` with one operator-managed
|
|
36
21
|
* access token, pasted into the connection in the operator UI.
|
|
37
22
|
*/
|
|
23
|
+
export interface VercelTeamPin {
|
|
24
|
+
readonly teamId: string;
|
|
25
|
+
}
|
|
38
26
|
export interface VercelTokenOptions extends VercelCommonOptions {
|
|
39
27
|
auth: {
|
|
40
28
|
type: "token";
|
|
41
29
|
};
|
|
42
30
|
/** Default team id for operations that accept one. Omit for the token owner's personal account. */
|
|
43
31
|
teamId?: string;
|
|
32
|
+
/** Deployment-owned confinement to one team, independent of routing defaults and token permissions. */
|
|
33
|
+
pin?: VercelTeamPin;
|
|
44
34
|
/** API base override for a proxy or test double. */
|
|
45
35
|
baseUrl?: string;
|
|
46
36
|
}
|
|
47
|
-
export type VercelOptions =
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
* reaches Vercel's hosted MCP server; an access token reaches Connecta's REST
|
|
51
|
-
* connector to `api.vercel.com`.
|
|
52
|
-
*/
|
|
53
|
-
export declare const vercel: import("../../provider.js").ProviderFactory<VercelOptions>;
|
|
54
|
-
/** @deprecated Read `vercel.definition.classify` instead. Kept for existing imports. */
|
|
55
|
-
export declare const VERCEL_MCP_VETTED_CATALOG: import("../../catalog-drift.js").VettedCatalog;
|
|
37
|
+
export type VercelOptions = VercelTokenOptions;
|
|
38
|
+
/** Maintained API connection. Hosted MCP uses a separate generic connection. */
|
|
39
|
+
export declare const vercel: import("../../provider.js").ProviderFactory<VercelTokenOptions>;
|