@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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Google Workspace access as the person calling, with no per-user consent:
|
|
3
3
|
* a service account with domain-wide delegation mints a token *as* the user
|
|
4
4
|
* the deployment maps the admitted caller to. The foundation each Workspace
|
|
5
|
-
* product provider
|
|
5
|
+
* product provider builds on; never its own export.
|
|
6
6
|
*
|
|
7
7
|
* Whose account. `ConnectorContext` carries no identity, and a deployment's
|
|
8
8
|
* own connector never learns its caller (`src/connector-caller.ts`). A
|
|
@@ -25,11 +25,9 @@
|
|
|
25
25
|
* answers `undefined` for, fails `auth_required` before anything leaves the
|
|
26
26
|
* host. Catalogs need no subject: tools are static.
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* product must decide on a Google reason the shared mapping leaves generic.
|
|
32
|
-
* Nothing else here is the products' to touch.
|
|
28
|
+
* This module owns shared options, connection parsing and subject resolution.
|
|
29
|
+
* `kit.ts` builds the delegated grant, Google error mapping and request adapter
|
|
30
|
+
* on these foundations; `service-account.ts` owns token exchange and caching.
|
|
33
31
|
*
|
|
34
32
|
* Setup, once per Workspace, shared by every product:
|
|
35
33
|
*
|
|
@@ -51,17 +49,15 @@
|
|
|
51
49
|
* admitted identity to a Workspace address in `subject`. Give each pool
|
|
52
50
|
* agents use an `owner`, the address they act as there.
|
|
53
51
|
*/
|
|
54
|
-
import { guardedFetch, retryAfterMs } from "../../../connectors/guarded-fetch.js";
|
|
55
52
|
import { keys, optionsOf } from "../../../config-schema.js";
|
|
56
53
|
import { callerOf } from "../../../connector-caller.js";
|
|
57
54
|
import { CALL_ADMISSION } from "../../../connectors/option-shapes.js";
|
|
58
55
|
import { ConnectorCallError } from "../../../errors.js";
|
|
59
56
|
import { isWorkspaceAddress } from "../../../workspace-address.js";
|
|
60
|
-
import {
|
|
61
|
-
import { delegatedToken, forgetDelegatedToken, parseServiceAccount, } from "./service-account.js";
|
|
57
|
+
import { parseServiceAccount } from "./service-account.js";
|
|
62
58
|
/** The closed options every Workspace provider accepts; see `assertKnownOptions`. */
|
|
63
59
|
export const GOOGLE_WORKSPACE_OPTIONS = optionsOf()({
|
|
64
|
-
...keys("purpose", "subject", "title", "instructions", "maxResultBytes", "baseUrl"),
|
|
60
|
+
...keys("purpose", "subject", "title", "instructions", "maxResultBytes", "baseUrl", "readOnly"),
|
|
65
61
|
// A string is the key's JSON; only the object form has keys to check.
|
|
66
62
|
serviceAccount: optionsOf()(keys("clientEmail", "privateKey", "clientId")),
|
|
67
63
|
callAdmission: CALL_ADMISSION,
|
|
@@ -84,19 +80,11 @@ export function workspaceConnection(factory, options) {
|
|
|
84
80
|
}
|
|
85
81
|
return { purpose, account, subject };
|
|
86
82
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Tag a built Workspace connector with whose account it acts as, so the
|
|
89
|
-
* operator matrix can say "humans act as themselves, agents as the pool
|
|
90
|
-
* owner" for a mapping, or name a fixed subject (#837). Returns it unchanged.
|
|
91
|
-
*/
|
|
92
|
-
export function workspaceConnector(connection, connector) {
|
|
93
|
-
return tagWorkspaceSubject(connector, connection.subject);
|
|
94
|
-
}
|
|
95
83
|
/**
|
|
96
84
|
* The Workspace address this call acts as, resolved from config and the
|
|
97
85
|
* admitted caller only. Throws before any network call when there is none.
|
|
98
86
|
*/
|
|
99
|
-
async function subjectFor(provider, subject, ctx) {
|
|
87
|
+
export async function subjectFor(provider, subject, ctx) {
|
|
100
88
|
if (typeof subject === "string")
|
|
101
89
|
return subject;
|
|
102
90
|
const caller = callerOf(ctx);
|
|
@@ -135,410 +123,3 @@ async function subjectFor(provider, subject, ctx) {
|
|
|
135
123
|
}
|
|
136
124
|
return mapped.trim();
|
|
137
125
|
}
|
|
138
|
-
// --- API failures ------------------------------------------------------------------
|
|
139
|
-
function asRecord(value) {
|
|
140
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
141
|
-
}
|
|
142
|
-
function asArray(value) {
|
|
143
|
-
return Array.isArray(value) ? value : [];
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* The machine-readable reasons in a Google API error body: the legacy
|
|
147
|
-
* `errors[].reason`, the AIP-193 `details[].reason`, and the canonical
|
|
148
|
-
* `status`. Classification reads these and nothing else; `message` is prose
|
|
149
|
-
* for the reader.
|
|
150
|
-
*/
|
|
151
|
-
function reasonsOf(error) {
|
|
152
|
-
const reasons = new Set();
|
|
153
|
-
for (const entry of [...asArray(error["errors"]), ...asArray(error["details"])]) {
|
|
154
|
-
const reason = asRecord(entry)["reason"];
|
|
155
|
-
if (typeof reason === "string")
|
|
156
|
-
reasons.add(reason);
|
|
157
|
-
}
|
|
158
|
-
if (typeof error["status"] === "string")
|
|
159
|
-
reasons.add(error["status"]);
|
|
160
|
-
return reasons;
|
|
161
|
-
}
|
|
162
|
-
/** The canonical and legacy spellings of a refused precondition. */
|
|
163
|
-
const PRECONDITION_REASONS = ["FAILED_PRECONDITION", "failedPrecondition"];
|
|
164
|
-
/** Concurrency aborts, which a revision-checked write also answers with. */
|
|
165
|
-
const ABORTED_REASONS = ["ABORTED", "aborted"];
|
|
166
|
-
/** A reason token as Google spells one; anything else is not passed on. */
|
|
167
|
-
const REASON_TOKEN = /^[A-Za-z0-9_.]{1,64}$/;
|
|
168
|
-
/** More than this many reasons on one error is noise, not information. */
|
|
169
|
-
const MAX_REASONS = 8;
|
|
170
|
-
/** Google's reasons for each failure this layer mapped, beside the error. */
|
|
171
|
-
const googleReasons = new WeakMap();
|
|
172
|
-
/**
|
|
173
|
-
* Google's machine-readable reasons for a failure the shared client mapped:
|
|
174
|
-
* `errors[].reason`, `details[].reason`, and the canonical `status`, in that
|
|
175
|
-
* order, deduplicated, at most eight, and only tokens of `[A-Za-z0-9_.]` up to
|
|
176
|
-
* 64 characters — never a message or any other part of the payload. An empty
|
|
177
|
-
* array for anything else, including a failure that never reached Google.
|
|
178
|
-
*
|
|
179
|
-
* For a product that needs a decision the shared mapping does not make, such
|
|
180
|
-
* as Drive telling `cannotDownloadAbusiveFile` from another refusal. Classify
|
|
181
|
-
* on these, never on the error's message.
|
|
182
|
-
*/
|
|
183
|
-
export function googleReasonsOf(error) {
|
|
184
|
-
return error instanceof ConnectorCallError ? (googleReasons.get(error) ?? []) : [];
|
|
185
|
-
}
|
|
186
|
-
const googleOutcomes = new WeakMap();
|
|
187
|
-
function outcome(error, facts) {
|
|
188
|
-
if (error !== null && typeof error === "object")
|
|
189
|
-
googleOutcomes.set(error, Object.freeze({ ...facts }));
|
|
190
|
-
return error;
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* How far the request behind a failure from `json`, `bytes`, or `text` got —
|
|
194
|
-
* see {@link GoogleOutcome}. `undefined` for anything the client did not
|
|
195
|
-
* throw. Read it beside the error, the way `googleReasonsOf` is read; the
|
|
196
|
-
* error itself already carries the right retry verdict.
|
|
197
|
-
*/
|
|
198
|
-
export function googleOutcomeOf(error) {
|
|
199
|
-
return error !== null && typeof error === "object" ? googleOutcomes.get(error) : undefined;
|
|
200
|
-
}
|
|
201
|
-
function apiFailure(context, status, headers, payload, options) {
|
|
202
|
-
const failure = classifyApiFailure(context, status, headers, payload, options);
|
|
203
|
-
const error = asRecord(asRecord(payload)["error"]);
|
|
204
|
-
const reasons = [...reasonsOf(error)].filter((reason) => REASON_TOKEN.test(reason)).slice(0, MAX_REASONS);
|
|
205
|
-
googleReasons.set(failure, Object.freeze(reasons));
|
|
206
|
-
return failure;
|
|
207
|
-
}
|
|
208
|
-
function classifyApiFailure(context, status, headers, payload, options) {
|
|
209
|
-
const { provider, api, scopes } = context;
|
|
210
|
-
const error = asRecord(asRecord(payload)["error"]);
|
|
211
|
-
const message = typeof error["message"] === "string" ? error["message"].trim().slice(0, 500) : "";
|
|
212
|
-
const detail = message ? `${provider}: ${message}` : `${provider} returned HTTP ${status}.`;
|
|
213
|
-
const reasons = reasonsOf(error);
|
|
214
|
-
const precondition = PRECONDITION_REASONS.some((reason) => reasons.has(reason));
|
|
215
|
-
// Order matters. Google's own specific reasons decide first, whatever the
|
|
216
|
-
// status and whatever the request asked for: a guarded write refused
|
|
217
|
-
// because the API is off, or because of a quota, must not read as "re-read
|
|
218
|
-
// and retry". Only a refused precondition or an abort with nothing more
|
|
219
|
-
// specific to say is left for the revision guard, then for the status.
|
|
220
|
-
if (status === 401) {
|
|
221
|
-
return new ConnectorCallError("auth_required", `${detail} Google rejected a freshly minted delegated token. The domain-wide delegation grant or the service account may have been revoked.`);
|
|
222
|
-
}
|
|
223
|
-
// The API is off in the service account's project.
|
|
224
|
-
if (reasons.has("accessNotConfigured") || reasons.has("SERVICE_DISABLED")) {
|
|
225
|
-
return new ConnectorCallError("connector_call_failed", `${detail} The ${api} is not enabled in the service account's Google Cloud project; an operator enables it there.`, { retryable: false });
|
|
226
|
-
}
|
|
227
|
-
if (status === 429 ||
|
|
228
|
-
reasons.has("rateLimitExceeded") ||
|
|
229
|
-
reasons.has("userRateLimitExceeded") ||
|
|
230
|
-
reasons.has("RATE_LIMIT_EXCEEDED")) {
|
|
231
|
-
const wait = retryAfterMs(headers);
|
|
232
|
-
return new ConnectorCallError("rate_limited", `${detail} Google meters this API per user and per project; wait before retrying.`, wait === undefined ? {} : { retryAfterMs: wait });
|
|
233
|
-
}
|
|
234
|
-
if (reasons.has("insufficientPermissions") || reasons.has("ACCESS_TOKEN_SCOPE_INSUFFICIENT")) {
|
|
235
|
-
return new ConnectorCallError("provider_permission_denied", `${detail} The delegated token lacks a scope this call needs. The Admin console's domain-wide delegation entry must list exactly: ${scopes.join(",")}.`);
|
|
236
|
-
}
|
|
237
|
-
// Where Google names the refusal precisely, so does the message.
|
|
238
|
-
if (reasons.has("exportSizeLimitExceeded")) {
|
|
239
|
-
return new ConnectorCallError("connector_call_failed", `${detail} The item is larger than Google will export in this format. Download the original file instead, or export a smaller part.`, { retryable: false });
|
|
240
|
-
}
|
|
241
|
-
if (reasons.has("domainPolicy")) {
|
|
242
|
-
return new ConnectorCallError("provider_permission_denied", `${detail} A Workspace domain policy forbids this action for this account; only a Workspace administrator can change that.`, { retryable: false });
|
|
243
|
-
}
|
|
244
|
-
if (reasons.has("insufficientFilePermissions") || reasons.has("forbidden")) {
|
|
245
|
-
return new ConnectorCallError("provider_permission_denied", `${detail} This account does not have the permission on this item that the action needs — it may be shared view-only, or not shared with this user. Its owner can grant more.`, { retryable: false });
|
|
246
|
-
}
|
|
247
|
-
// A write that named the revision it was made against hears "the item moved
|
|
248
|
-
// on" as a refused precondition or an abort. Fixed words, so no provider
|
|
249
|
-
// ever matches on Google's prose or on this file's.
|
|
250
|
-
if (options.revisionGuarded === true &&
|
|
251
|
-
(status === 400 || status === 409) &&
|
|
252
|
-
(precondition || ABORTED_REASONS.some((reason) => reasons.has(reason)))) {
|
|
253
|
-
return new ConnectorCallError("conflict", `${provider} refused the write because the item changed after the revision it named. Re-read it for the current revision, reapply the change, and retry.`);
|
|
254
|
-
}
|
|
255
|
-
if (status === 403) {
|
|
256
|
-
return new ConnectorCallError("provider_permission_denied", `${detail} Google refused the request; the account may lack access to this item, or a Workspace policy may block it. Google does not say which. Ask the item owner to grant access or a Workspace administrator to review the policy.`, { retryable: false });
|
|
257
|
-
}
|
|
258
|
-
if (status === 404) {
|
|
259
|
-
return context.notFound === "absent"
|
|
260
|
-
? new ConnectorCallError("not_found", `${detail} Re-read the id from a list or search tool.`)
|
|
261
|
-
: new ConnectorCallError("connector_call_failed", `${detail} The id is unknown or not visible to this account; Google does not distinguish them.`, { retryable: false });
|
|
262
|
-
}
|
|
263
|
-
// Google uses the same reason for a product switched off for this user
|
|
264
|
-
// (Gmail's "Mail service not enabled") and for a stale write, so the
|
|
265
|
-
// classification asserts neither; Google's own message says which.
|
|
266
|
-
if (status === 400 && precondition) {
|
|
267
|
-
return new ConnectorCallError("connector_call_failed", `${detail} Google refused the request's precondition; its message says which.`, { retryable: false });
|
|
268
|
-
}
|
|
269
|
-
if (status === 400 || status === 422) {
|
|
270
|
-
return new ConnectorCallError("invalid_args", detail);
|
|
271
|
-
}
|
|
272
|
-
if (status >= 500) {
|
|
273
|
-
const wait = retryAfterMs(headers);
|
|
274
|
-
return new ConnectorCallError("unavailable", `${detail} Google is failing upstream.`, wait === undefined ? {} : { retryAfterMs: wait });
|
|
275
|
-
}
|
|
276
|
-
return new ConnectorCallError("connector_call_failed", detail, { retryable: false });
|
|
277
|
-
}
|
|
278
|
-
/** The token the API refused, carried out of the mapper for one replay. */
|
|
279
|
-
class TokenRejected extends Error {
|
|
280
|
-
failure;
|
|
281
|
-
constructor(failure) {
|
|
282
|
-
super(failure.message);
|
|
283
|
-
this.failure = failure;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
/** A Range request against an empty file: Google's verified "nothing here". */
|
|
287
|
-
function isEmptyRange(response) {
|
|
288
|
-
return response.status === 416 && /^bytes \*\/0$/i.test(response.headers.get("content-range")?.trim() ?? "");
|
|
289
|
-
}
|
|
290
|
-
function charsetOf(contentType) {
|
|
291
|
-
return /charset="?([^";\s]+)"?/i.exec(contentType ?? "")?.[1] ?? "utf-8";
|
|
292
|
-
}
|
|
293
|
-
/** Build the delegated, confined, Google-error-mapped transport for one product. */
|
|
294
|
-
export function googleWorkspaceClient(options) {
|
|
295
|
-
const { provider, scopes, connection } = options;
|
|
296
|
-
const failure = {
|
|
297
|
-
provider,
|
|
298
|
-
api: options.api,
|
|
299
|
-
scopes,
|
|
300
|
-
notFound: options.notFound,
|
|
301
|
-
};
|
|
302
|
-
// One resolution per call context: a tool that sends several requests acts
|
|
303
|
-
// as one account throughout, and the mapping runs once rather than per
|
|
304
|
-
// request. The context dies with its call, and so does the entry.
|
|
305
|
-
const subjects = new WeakMap();
|
|
306
|
-
const tokenRequest = async (ctx) => {
|
|
307
|
-
let subject = subjects.get(ctx);
|
|
308
|
-
if (!subject) {
|
|
309
|
-
subject = subjectFor(provider, connection.subject, ctx);
|
|
310
|
-
subjects.set(ctx, subject);
|
|
311
|
-
}
|
|
312
|
-
return { account: connection.account, subject: await subject, scopes };
|
|
313
|
-
};
|
|
314
|
-
// The transport is bound to one token, so the request that a 401 answers
|
|
315
|
-
// names exactly the token to forget. Building one validates the base URL
|
|
316
|
-
// and ceiling at construction, where a structural mistake belongs.
|
|
317
|
-
const transport = (token, facts = { dispatched: false }) => guardedFetch({
|
|
318
|
-
provider,
|
|
319
|
-
baseUrl: options.baseUrl,
|
|
320
|
-
maxResponseBytes: options.maxResponseBytes,
|
|
321
|
-
authenticate: () => ({ Authorization: `Bearer ${token}` }),
|
|
322
|
-
// The one place a request leaves and an answer arrives, recorded as
|
|
323
|
-
// they happen so a failure can say how far the request got.
|
|
324
|
-
fetch: async (url, init) => {
|
|
325
|
-
facts.dispatched = true;
|
|
326
|
-
const response = await fetch(url, init);
|
|
327
|
-
// Recorded before the transport validates anything: a ceiling or a
|
|
328
|
-
// redirect check can fail an answer, but not change what it said.
|
|
329
|
-
facts.status = response.status;
|
|
330
|
-
facts.headers = response.headers;
|
|
331
|
-
return response;
|
|
332
|
-
},
|
|
333
|
-
});
|
|
334
|
-
transport("");
|
|
335
|
-
/**
|
|
336
|
-
* What a failure means for a request that may have changed something. A
|
|
337
|
-
* write Google accepted, or one that left with no answer, may have landed:
|
|
338
|
-
* telling the agent to retry it invites a duplicate, so it is reported as
|
|
339
|
-
* an uncertain outcome that is not retried — the same verdict core reaches
|
|
340
|
-
* for a program write (`write_outcome_unknown`). A read is safe to
|
|
341
|
-
* repeat and keeps its retryable classification.
|
|
342
|
-
*/
|
|
343
|
-
/**
|
|
344
|
-
* A 5xx answering a write that is not idempotent. Google can fail after a
|
|
345
|
-
* change commits, so "unavailable, retry" would invite a duplicate insert
|
|
346
|
-
* or a second draft: the outcome is unknown and the write is not retried,
|
|
347
|
-
* whatever reason the 5xx names. Its reasons stay readable beside it.
|
|
348
|
-
*/
|
|
349
|
-
function serverErrorOnWrite(mapped, status, method, requestOptions) {
|
|
350
|
-
// Any 5xx, whatever reason it names: a quota reason on a 503 does not
|
|
351
|
-
// prove the write was turned away before it committed. Only a 429, or a
|
|
352
|
-
// 4xx naming a quota, is a rejection that applied nothing.
|
|
353
|
-
if (status < 500 || method === "GET" || requestOptions.idempotent === true) {
|
|
354
|
-
return undefined;
|
|
355
|
-
}
|
|
356
|
-
const unknown = new ConnectorCallError("connector_call_failed", `${provider} answered HTTP ${status} after receiving the request, so its outcome is unknown — it may have been applied. Re-read its target before repeating it.`, { retryable: false, cause: mapped });
|
|
357
|
-
const reasons = googleReasons.get(mapped);
|
|
358
|
-
if (reasons)
|
|
359
|
-
googleReasons.set(unknown, reasons);
|
|
360
|
-
return unknown;
|
|
361
|
-
}
|
|
362
|
-
// The errors built below keep no cause: a stream's or parser's error can
|
|
363
|
-
// quote the body it failed on, and the phase and status already say what
|
|
364
|
-
// happened (#695).
|
|
365
|
-
function settled(error, facts, method, ctx, requestOptions) {
|
|
366
|
-
const write = method !== "GET";
|
|
367
|
-
const status = facts.status;
|
|
368
|
-
if (facts.serverError) {
|
|
369
|
-
return outcome(error, { dispatched: true, phase: "server-error", ...(status === undefined ? {} : { status }) });
|
|
370
|
-
}
|
|
371
|
-
if (facts.refused) {
|
|
372
|
-
return outcome(error, { dispatched: true, phase: "refused", ...(status === undefined ? {} : { status }) });
|
|
373
|
-
}
|
|
374
|
-
// An error status keeps its meaning whatever went wrong reading its body
|
|
375
|
-
// — a body past the ceiling, a stream that broke off: the status alone
|
|
376
|
-
// still says what to do next.
|
|
377
|
-
if (status !== undefined && status >= 400 && !ctx.signal?.aborted) {
|
|
378
|
-
const mapped = apiFailure(failure, status, facts.headers ?? new Headers(), undefined, requestOptions);
|
|
379
|
-
const unknown = serverErrorOnWrite(mapped, status, method, requestOptions);
|
|
380
|
-
return unknown
|
|
381
|
-
? outcome(unknown, { dispatched: true, status, phase: "server-error" })
|
|
382
|
-
: outcome(mapped, { dispatched: true, status, phase: "refused" });
|
|
383
|
-
}
|
|
384
|
-
if (!facts.dispatched)
|
|
385
|
-
return outcome(error, { dispatched: false, phase: "before-send" });
|
|
386
|
-
// The caller's own cancellation is core's to classify, not this layer's —
|
|
387
|
-
// but what Google had already said by then is still what it said.
|
|
388
|
-
if (ctx.signal?.aborted) {
|
|
389
|
-
const phase = status === undefined
|
|
390
|
-
? "awaiting-response"
|
|
391
|
-
: status >= 500 && write && requestOptions.idempotent !== true
|
|
392
|
-
? "server-error"
|
|
393
|
-
: status >= 400
|
|
394
|
-
? "refused"
|
|
395
|
-
: status >= 300
|
|
396
|
-
? "redirected"
|
|
397
|
-
: "reading-body";
|
|
398
|
-
return outcome(error, { dispatched: true, phase, ...(status === undefined ? {} : { status }) });
|
|
399
|
-
}
|
|
400
|
-
if (status === undefined) {
|
|
401
|
-
const mapped = write
|
|
402
|
-
? new ConnectorCallError("connector_call_failed", `${provider} was sent the request but no answer came back, so it may or may not have been applied. Re-read its target before repeating it.`, { retryable: false })
|
|
403
|
-
: error;
|
|
404
|
-
return outcome(mapped, { dispatched: true, phase: "awaiting-response" });
|
|
405
|
-
}
|
|
406
|
-
// A redirect, which the transport refuses to follow. It shows neither
|
|
407
|
-
// that a write applied nor that it did not, so a write's outcome is
|
|
408
|
-
// unknown — never "probably applied". A read keeps the transport's own
|
|
409
|
-
// refusal.
|
|
410
|
-
if (status >= 300 && status < 400) {
|
|
411
|
-
const redirected = write
|
|
412
|
-
? new ConnectorCallError("connector_call_failed", `${provider} answered HTTP ${status} with a redirect, which this connection does not follow, so whether the request was applied is unknown. Re-read its target before repeating it.`, { retryable: false })
|
|
413
|
-
: error;
|
|
414
|
-
return outcome(redirected, { dispatched: true, status, phase: "redirected" });
|
|
415
|
-
}
|
|
416
|
-
// Google accepted the request — a 2xx — and its reply broke off,
|
|
417
|
-
// overflowed, or would not parse.
|
|
418
|
-
const mapped = write
|
|
419
|
-
? new ConnectorCallError("connector_call_failed", `${provider} accepted the request but its reply could not be read; the change probably applied. Re-read before repeating it.`, { retryable: false })
|
|
420
|
-
: error instanceof ConnectorCallError
|
|
421
|
-
? error
|
|
422
|
-
: new ConnectorCallError("unavailable", `${provider}'s reply broke off while it was being read; reading again is safe.`);
|
|
423
|
-
return outcome(mapped, { dispatched: true, status, phase: "reading-body" });
|
|
424
|
-
}
|
|
425
|
-
async function call(request, ctx, accept, read, requestOptions = {}) {
|
|
426
|
-
// A 401 is answered by sending the same request again, so its body must
|
|
427
|
-
// survive being sent twice. A stream is consumed by the first send; it
|
|
428
|
-
// is a provider's wiring mistake, refused before anything leaves.
|
|
429
|
-
if (typeof ReadableStream !== "undefined" && request.rawBody instanceof ReadableStream) {
|
|
430
|
-
throw outcome(new TypeError(`${provider}: a Workspace request body must be replayable — pass a string, Uint8Array, ArrayBuffer, Blob, FormData, or URLSearchParams, never a ReadableStream.`), { dispatched: false, phase: "before-send" });
|
|
431
|
-
}
|
|
432
|
-
const headed = { ...request, headers: { Accept: accept, ...request.headers } };
|
|
433
|
-
for (let attempt = 0;; attempt += 1) {
|
|
434
|
-
const facts = { dispatched: false };
|
|
435
|
-
let delegated;
|
|
436
|
-
let token;
|
|
437
|
-
try {
|
|
438
|
-
// Subject first: no caller, or no mapped account, fails before any
|
|
439
|
-
// token is minted or request sent.
|
|
440
|
-
delegated = await tokenRequest(ctx);
|
|
441
|
-
token = await delegatedToken(delegated, ctx);
|
|
442
|
-
}
|
|
443
|
-
catch (error) {
|
|
444
|
-
throw outcome(error, { dispatched: false, phase: "before-send" });
|
|
445
|
-
}
|
|
446
|
-
try {
|
|
447
|
-
return await transport(token, facts)(headed, ctx, async (response) => {
|
|
448
|
-
// Every 3xx — the ones the transport refuses and the 300, 304, 305,
|
|
449
|
-
// and 306 it lets through — is a redirect before it is anything
|
|
450
|
-
// else: no body or reason it carries makes it a refusal.
|
|
451
|
-
if (response.status >= 300 && response.status < 400) {
|
|
452
|
-
throw new ConnectorCallError("connector_call_failed", `${provider} answered HTTP ${response.status} with a redirect; this connection talks to exactly one origin and never forwards its credential to another.`, { retryable: false });
|
|
453
|
-
}
|
|
454
|
-
// A bounded read answers an empty file's 416 as an empty result.
|
|
455
|
-
if (!response.ok && !(headed.prefixOnly === true && isEmptyRange(response))) {
|
|
456
|
-
const parsed = await response.jsonResult();
|
|
457
|
-
const payload = "value" in parsed ? parsed.value : undefined;
|
|
458
|
-
const mapped = apiFailure(failure, response.status, response.headers, payload, requestOptions);
|
|
459
|
-
const unknown = serverErrorOnWrite(mapped, response.status, headed.method, requestOptions);
|
|
460
|
-
if (unknown) {
|
|
461
|
-
facts.serverError = true;
|
|
462
|
-
throw unknown;
|
|
463
|
-
}
|
|
464
|
-
facts.refused = true;
|
|
465
|
-
throw response.status === 401 ? new TokenRejected(mapped) : mapped;
|
|
466
|
-
}
|
|
467
|
-
return await read(response);
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
catch (error) {
|
|
471
|
-
// Every 401 Google sent earns the refresh and the one replay, whether
|
|
472
|
-
// its body was read or failed the transport's checks first: what the
|
|
473
|
-
// status said about the token does not depend on the body.
|
|
474
|
-
// A caller that has left gets neither: the 401 it saw is reported as
|
|
475
|
-
// it was, and no new token is minted for a request nobody awaits.
|
|
476
|
-
const failed = error instanceof TokenRejected ? error.failure : error;
|
|
477
|
-
const unauthorized = !ctx.signal?.aborted && (error instanceof TokenRejected || facts.status === 401);
|
|
478
|
-
if (!unauthorized)
|
|
479
|
-
throw settled(failed, facts, headed.method, ctx, requestOptions);
|
|
480
|
-
// Only the token this request carried; a newer one stays.
|
|
481
|
-
await forgetDelegatedToken(delegated, token);
|
|
482
|
-
if (attempt > 0 || ctx.signal?.aborted) {
|
|
483
|
-
throw settled(failed, facts, headed.method, ctx, requestOptions);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
return {
|
|
489
|
-
json: (request, ctx, requestOptions) => call(request, ctx, "application/json", async (response) => {
|
|
490
|
-
// Read first, parse second: a body that breaks off while it is being
|
|
491
|
-
// read is a transport failure (a GET may read again), and only text
|
|
492
|
-
// that arrived whole and still is not JSON is malformed.
|
|
493
|
-
const body = await response.text();
|
|
494
|
-
if (body.trim() === "")
|
|
495
|
-
return undefined;
|
|
496
|
-
try {
|
|
497
|
-
return JSON.parse(body);
|
|
498
|
-
}
|
|
499
|
-
catch {
|
|
500
|
-
throw new ConnectorCallError("connector_call_failed", `${provider} returned a successful response that is not JSON.`, { retryable: false });
|
|
501
|
-
}
|
|
502
|
-
}, requestOptions),
|
|
503
|
-
bytes: (request, ctx, accept = "*/*", requestOptions) => read(request, ctx, accept, requestOptions),
|
|
504
|
-
text: async (request, ctx, accept = "text/plain, */*", requestOptions) => {
|
|
505
|
-
const { bytes, ...rest } = await read(request, ctx, accept, requestOptions);
|
|
506
|
-
let text;
|
|
507
|
-
try {
|
|
508
|
-
// A cut prefix may end inside a code point; streaming decode drops
|
|
509
|
-
// the incomplete tail instead of inventing a replacement character.
|
|
510
|
-
text = new TextDecoder(charsetOf(rest.contentType)).decode(bytes, { stream: rest.truncated });
|
|
511
|
-
}
|
|
512
|
-
catch {
|
|
513
|
-
text = new TextDecoder().decode(bytes, { stream: rest.truncated });
|
|
514
|
-
}
|
|
515
|
-
return { text, ...rest };
|
|
516
|
-
},
|
|
517
|
-
};
|
|
518
|
-
/** The body as bytes: whole, or a bounded prefix when `maxBytes` is set. */
|
|
519
|
-
async function read(request, ctx, accept, requestOptions = {}) {
|
|
520
|
-
const { maxBytes } = requestOptions;
|
|
521
|
-
if (maxBytes !== undefined && (!Number.isInteger(maxBytes) || maxBytes < 0)) {
|
|
522
|
-
throw outcome(new TypeError(`${provider}: maxBytes must be a whole number of bytes, zero or more.`), {
|
|
523
|
-
dispatched: false,
|
|
524
|
-
phase: "before-send",
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
const bounded = maxBytes !== undefined;
|
|
528
|
-
return call(bounded ? { ...request, prefixOnly: true } : request, ctx, accept, async (response) => {
|
|
529
|
-
const facts = {
|
|
530
|
-
contentType: response.headers.get("content-type") ?? undefined,
|
|
531
|
-
status: response.status,
|
|
532
|
-
contentRange: response.headers.get("content-range") ?? undefined,
|
|
533
|
-
};
|
|
534
|
-
if (!bounded)
|
|
535
|
-
return { bytes: await response.bytes(), truncated: false, ...facts };
|
|
536
|
-
if (!response.ok) {
|
|
537
|
-
// Only the empty-file 416 reaches here; its body is not the file.
|
|
538
|
-
await response.prefix(0);
|
|
539
|
-
return { bytes: new Uint8Array(), truncated: false, ...facts };
|
|
540
|
-
}
|
|
541
|
-
return { ...(await response.prefix(maxBytes)), ...facts };
|
|
542
|
-
}, requestOptions);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Physical decode ceiling, independent of request/schema/discovery budgets.
|
|
2
|
+
const MAX_TEXT = 64 * 1024 * 1024;
|
|
3
|
+
const parsers = new WeakMap();
|
|
4
|
+
function limit(length) {
|
|
5
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > MAX_TEXT)
|
|
6
|
+
throw new Error("Invalid compact index length.");
|
|
7
|
+
}
|
|
8
|
+
function rowText(packed, row) {
|
|
9
|
+
if (row === 0)
|
|
10
|
+
return "0";
|
|
11
|
+
const [ids, body] = row;
|
|
12
|
+
let length = body.length + 5; // [[params],body]
|
|
13
|
+
limit(length);
|
|
14
|
+
const parameters = [];
|
|
15
|
+
for (const id of ids) {
|
|
16
|
+
if (!Number.isSafeInteger(id) || id < 0 || typeof packed.parameters[id] !== "string")
|
|
17
|
+
throw new Error("Invalid compact index parameter reference.");
|
|
18
|
+
const text = packed.parameters[id];
|
|
19
|
+
length += text.length + (parameters.length ? 1 : 0);
|
|
20
|
+
limit(length);
|
|
21
|
+
parameters.push(text);
|
|
22
|
+
}
|
|
23
|
+
return `[[${parameters.join(",")}],${body}]`;
|
|
24
|
+
}
|
|
25
|
+
/** Recreate the exact old JSON string, for semantic identity/cursor binding. */
|
|
26
|
+
export function expandIndexDetails(packed) {
|
|
27
|
+
limit(packed.length);
|
|
28
|
+
let length = packed.definitions.length + 13; // {"d":defs,"o":[]}
|
|
29
|
+
limit(length);
|
|
30
|
+
const rows = [];
|
|
31
|
+
for (const row of packed.operations) {
|
|
32
|
+
const text = rowText(packed, row);
|
|
33
|
+
length += text.length + (rows.length ? 1 : 0);
|
|
34
|
+
if (length > packed.length)
|
|
35
|
+
throw new Error("Compact index length exceeded.");
|
|
36
|
+
rows.push(text);
|
|
37
|
+
}
|
|
38
|
+
if (length !== packed.length)
|
|
39
|
+
throw new Error("Compact index length mismatch.");
|
|
40
|
+
return `{"d":${packed.definitions},"o":[${rows.join(",")}]}`;
|
|
41
|
+
}
|
|
42
|
+
/** Preserve OpenApiData's logical shape; decode no schema during construction. */
|
|
43
|
+
export function compactOpenApi(metadata, packed) {
|
|
44
|
+
const { fields, requestSchemas, ...base } = metadata;
|
|
45
|
+
const data = base;
|
|
46
|
+
const get = () => expandIndexDetails(packed);
|
|
47
|
+
Object.defineProperty(data, "details", {
|
|
48
|
+
enumerable: true,
|
|
49
|
+
configurable: true,
|
|
50
|
+
get,
|
|
51
|
+
set(value) {
|
|
52
|
+
Object.defineProperty(data, "details", { value, writable: true, enumerable: true, configurable: true });
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
if (Object.hasOwn(metadata, "fields"))
|
|
56
|
+
Object.assign(data, { fields });
|
|
57
|
+
if (Object.hasOwn(metadata, "requestSchemas"))
|
|
58
|
+
Object.assign(data, { requestSchemas });
|
|
59
|
+
parsers.set(data, () => {
|
|
60
|
+
// Direct callers may replace the logical property before construction.
|
|
61
|
+
if (Object.getOwnPropertyDescriptor(data, "details")?.get !== get)
|
|
62
|
+
return JSON.parse(data.details);
|
|
63
|
+
limit(packed.length);
|
|
64
|
+
limit(packed.definitions.length);
|
|
65
|
+
const o = [];
|
|
66
|
+
for (let row = 0; row < packed.operations.length; row++) {
|
|
67
|
+
Object.defineProperty(o, row, {
|
|
68
|
+
enumerable: true,
|
|
69
|
+
configurable: true,
|
|
70
|
+
get() {
|
|
71
|
+
// A separate parse for each occurrence preserves the old object
|
|
72
|
+
// identity/mutation behaviour even when parameter text is shared.
|
|
73
|
+
const value = JSON.parse(rowText(packed, packed.operations[row]));
|
|
74
|
+
Object.defineProperty(o, row, { value, writable: true, enumerable: true, configurable: true });
|
|
75
|
+
return value;
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return { d: JSON.parse(packed.definitions), o };
|
|
80
|
+
});
|
|
81
|
+
return data;
|
|
82
|
+
}
|
|
83
|
+
/** Each OperationIndex owns its parsed values; kit indexes keep JSON.parse. */
|
|
84
|
+
export function parseIndexDetails(data) {
|
|
85
|
+
return parsers.get(data)?.() ?? JSON.parse(data.details);
|
|
86
|
+
}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
// Auth selects the implementation (decisions/0005): a `"dual"` provider sends
|
|
2
|
-
// OAuth to the vendor's hosted MCP server and a key to Connecta's own REST
|
|
3
|
-
// connector. The choice is made once, at construction, from `auth.type`; one
|
|
4
|
-
// connector id is one implementation, and a failure on one is never replayed
|
|
5
|
-
// against the other (INV-9).
|
|
6
|
-
import { remoteMcp } from "../../../connectors/remote-mcp.js";
|
|
7
1
|
/**
|
|
8
2
|
* A provider `create` that dispatches on `options.auth.type`. Pair it with
|
|
9
3
|
* `variants(["auth", "type"], …)` options, which refuse a missing or unknown
|
|
@@ -15,27 +9,13 @@ export function byAuth(cases) {
|
|
|
15
9
|
const create = typeof type === "string" && Object.hasOwn(cases, type) ? cases[type] : undefined;
|
|
16
10
|
if (!create) {
|
|
17
11
|
throw new Error(`auth.type must be one of ${Object.keys(cases)
|
|
12
|
+
.filter((name) => cases[name] !== removedHostedMcp)
|
|
18
13
|
.map((name) => JSON.stringify(name))
|
|
19
14
|
.join(", ")}.`);
|
|
20
15
|
}
|
|
21
16
|
return create(id, options, provider);
|
|
22
17
|
};
|
|
23
18
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* operator credential reaches a hosted endpoint. The provider's reviewed
|
|
27
|
-
* classification classifies its catalog.
|
|
28
|
-
*/
|
|
29
|
-
export function hostedOAuth(id, provider, options) {
|
|
30
|
-
return remoteMcp(id, {
|
|
31
|
-
url: options.url,
|
|
32
|
-
...provider.connectorOptions,
|
|
33
|
-
title: options.title,
|
|
34
|
-
description: options.description,
|
|
35
|
-
auth: { type: "oauth" },
|
|
36
|
-
requireHttps: true,
|
|
37
|
-
classify: provider.classify,
|
|
38
|
-
...(options.callAdmission ? { callAdmission: options.callAdmission } : {}),
|
|
39
|
-
usageGuide: options.usageGuide,
|
|
40
|
-
});
|
|
19
|
+
export function removedHostedMcp() {
|
|
20
|
+
throw new Error("remoteMcp() with ConnectaConfig.classification for hosted MCP connections; use a distinct id beside the API connector.");
|
|
41
21
|
}
|