@zackbart/connecta 0.34.0 → 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 +393 -0
- package/README.md +62 -105
- 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 +15 -7
- package/dist/connectors/api-connector.js +147 -57
- 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/option-shapes.js +1 -1
- package/dist/connectors/remote-mcp.d.ts +12 -30
- package/dist/connectors/remote-mcp.js +960 -564
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +5 -1
- package/dist/describe-config.js +3 -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 +221 -87
- 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 +534 -408
- 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 +8 -4
- package/dist/operator-ui/connection-map.js +9 -6
- package/dist/operator-ui/contract.d.ts +7 -3
- package/dist/operator-ui/endpoints.js +5 -14
- 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/health.js +52 -0
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/operator-ui/overview.js +11 -19
- package/dist/operator-ui/setup-commands.js +0 -4
- package/dist/operator-ui/view.d.ts +5 -29
- package/dist/operator-ui/view.js +3 -59
- 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 +71 -32
- package/dist/provider.js +134 -40
- 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 +8 -209
- package/dist/providers/_shared/google/workspace.js +7 -426
- 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 +3 -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 +17 -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 +7 -22
- package/dist/providers/cloudflare/index.js +80 -194
- 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 +3299 -3208
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +538 -18
- 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 +3 -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 +6 -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 +7 -27
- package/dist/providers/stripe/index.js +75 -191
- 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 -10
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +19 -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 +11 -25
- package/dist/providers/vercel/index.js +67 -261
- 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 +398 -376
- package/dist/providers/vercel/{value-safety.js → safety.js} +261 -42
- 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 +30 -28
- package/dist/registry.js +446 -413
- 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 +29 -22
- 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 +330 -224
- 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 +11 -99
- 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 +2550 -186
- package/documentation/auth.md +821 -98
- package/documentation/code-mode.md +56 -21
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +144 -105
- package/documentation/meta-tools.md +411 -34
- package/documentation/operating.md +28 -4
- package/documentation/operator-tests.md +11 -2
- package/documentation/operator-ui.md +103 -33
- package/documentation/provider-auth-migration.md +38 -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 -42
- 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 -134
- package/dist/catalog-drift.js +0 -493
- 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 -43
- 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 -1078
- 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/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -352
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -57
- package/dist/providers/mixpanel/index.js +0 -468
- 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 -65
- 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 -49
- package/dist/providers/revenuecat/index.js +0 -557
- 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,150 @@
|
|
|
1
|
+
// GitHub's finite REST complement. App JWT discovery and minting remain private
|
|
2
|
+
// auxiliary requests; rejected installation tokens renew on the next call only.
|
|
3
|
+
import { Effect, Schema } from "effect";
|
|
4
|
+
import { indexedVendor } from "../_shared/rest/kit.js";
|
|
5
|
+
import { githubIndex, GITHUB_HATCH } from "./lib/discovery.js";
|
|
6
|
+
import { githubSafety } from "./safety.js";
|
|
7
|
+
import { INTERNAL_READS, MERGE, READ_PERMISSIONS, RELEASE_PERMISSIONS } from "./lib/operations.js";
|
|
8
|
+
import { errorMeanings } from "../../http/errors.js";
|
|
9
|
+
import { ConnectorCallError } from "../../errors.js";
|
|
10
|
+
import { guardedClient } from "../../http/client.js";
|
|
11
|
+
import { captureClassifierFacts, StatusFailure } from "../../http/errors.js";
|
|
12
|
+
import { resultSafety } from "../../http/safety.js";
|
|
13
|
+
import { connect, get, operations } from "../../http/vendor.js";
|
|
14
|
+
const repository = { owner: Schema.String, repo: Schema.String };
|
|
15
|
+
const success = Schema.Unknown;
|
|
16
|
+
export const GitHubApi = indexedVendor("GitHub", githubIndex, [
|
|
17
|
+
get("repository", "/repos/:owner/:repo", { params: repository, success }),
|
|
18
|
+
]);
|
|
19
|
+
function rateLimited(status, retry, remaining) {
|
|
20
|
+
return status === 429 || (status === 403 && (retry !== undefined || remaining === 0));
|
|
21
|
+
}
|
|
22
|
+
export function rejectedToken(status, headers) {
|
|
23
|
+
if (status !== 403)
|
|
24
|
+
return status === 401;
|
|
25
|
+
const numbers = captureClassifierFacts(undefined, errors, headers)?.numbers;
|
|
26
|
+
return !rateLimited(status, numbers?.["retryAfterMs"], numbers?.["remaining"]);
|
|
27
|
+
}
|
|
28
|
+
const AUTH = "Verify the GitHub App key, installation, repository selection and required permissions.";
|
|
29
|
+
const FAILED = "The GitHub API request failed; downstream error text is withheld.";
|
|
30
|
+
function rate(input) {
|
|
31
|
+
if (!rateLimited(input.status, input.retryAfterMs, input.numbers["remaining"]))
|
|
32
|
+
return undefined;
|
|
33
|
+
const wait = input.retryAfterMs ?? input.numbers["resetAfterMs"];
|
|
34
|
+
return {
|
|
35
|
+
code: "rate_limited",
|
|
36
|
+
retryable: true,
|
|
37
|
+
...(wait === undefined ? {} : { retryAfterMs: wait }),
|
|
38
|
+
words: () => "GitHub rate limit reached.",
|
|
39
|
+
advice: " Honor the retry timing before trying again.",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export const errors = {
|
|
43
|
+
numericFacts: {
|
|
44
|
+
retryAfterMs: {
|
|
45
|
+
header: "retry-after",
|
|
46
|
+
format: "retry-after",
|
|
47
|
+
review: "HTTP Retry-After delay for failure recovery.",
|
|
48
|
+
},
|
|
49
|
+
remaining: {
|
|
50
|
+
header: "x-ratelimit-remaining",
|
|
51
|
+
review: "GitHub classifies a 403 with no remaining requests as rate limited.",
|
|
52
|
+
},
|
|
53
|
+
resetAfterMs: {
|
|
54
|
+
header: "x-ratelimit-reset",
|
|
55
|
+
format: "epoch-seconds",
|
|
56
|
+
review: "GitHub rate-limit reset time in epoch seconds.",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
provider: "GitHub",
|
|
60
|
+
facts: (body) => ({
|
|
61
|
+
merged: body && typeof body === "object" && "merged" in body && body.merged === false ? false : undefined,
|
|
62
|
+
}),
|
|
63
|
+
factVocabulary: { merged: [false] },
|
|
64
|
+
meanings: {
|
|
65
|
+
unknownId: "connector_call_failed",
|
|
66
|
+
provesAbsence: false,
|
|
67
|
+
conflicts: [
|
|
68
|
+
"HTTP 409 is a conflict; a merged:false response and the expected-SHA/same-repository preflight also refuse a merge",
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
classify: [
|
|
72
|
+
({ operation, facts }) => operation.method === "PUT" &&
|
|
73
|
+
operation.template === "/repos/{owner}/{repo}/pulls/{pull_number}/merge" &&
|
|
74
|
+
facts["merged"] === false
|
|
75
|
+
? {
|
|
76
|
+
code: "conflict",
|
|
77
|
+
retryable: false,
|
|
78
|
+
words: () => "GitHub reported that the pull request was not merged. Read its current state before another attempt.",
|
|
79
|
+
}
|
|
80
|
+
: undefined,
|
|
81
|
+
rate,
|
|
82
|
+
],
|
|
83
|
+
detail: ({ status: code }) => code === 401 || code === 403
|
|
84
|
+
? AUTH
|
|
85
|
+
: code === 409
|
|
86
|
+
? "GitHub reported a conflict. Read the current state."
|
|
87
|
+
: code === 404
|
|
88
|
+
? "GitHub returned 404; the resource may be absent or inaccessible to this installation."
|
|
89
|
+
: FAILED,
|
|
90
|
+
statuses: {
|
|
91
|
+
401: () => ({ code: "auth_required", retryable: false }),
|
|
92
|
+
403: () => ({ code: "auth_required", retryable: false }),
|
|
93
|
+
404: () => ({ code: "connector_call_failed", retryable: false }),
|
|
94
|
+
409: () => ({ code: "conflict", retryable: false, advice: " before resubmitting" }),
|
|
95
|
+
},
|
|
96
|
+
bodyFailures: {
|
|
97
|
+
review: "GitHub failure classification uses only status and declared numeric headers; failed success reads disclose no raw cause.",
|
|
98
|
+
statusOnly: [],
|
|
99
|
+
readFailed: "GitHub's response body could not be read completely.",
|
|
100
|
+
},
|
|
101
|
+
unreadable: "GitHub returned malformed JSON; response text is withheld.",
|
|
102
|
+
};
|
|
103
|
+
export function githubConnection() {
|
|
104
|
+
const safety = resultSafety({
|
|
105
|
+
reviewed: {
|
|
106
|
+
refusal: (op) => githubSafety.refusal(op.method, op.template),
|
|
107
|
+
resolve: (method, path) => {
|
|
108
|
+
const { op } = githubIndex.resolve(method, path);
|
|
109
|
+
const key = `${op.method} ${op.path}`;
|
|
110
|
+
if (!READ_PERMISSIONS[key] && !RELEASE_PERMISSIONS[key] && !INTERNAL_READS.has(key) && key !== MERGE)
|
|
111
|
+
throw new ConnectorCallError("invalid_args", "This GitHub operation is not exposed by the connection.");
|
|
112
|
+
return { method: op.method, template: op.path };
|
|
113
|
+
},
|
|
114
|
+
redact: (data, op) => githubSafety.redact(data, op.method, op.template),
|
|
115
|
+
withholdsErrors: () => true,
|
|
116
|
+
},
|
|
117
|
+
sensitiveFields: { names: [], choice: "drop" },
|
|
118
|
+
});
|
|
119
|
+
return connect(GitHubApi, guardedClient({
|
|
120
|
+
provider: "GitHub",
|
|
121
|
+
baseUrl: "https://api.github.com",
|
|
122
|
+
maxResponseBytes: 4 * 1024 * 1024,
|
|
123
|
+
headers: {
|
|
124
|
+
Accept: "application/vnd.github+json",
|
|
125
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
126
|
+
"User-Agent": "connecta",
|
|
127
|
+
},
|
|
128
|
+
authenticate: Effect.succeed({}),
|
|
129
|
+
}), {
|
|
130
|
+
errors,
|
|
131
|
+
safety,
|
|
132
|
+
inBandRefusals: true,
|
|
133
|
+
result: (body, answer) => answer.operation.method === "PUT" &&
|
|
134
|
+
answer.operation.template === "/repos/{owner}/{repo}/pulls/{pull_number}/merge" &&
|
|
135
|
+
body &&
|
|
136
|
+
typeof body === "object" &&
|
|
137
|
+
"merged" in body &&
|
|
138
|
+
body.merged === false
|
|
139
|
+
? Effect.fail(new StatusFailure({ status: answer.status, headers: answer.headers, body, operation: answer.operation }))
|
|
140
|
+
: Effect.succeed(answer.bodyFormat === "empty" ? undefined : body),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
export const kit = {
|
|
144
|
+
operations: operations(GitHubApi),
|
|
145
|
+
pageSize: { default: 30, max: 100 },
|
|
146
|
+
hatch: { name: GITHUB_HATCH.name, verdicts: GITHUB_HATCH.verdicts, paging: "argument" },
|
|
147
|
+
errors: errorMeanings(errors),
|
|
148
|
+
idempotencyKey: false,
|
|
149
|
+
refusesWithheld: true,
|
|
150
|
+
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { type ProviderOptions } from "../../provider.js";
|
|
2
|
-
import { type GitHubApp } from "./auth.js";
|
|
3
|
-
import { type GitHubScope } from "./scope.js";
|
|
4
|
-
export type { GitHubApp } from "./auth.js";
|
|
5
|
-
export type { GitHubScope } from "./scope.js";
|
|
6
|
-
export declare const GITHUB_MCP_ENDPOINT = "https://api.githubcopilot.com/mcp/";
|
|
2
|
+
import { type GitHubApp } from "./lib/auth.js";
|
|
3
|
+
import { type GitHubScope } from "./lib/scope.js";
|
|
4
|
+
export type { GitHubApp } from "./lib/auth.js";
|
|
5
|
+
export type { GitHubScope } from "./lib/scope.js";
|
|
7
6
|
export interface GitHubOptions extends ProviderOptions {
|
|
8
7
|
app: GitHubApp;
|
|
9
8
|
/** Mixed org and exact repository grants, with explicit access. */
|
|
@@ -1,37 +1,22 @@
|
|
|
1
|
-
import { remoteMcp } from "../../connectors/remote-mcp.js";
|
|
2
1
|
import { array, keys, optionsOf } from "../../config-schema.js";
|
|
3
2
|
import { ConnectorCallError } from "../../errors.js";
|
|
4
|
-
import {
|
|
3
|
+
import { delegatesEachCall, knownProviderFailure } from "../../provider-call.js";
|
|
5
4
|
import { defineProvider, PROVIDER_COMMON } from "../../provider.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { restConnector } from "./
|
|
9
|
-
import { parseScopes, ScopePolicy } from "./scope.js";
|
|
5
|
+
import { kit } from "./api.js";
|
|
6
|
+
import { AppAuth } from "./lib/auth.js";
|
|
7
|
+
import { restConnector } from "./tools.js";
|
|
8
|
+
import { parseScopes, ScopePolicy } from "./lib/scope.js";
|
|
10
9
|
import { skill } from "./skill.generated.js";
|
|
11
|
-
export const GITHUB_MCP_ENDPOINT = "https://api.githubcopilot.com/mcp/";
|
|
12
|
-
const CLASSIFICATION = {
|
|
13
|
-
unlisted: "hide",
|
|
14
|
-
tools: {
|
|
15
|
-
...HOSTED_CLASSIFICATION.tools,
|
|
16
|
-
list_scopes: "read",
|
|
17
|
-
search_scoped: "read",
|
|
18
|
-
create_release: "write",
|
|
19
|
-
update_release: "destructive",
|
|
20
|
-
delete_release: "destructive",
|
|
21
|
-
merge_pull_request: "destructive",
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
10
|
/** One GitHub App, multiple owner installations, explicit org/repo grants. */
|
|
25
11
|
export const github = defineProvider({
|
|
26
12
|
name: "github",
|
|
27
13
|
title: "GitHub",
|
|
28
|
-
kind: "
|
|
14
|
+
kind: "api",
|
|
29
15
|
skill,
|
|
30
|
-
|
|
16
|
+
kit,
|
|
31
17
|
bundle: {
|
|
32
|
-
baselineGzip:
|
|
33
|
-
|
|
34
|
-
note: "./providers/github initial measured baseline includes the hosted MCP SDK plus portable GitHub App signing, exact routing and REST complements; cap retains the existing 60,000-byte provider headroom policy.",
|
|
18
|
+
baselineGzip: 451137,
|
|
19
|
+
note: "./providers/github merged step-2 snapshot measures 451,137 B gzip standalone before recording these bundle facts (#703), Node 26.12.0 with zlib-ng 1.3.1, scripts/bundle-size.mjs. The complete immutable 1,204-operation index is retained. The same-toolchain pre-change probe was 335,233 B; growth is approximately 115 KB, below the approved 300 KB gate. This entry includes the shared Effect provider kit, paid once per deployment; standalone provider sizes are not additive. Provider entries are reported, not capped (#899).",
|
|
35
20
|
},
|
|
36
21
|
options: optionsOf()({
|
|
37
22
|
...PROVIDER_COMMON,
|
|
@@ -40,45 +25,23 @@ export const github = defineProvider({
|
|
|
40
25
|
}),
|
|
41
26
|
create(id, options, provider) {
|
|
42
27
|
if (options.authScope === "personal")
|
|
43
|
-
throw new Error("shared GitHub App identity; per-user OAuth attribution is planned separately
|
|
28
|
+
throw new Error("shared GitHub App identity; per-user OAuth attribution is planned separately.");
|
|
44
29
|
const policy = new ScopePolicy(parseScopes(options.scopes));
|
|
45
30
|
const auth = new AppAuth(options.app, policy);
|
|
46
31
|
const rest = restConnector(id, auth, policy);
|
|
32
|
+
// Delegation invokes these captured functions with Reflect.apply, never
|
|
33
|
+
// through a property or a function's own `call`.
|
|
34
|
+
const restCall = rest.callTool;
|
|
47
35
|
const usageGuide = provider.usageGuide({
|
|
48
|
-
context: [
|
|
49
|
-
|
|
36
|
+
context: [
|
|
37
|
+
`Act as the GitHub App. Reachable configuration: ${policy.summary()}.`,
|
|
38
|
+
`Purpose: ${options.purpose}`,
|
|
39
|
+
"The App identity is already configured. Installation access is resolved on use; ask the operator to repair configuration only after an auth failure.",
|
|
40
|
+
...(options.readOnly === true ? ["This connection is read-only; every write is refused."] : []),
|
|
41
|
+
],
|
|
42
|
+
summary: "GitHub App repository reads, scoped data search, release writes and guarded merges.",
|
|
50
43
|
required: true,
|
|
51
44
|
});
|
|
52
|
-
// Each operation owns its token-bound client, transport and callback.
|
|
53
|
-
// It closes them before returning; no shared header or client mutation.
|
|
54
|
-
const hosted = (token, classify = HOSTED_CLASSIFICATION) => remoteMcp(id, {
|
|
55
|
-
url: GITHUB_MCP_ENDPOINT,
|
|
56
|
-
requireHttps: true,
|
|
57
|
-
redirects: "none",
|
|
58
|
-
classify,
|
|
59
|
-
auth: {
|
|
60
|
-
type: "request",
|
|
61
|
-
token: async () => token,
|
|
62
|
-
headers: {
|
|
63
|
-
"X-MCP-Toolsets": "repos,git,issues,pull_requests,actions,labels",
|
|
64
|
-
"X-MCP-Tools": Object.keys(HOSTED_ROUTES).join(","),
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
const withHosted = async (token, ctx, run) => {
|
|
69
|
-
const client = hosted(token);
|
|
70
|
-
try {
|
|
71
|
-
return await run(client);
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
if (error instanceof ConnectorCallError && error.code === "auth_required")
|
|
75
|
-
await auth.rejectToken(token);
|
|
76
|
-
throw redactSentSecrets(ctx, error);
|
|
77
|
-
}
|
|
78
|
-
finally {
|
|
79
|
-
await client.closeScope?.(ctx);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
45
|
const credential = options.app.privateKey === undefined
|
|
83
46
|
? {
|
|
84
47
|
label: "GitHub App private key",
|
|
@@ -86,55 +49,35 @@ export const github = defineProvider({
|
|
|
86
49
|
fields: [{ name: "privateKey", label: "Private key PEM", inputType: "password" }],
|
|
87
50
|
}
|
|
88
51
|
: undefined;
|
|
89
|
-
|
|
52
|
+
// Each call goes to its REST boundary, whose write-outcome verdict stands.
|
|
53
|
+
return delegatesEachCall({
|
|
90
54
|
id,
|
|
91
55
|
title: options.title ?? "GitHub",
|
|
56
|
+
// Preserve the REST tools' existing MCP result envelope.
|
|
92
57
|
kind: "mcp",
|
|
93
58
|
authScope: "shared",
|
|
94
59
|
...provider.connectorOptions,
|
|
95
60
|
...(credential ? { credential } : {}),
|
|
96
|
-
classification: provider.classify,
|
|
97
61
|
description: `GitHub App access to ${policy.summary()}.`,
|
|
98
62
|
usageGuide,
|
|
99
63
|
describe: () => ({
|
|
100
64
|
source: { kind: "custom" },
|
|
101
|
-
endpoint: { origin: "https://api.
|
|
65
|
+
endpoint: { origin: "https://api.github.com", path: "/" },
|
|
102
66
|
auth: { mode: "request", header: "Authorization", scheme: "Bearer" },
|
|
103
67
|
}),
|
|
68
|
+
staticTools: rest.staticTools,
|
|
104
69
|
async listTools(ctx) {
|
|
105
|
-
|
|
106
|
-
// permissions. Fetch one complete vendor catalog and preserve schemas.
|
|
107
|
-
const first = policy.scopes[0];
|
|
108
|
-
const owner = first.org ?? first.repo.split("/")[0];
|
|
109
|
-
const repos = first.repo ? [first.repo.split("/")[1]] : undefined;
|
|
110
|
-
const token = await auth.token(owner, repos, {
|
|
111
|
-
contents: "read",
|
|
112
|
-
issues: "read",
|
|
113
|
-
pull_requests: "read",
|
|
114
|
-
actions: "read",
|
|
115
|
-
checks: "read",
|
|
116
|
-
statuses: "read",
|
|
117
|
-
}, ctx);
|
|
118
|
-
const facts = await withHosted(token, ctx, (client) => client.listTools(ctx));
|
|
119
|
-
const complement = await rest.listTools(ctx);
|
|
120
|
-
// Names not reviewed never leave this boundary, including collisions
|
|
121
|
-
// with the REST complement. The registry alone applies verdicts.
|
|
122
|
-
return [...facts.filter((tool) => Object.hasOwn(HOSTED_ROUTES, tool.name)), ...complement];
|
|
70
|
+
return rest.listTools(ctx);
|
|
123
71
|
},
|
|
124
72
|
async callTool(name, args, ctx, callOptions) {
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return withHosted(token, ctx, (client) => client.callTool(name, args, ctx, callOptions));
|
|
129
|
-
}
|
|
130
|
-
if (!Object.hasOwn(CLASSIFICATION.tools, name))
|
|
131
|
-
throw new ConnectorCallError("invalid_args", "This GitHub tool is not in the reviewed allowlist.");
|
|
132
|
-
const result = await rest.callTool(name, args, ctx, callOptions);
|
|
73
|
+
if (!rest.staticTools?.some((tool) => tool.name === name))
|
|
74
|
+
throw knownProviderFailure(new ConnectorCallError("invalid_args", "This GitHub tool is not in the REST tool set."));
|
|
75
|
+
const result = await Reflect.apply(restCall, rest, [name, args, ctx, callOptions]);
|
|
133
76
|
return { content: [{ type: "text", text: JSON.stringify(result) }], structuredContent: result };
|
|
134
77
|
},
|
|
135
78
|
async status() {
|
|
136
79
|
return { state: "ok", message: "GitHub App configured; installation access is resolved lazily on use." };
|
|
137
80
|
},
|
|
138
|
-
};
|
|
81
|
+
});
|
|
139
82
|
},
|
|
140
83
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type GuardedRequest } from "
|
|
2
|
-
import type { ConnectorContext } from "
|
|
1
|
+
import { type GuardedRequest } from "../../../connectors/guarded-fetch.js";
|
|
2
|
+
import type { ConnectorContext } from "../../../types.js";
|
|
3
3
|
import { ScopePolicy, type Target } from "./scope.js";
|
|
4
4
|
export interface GitHubApp {
|
|
5
5
|
appId: string | number;
|
|
@@ -17,9 +17,11 @@ export declare class AppAuth {
|
|
|
17
17
|
private readonly orgInstallations;
|
|
18
18
|
private readonly configuredDer?;
|
|
19
19
|
private readonly appId;
|
|
20
|
+
private readonly send;
|
|
20
21
|
constructor(app: GitHubApp, policy: ScopePolicy);
|
|
21
22
|
private key;
|
|
22
|
-
|
|
23
|
+
/** `write` sends the request once: a failure that may follow the change is not retryable. */
|
|
24
|
+
json(request: GuardedRequest, bearer: string, ctx: ConnectorContext, write?: boolean): Promise<unknown>;
|
|
23
25
|
private invalidate;
|
|
24
26
|
rejectToken(bearer: string): Promise<void>;
|
|
25
27
|
private installation;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { guardedFetch
|
|
2
|
-
import { ConnectorCallError } from "
|
|
3
|
-
import {
|
|
1
|
+
import { guardedFetch } from "../../../connectors/guarded-fetch.js";
|
|
2
|
+
import { ConnectorCallError } from "../../../errors.js";
|
|
3
|
+
import { captureClassifierFacts, StatusFailure, toConnectorCallError, withClassifierFacts, } from "../../../http/errors.js";
|
|
4
|
+
import { errors, rejectedToken } from "../api.js";
|
|
4
5
|
import { appJwt, privateKeyDer } from "./key.js";
|
|
5
6
|
import { target } from "./scope.js";
|
|
7
|
+
import { sentSecretsFor } from "../../../sent-secrets.js";
|
|
6
8
|
const MAX_ENTRIES = 256;
|
|
7
9
|
const REFRESH_MARGIN = 60_000;
|
|
8
10
|
const INSTALLATION_TTL = 300_000;
|
|
@@ -30,6 +32,17 @@ export class AppAuth {
|
|
|
30
32
|
orgInstallations = new Map();
|
|
31
33
|
configuredDer;
|
|
32
34
|
appId;
|
|
35
|
+
send = guardedFetch({
|
|
36
|
+
provider: "GitHub",
|
|
37
|
+
baseUrl: "https://api.github.com",
|
|
38
|
+
maxResponseBytes: 4 * 1024 * 1024,
|
|
39
|
+
headers: {
|
|
40
|
+
Accept: "application/vnd.github+json",
|
|
41
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
42
|
+
"User-Agent": "connecta",
|
|
43
|
+
},
|
|
44
|
+
authenticate: () => ({}),
|
|
45
|
+
});
|
|
33
46
|
constructor(app, policy) {
|
|
34
47
|
this.app = app;
|
|
35
48
|
this.policy = policy;
|
|
@@ -48,6 +61,8 @@ export class AppAuth {
|
|
|
48
61
|
}
|
|
49
62
|
async key(ctx) {
|
|
50
63
|
ctx.signal?.throwIfAborted();
|
|
64
|
+
if (this.app.privateKey !== undefined)
|
|
65
|
+
sentSecretsFor(ctx).add(this.app.privateKey);
|
|
51
66
|
let der = this.configuredDer;
|
|
52
67
|
if (!der) {
|
|
53
68
|
const pem = await ctx.credential?.get("privateKey");
|
|
@@ -67,49 +82,30 @@ export class AppAuth {
|
|
|
67
82
|
fingerprint: `${this.appId}:${Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")}`,
|
|
68
83
|
};
|
|
69
84
|
}
|
|
70
|
-
|
|
85
|
+
/** `write` sends the request once: a failure that may follow the change is not retryable. */
|
|
86
|
+
async json(request, bearer, ctx, write = false) {
|
|
71
87
|
ctx.signal?.throwIfAborted();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Accept: "application/vnd.github+json",
|
|
78
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
79
|
-
"User-Agent": "connecta",
|
|
80
|
-
},
|
|
81
|
-
authenticate: () => ({ Authorization: `Bearer ${bearer}` }),
|
|
82
|
-
});
|
|
83
|
-
return transport(request, ctx, async (response) => {
|
|
88
|
+
return this.send({
|
|
89
|
+
classification: write ? "write" : "read",
|
|
90
|
+
...request,
|
|
91
|
+
headers: { ...request.headers, Authorization: `Bearer ${bearer}` },
|
|
92
|
+
}, ctx, async (response) => {
|
|
84
93
|
if (!response.ok) {
|
|
85
94
|
// Dispose of the body without interpreting error text or Content-Type.
|
|
86
95
|
await response.prefix(0);
|
|
87
|
-
|
|
88
|
-
const reset = Number(response.headers.get("x-ratelimit-reset"));
|
|
89
|
-
const retry = retryAfterMs(response.headers);
|
|
90
|
-
const wait = retry ?? (Number.isFinite(reset) && reset > 0 ? Math.max(0, reset * 1000 - Date.now()) : undefined);
|
|
91
|
-
const rate = status === 429 ||
|
|
92
|
-
(status === 403 && (retry !== undefined || response.headers.get("x-ratelimit-remaining") === "0"));
|
|
93
|
-
if (status === 401 || (status === 403 && !rate))
|
|
96
|
+
if (rejectedToken(response.status, response.headers))
|
|
94
97
|
this.invalidate(bearer);
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
: code === "auth_required"
|
|
107
|
-
? "Verify the GitHub App key, installation, repository selection and required permissions."
|
|
108
|
-
: code === "conflict"
|
|
109
|
-
? "GitHub rejected a stale version. Read the current state before resubmitting."
|
|
110
|
-
: status === 404
|
|
111
|
-
? "GitHub returned 404; the resource may be absent or inaccessible to this installation."
|
|
112
|
-
: "The GitHub API request failed; downstream error text is withheld.", { retryable: rate || status >= 500, ...(rate && wait !== undefined ? { retryAfterMs: wait } : {}) }), { httpStatus: status });
|
|
98
|
+
const failure = withClassifierFacts(new StatusFailure({
|
|
99
|
+
status: response.status,
|
|
100
|
+
headers: response.headers,
|
|
101
|
+
body: undefined,
|
|
102
|
+
operation: { method: request.method, template: request.path },
|
|
103
|
+
}), captureClassifierFacts(undefined, errors, response.headers));
|
|
104
|
+
throw toConnectorCallError(failure, errors, {
|
|
105
|
+
classification: write ? "write" : "read",
|
|
106
|
+
dispatches: [],
|
|
107
|
+
withholds: () => true,
|
|
108
|
+
});
|
|
113
109
|
}
|
|
114
110
|
const parsed = await response.jsonResult();
|
|
115
111
|
ctx.signal?.throwIfAborted();
|
|
@@ -147,7 +143,7 @@ export class AppAuth {
|
|
|
147
143
|
}
|
|
148
144
|
let found;
|
|
149
145
|
for (let page = 1; page <= 20; page++) {
|
|
150
|
-
const data = await this.json({ method: "GET", path: "/app/installations", query: { per_page: 100, page } }, jwt, ctx);
|
|
146
|
+
const data = await this.json({ auxiliary: true, method: "GET", path: "/app/installations", query: { per_page: 100, page } }, jwt, ctx);
|
|
151
147
|
if (!Array.isArray(data))
|
|
152
148
|
throw new ConnectorCallError("connector_call_failed", "GitHub returned an invalid installation list.");
|
|
153
149
|
for (const item of data) {
|
|
@@ -204,7 +200,7 @@ export class AppAuth {
|
|
|
204
200
|
const found = new Map();
|
|
205
201
|
const configured = this.policy.scopes.filter((scope) => scope.repo?.startsWith(`${owner}/`));
|
|
206
202
|
for (let page = 1; page <= 100; page++) {
|
|
207
|
-
const data = record(await this.json({ method: "GET", path: "/installation/repositories", query: { per_page: 100, page } }, resolver, ctx));
|
|
203
|
+
const data = record(await this.json({ auxiliary: true, method: "GET", path: "/installation/repositories", query: { per_page: 100, page } }, resolver, ctx));
|
|
208
204
|
if (!Array.isArray(data.repositories))
|
|
209
205
|
throw new ConnectorCallError("connector_call_failed", "GitHub returned an invalid repository list.");
|
|
210
206
|
for (const item of data.repositories) {
|
|
@@ -303,6 +299,7 @@ export class AppAuth {
|
|
|
303
299
|
let response;
|
|
304
300
|
try {
|
|
305
301
|
response = record(await this.json({
|
|
302
|
+
auxiliary: true,
|
|
306
303
|
method: "POST",
|
|
307
304
|
path: `/app/installations/${installation.id}/access_tokens`,
|
|
308
305
|
body: { permissions: permissionSet, ...(repositorySet ? { repository_ids: repositorySet } : {}) },
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { OperationIndex } from "../../_shared/rest/operation-index.js";
|
|
2
|
+
import { githubPage } from "../../../http/hatch-paging.js";
|
|
3
|
+
import { openapi } from "../openapi.generated.js";
|
|
4
|
+
import { READ_PERMISSIONS, RELEASE_PERMISSIONS, INTERNAL_READS, MERGE } from "./operations.js";
|
|
5
|
+
import { workflowPath } from "./scope.js";
|
|
6
|
+
import { ConnectorCallError } from "../../../errors.js";
|
|
7
|
+
/** Only the documented contents path can consume several literal URL segments. */
|
|
8
|
+
export function contentsPath(path) {
|
|
9
|
+
// Preserve the shared refusal of URL suffixes and unfilled placeholders.
|
|
10
|
+
if (/[?#{}]/.test(path))
|
|
11
|
+
return path;
|
|
12
|
+
const match = /^(\/repos\/[^/]+\/[^/]+\/contents\/)(.+)$/.exec(path);
|
|
13
|
+
if (!match)
|
|
14
|
+
return path;
|
|
15
|
+
let relative;
|
|
16
|
+
try {
|
|
17
|
+
relative = decodeURIComponent(match[2]);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Keep malformed encodings for the index's standard refusal.
|
|
21
|
+
return path;
|
|
22
|
+
}
|
|
23
|
+
workflowPath(relative);
|
|
24
|
+
return match[1] + encodeURIComponent(relative);
|
|
25
|
+
}
|
|
26
|
+
// Prefer the deployed coverage for natural queries. Explicit paths and queries
|
|
27
|
+
// without reviewed matches retain full-pin refusal discovery. Metadata, contract
|
|
28
|
+
// lookup, validation and the manifest still use the complete immutable pin.
|
|
29
|
+
class RepositoryIndex extends OperationIndex {
|
|
30
|
+
resolve(method, path) {
|
|
31
|
+
if (method === "GET" && /^\/repos\/[^/]+\/[^/]+\/contents$/.test(path)) {
|
|
32
|
+
// GitHub documents omitted path as the root directory. Reuse the pinned
|
|
33
|
+
// contents contract; validate owner/repo normally, and never send this
|
|
34
|
+
// placeholder. The actual resource request keeps the /contents URL.
|
|
35
|
+
const match = super.resolve(method, `${path}/root`);
|
|
36
|
+
return { ...match, params: { ...match.params, path: "" } };
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return super.resolve(method, contentsPath(path));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (method === "GET" &&
|
|
43
|
+
error instanceof ConnectorCallError &&
|
|
44
|
+
error.code === "invalid_args" &&
|
|
45
|
+
error.message.includes(" operation in the pinned API index ") &&
|
|
46
|
+
/^\/repos\/[^/]+\/[^/]+\/(?:commits|compare)\/[^/]+\/.+/.test(path))
|
|
47
|
+
throw new ConnectorCallError("invalid_args", `${error.message} Encode '/' inside refs as %2F.`);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
contract(op, only) {
|
|
52
|
+
const contract = super.contract(op, only);
|
|
53
|
+
if (op.path === "/repos/{owner}/{repo}/contents/{path}")
|
|
54
|
+
for (const param of contract.parameters)
|
|
55
|
+
if (param.name === "path")
|
|
56
|
+
param.description =
|
|
57
|
+
"Canonical relative file or directory path; nested paths such as src/index.ts are accepted. For the root directory use /repos/{owner}/{repo}/contents without a final path.";
|
|
58
|
+
if (["/repos/{owner}/{repo}/commits/{ref}", "/repos/{owner}/{repo}/compare/{basehead}"].includes(op.path))
|
|
59
|
+
for (const param of contract.parameters)
|
|
60
|
+
if (param.name === "ref" || param.name === "basehead")
|
|
61
|
+
param.description =
|
|
62
|
+
"Repository-local ref or SHA; encode a slash inside a branch name as %2F. Compare uses BASE...HEAD. Owner-prefixed refs containing a colon are refused.";
|
|
63
|
+
return contract;
|
|
64
|
+
}
|
|
65
|
+
search(query, options) {
|
|
66
|
+
const all = super.search(query, { ...options, limit: this.ops.length });
|
|
67
|
+
if (query.trim().startsWith("/"))
|
|
68
|
+
return all.slice(0, options.limit);
|
|
69
|
+
const preferred = all.filter((op) => {
|
|
70
|
+
const key = `${op.method} ${op.path}`;
|
|
71
|
+
return READ_PERMISSIONS[key] || RELEASE_PERMISSIONS[key] || INTERNAL_READS.has(key) || key === MERGE;
|
|
72
|
+
});
|
|
73
|
+
// The shared lexical framing drops "latest". Bare release discovery
|
|
74
|
+
// therefore leads with the collection and latest read; explicit actions,
|
|
75
|
+
// method filters and paths still discover every reviewed release write.
|
|
76
|
+
const found = /^releases?$/i.test(query.trim()) && (options.method === undefined || options.method === "GET")
|
|
77
|
+
? preferred.filter((op) => op.method === "GET" &&
|
|
78
|
+
["/repos/{owner}/{repo}/releases", "/repos/{owner}/{repo}/releases/latest"].includes(op.path))
|
|
79
|
+
: preferred;
|
|
80
|
+
return (found.length ? found : all).slice(0, options.limit);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export const githubIndex = new RepositoryIndex({
|
|
84
|
+
...openapi,
|
|
85
|
+
ops: openapi.ops.map((row) => {
|
|
86
|
+
const [method, path, id, summary, tag, ...server] = row;
|
|
87
|
+
const alias = method === "PATCH" && path === "/repos/{owner}/{repo}/releases/{release_id}"
|
|
88
|
+
? " (Publish a release by updating draft to false)"
|
|
89
|
+
: path === "/repos/{owner}/{repo}/releases/latest"
|
|
90
|
+
? " (Latest published release)"
|
|
91
|
+
: path.includes("/pulls")
|
|
92
|
+
? " (PR pull request)"
|
|
93
|
+
: path.includes("/contents/")
|
|
94
|
+
? " (Read file or directory contents)"
|
|
95
|
+
: "";
|
|
96
|
+
return [method, path, id, summary + alias, tag, ...server];
|
|
97
|
+
}),
|
|
98
|
+
}, { vendor: "github", title: "GitHub", slashParams: ["path", "ref", "basehead"] });
|
|
99
|
+
for (const key of [...Object.keys(READ_PERMISSIONS), ...Object.keys(RELEASE_PERMISSIONS), ...INTERNAL_READS, MERGE]) {
|
|
100
|
+
if (!githubIndex.ops.some((op) => `${op.method} ${op.path}` === key))
|
|
101
|
+
throw new Error(`GitHub reviewed operation is absent from the pin: ${key}`);
|
|
102
|
+
}
|
|
103
|
+
export const GITHUB_VERDICTS = {
|
|
104
|
+
...Object.fromEntries(Object.keys(READ_PERMISSIONS).map((key) => [key, "read"])),
|
|
105
|
+
...Object.fromEntries(Object.keys(RELEASE_PERMISSIONS).map((key) => [key, "destructive"])),
|
|
106
|
+
[MERGE]: "refuse",
|
|
107
|
+
...Object.fromEntries([...INTERNAL_READS].map((key) => [key, "refuse"])),
|
|
108
|
+
};
|
|
109
|
+
export const GITHUB_NAMED = {
|
|
110
|
+
[MERGE]: "merge_pull_request",
|
|
111
|
+
"GET /installation/repositories": "list_scopes",
|
|
112
|
+
"GET /search/repositories": "search_scoped",
|
|
113
|
+
"GET /search/issues": "search_scoped",
|
|
114
|
+
"GET /search/code": "search_scoped",
|
|
115
|
+
};
|
|
116
|
+
const adapter = githubPage("https://api.github.com");
|
|
117
|
+
export const GITHUB_PAGES = Object.fromEntries(githubIndex.ops
|
|
118
|
+
.filter((op) => {
|
|
119
|
+
const key = `${op.method} ${op.path}`;
|
|
120
|
+
return (READ_PERMISSIONS[key] && githubIndex.contract(op).parameters.some((p) => p.in === "query" && p.name === "page"));
|
|
121
|
+
})
|
|
122
|
+
.map((op) => [`${op.method} ${op.path}`, adapter]));
|
|
123
|
+
export const GITHUB_HATCH = {
|
|
124
|
+
name: "github",
|
|
125
|
+
title: "GitHub",
|
|
126
|
+
index: githubIndex,
|
|
127
|
+
verdicts: GITHUB_VERDICTS,
|
|
128
|
+
named: GITHUB_NAMED,
|
|
129
|
+
pages: GITHUB_PAGES,
|
|
130
|
+
pageCursor: "argument",
|
|
131
|
+
pathExample: "/repos/alice/widget/issues",
|
|
132
|
+
};
|