@zackbart/connecta 0.34.1 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +363 -0
- package/README.md +62 -106
- package/SECURITY.md +3 -2
- package/bin/connecta.mjs +41 -16
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +2 -1
- package/dist/access-tokens.js +66 -30
- package/dist/activity.d.ts +1 -1
- package/dist/activity.js +2 -0
- package/dist/agent-config.d.ts +23 -0
- package/dist/agent-config.js +59 -0
- package/dist/agent-description.js +16 -0
- package/dist/agent-identity.js +4 -4
- package/dist/agent-observation.js +39 -0
- package/dist/auth/clerk-sdk/client.d.ts +2 -0
- package/dist/auth/clerk-sdk/generated.js +554 -563
- package/dist/auth/clerk-transport.js +70 -3
- package/dist/auth/clerk.d.ts +13 -0
- package/dist/auth/clerk.js +653 -246
- package/dist/auth/cloudflare-access.js +46 -42
- package/dist/auth/downstream-client-metadata.js +2 -2
- package/dist/auth/downstream-oauth.js +2051 -1160
- package/dist/auth/downstream-revocation.js +88 -88
- package/dist/auth/native-call.js +20 -0
- package/dist/auth/native.js +184 -0
- package/dist/auth/oauth-start-reset.js +11 -5
- package/dist/auth/oauth-token-response.js +121 -102
- package/dist/auth/oauth-v2-migration.js +97 -78
- package/dist/auth/renewable-grant.js +10 -0
- package/dist/auth/static-oauth.js +663 -271
- package/dist/auth/vercel-agent.js +266 -0
- package/dist/auth-admission.js +204 -0
- package/dist/auth-elicitation.js +350 -241
- package/dist/authorization-handoff.js +164 -90
- package/dist/call-admission-context.js +3 -10
- package/dist/call-admission.js +2 -0
- package/dist/call-classification.js +9 -0
- package/dist/call-recovery.js +32 -13
- package/dist/canonical-json.js +8 -0
- package/dist/catalog-cache.js +369 -213
- package/dist/catalog-service.js +659 -530
- package/dist/catalog.js +5 -1
- package/dist/concurrency.js +0 -25
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +3 -1
- package/dist/config-schema.js +13 -3
- package/dist/config.d.ts +40 -6
- package/dist/config.js +40 -26
- package/dist/connector-caller.d.ts +4 -0
- package/dist/connector-caller.js +17 -4
- package/dist/connectors/api-connector.d.ts +12 -7
- package/dist/connectors/api-connector.js +142 -56
- package/dist/connectors/guarded-fetch.d.ts +51 -17
- package/dist/connectors/guarded-fetch.js +215 -121
- package/dist/connectors/negotiation-cache.js +20 -10
- package/dist/connectors/remote-mcp.d.ts +10 -30
- package/dist/connectors/remote-mcp.js +955 -563
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +3 -1
- package/dist/describe-config.js +2 -0
- package/dist/downstream-input.js +347 -260
- package/dist/errors.d.ts +87 -6
- package/dist/errors.js +114 -4
- package/dist/execute.js +87 -50
- package/dist/executor-admission.js +3 -0
- package/dist/executors/quickjs.js +163 -112
- package/dist/guarded-transport.js +50 -0
- package/dist/guest-runtime.js +1 -1
- package/dist/http/client.js +439 -0
- package/dist/http/errors.js +558 -0
- package/dist/http/fields.js +49 -0
- package/dist/http/hatch-paging.js +318 -0
- package/dist/http/hatch.js +1279 -0
- package/dist/http/json-schema.js +267 -0
- package/dist/http/node-transport.js +241 -0
- package/dist/http/oauth.js +162 -0
- package/dist/http/paging.js +228 -0
- package/dist/http/provider.js +221 -0
- package/dist/http/safety.js +82 -0
- package/dist/http/services.js +79 -0
- package/dist/http/tools.js +383 -0
- package/dist/http/vendor-index.js +138 -0
- package/dist/http/vendor.js +448 -0
- package/dist/http/write-request.js +66 -0
- package/dist/http-policy.d.ts +11 -0
- package/dist/http-policy.js +45 -0
- package/dist/inbound-credential.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/invocation-auth.js +30 -19
- package/dist/invocation.js +218 -85
- package/dist/mcp-result.js +0 -4
- package/dist/membership.d.ts +4 -0
- package/dist/meta-output.js +1 -1
- package/dist/meta-tools.js +531 -406
- package/dist/native-recovery.js +52 -0
- package/dist/native-tools.js +382 -0
- package/dist/node.d.ts +4 -3
- package/dist/node.js +45 -8
- package/dist/oauth-handoff.js +177 -104
- package/dist/oauth-partition.js +6 -20
- package/dist/oauth-sealing.js +6 -7
- package/dist/operation-vocabulary.js +101 -0
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +17 -4
- package/dist/operator-ui/agents.d.ts +5 -4
- package/dist/operator-ui/agents.js +17 -7
- package/dist/operator-ui/config-summary.js +5 -4
- package/dist/operator-ui/connection-map.js +7 -5
- package/dist/operator-ui/contract.d.ts +7 -3
- package/dist/operator-ui/endpoints.js +4 -13
- package/dist/operator-ui/fix-prompts.d.ts +1 -1
- package/dist/operator-ui/fix-prompts.js +0 -8
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/operator-ui/overview.js +5 -2
- package/dist/operator-ui/setup-commands.js +0 -4
- package/dist/operator-ui/view.d.ts +2 -25
- package/dist/operator-ui/view.js +2 -76
- package/dist/program-writes.js +47 -26
- package/dist/provider-call-facts.d.ts +115 -0
- package/dist/provider-call-facts.js +182 -0
- package/dist/provider-call.d.ts +112 -0
- package/dist/provider-call.js +271 -0
- package/dist/provider.d.ts +65 -31
- package/dist/provider.js +116 -39
- package/dist/providers/_shared/google/hatch.js +91 -0
- package/dist/providers/_shared/google/kit.js +537 -0
- package/dist/providers/_shared/google/result-size.js +15 -2
- package/dist/providers/_shared/google/service-account.d.ts +2 -1
- package/dist/providers/_shared/google/service-account.js +8 -4
- package/dist/providers/_shared/google/workspace.d.ts +6 -210
- package/dist/providers/_shared/google/workspace.js +6 -425
- package/dist/providers/_shared/rest/compact-index.js +86 -0
- package/dist/providers/_shared/rest/dispatch.js +3 -23
- package/dist/providers/_shared/rest/kit.js +546 -0
- package/dist/providers/_shared/rest/operation-index.js +230 -14
- package/dist/providers/_shared/rest/response-media.js +11 -0
- package/dist/providers/_shared/rest/tools.js +101 -78
- package/dist/providers/_shared/rest/value-safety.js +353 -195
- package/dist/providers/_shared/sensitive-fields.js +27 -0
- package/dist/providers/breeze/api.js +584 -0
- package/dist/providers/breeze/index.d.ts +1 -3
- package/dist/providers/breeze/index.js +67 -1420
- package/dist/providers/breeze/openapi.generated.js +60 -0
- package/dist/providers/breeze/skill.generated.js +2 -6
- package/dist/providers/breeze/tools.js +696 -0
- package/dist/providers/ccb/api.js +496 -0
- package/dist/providers/ccb/index.d.ts +15 -37
- package/dist/providers/ccb/index.js +77 -1702
- package/dist/providers/ccb/lib/constants.d.ts +34 -0
- package/dist/providers/ccb/lib/constants.js +74 -0
- package/dist/providers/ccb/lib/guide.js +9 -0
- package/dist/providers/ccb/lib/verdicts.js +387 -0
- package/dist/providers/ccb/openapi.generated.js +397 -0
- package/dist/providers/ccb/safety.js +295 -0
- package/dist/providers/ccb/skill.generated.js +2 -6
- package/dist/providers/ccb/tools.js +853 -0
- package/dist/providers/cloudflare/api.js +1066 -0
- package/dist/providers/cloudflare/index.d.ts +5 -22
- package/dist/providers/cloudflare/index.js +79 -197
- package/dist/providers/cloudflare/lib/config.d.ts +16 -0
- package/dist/providers/cloudflare/lib/config.js +2 -0
- package/dist/providers/cloudflare/lib/discovery.js +29 -0
- package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
- package/dist/providers/cloudflare/lib/hatch.js +103 -0
- package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
- package/dist/providers/cloudflare/lib/response-media.js +75 -0
- package/dist/providers/cloudflare/openapi.generated.js +4 -3
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
- package/dist/providers/cloudflare/skill.generated.js +2 -10
- package/dist/providers/cloudflare/tools.js +360 -0
- package/dist/providers/docs/api.js +51 -0
- package/dist/providers/docs/index.d.ts +1 -12
- package/dist/providers/docs/index.js +53 -1165
- package/dist/providers/docs/lib/constants.d.ts +18 -0
- package/dist/providers/docs/lib/constants.js +18 -0
- package/dist/providers/docs/lib/discovery.js +26 -0
- package/dist/providers/docs/lib/requests.js +48 -0
- package/dist/providers/docs/lib/schemas.js +168 -0
- package/dist/providers/docs/openapi.generated.js +16 -0
- package/dist/providers/docs/safety.js +54 -0
- package/dist/providers/docs/skill.generated.js +2 -6
- package/dist/providers/docs/tools.js +802 -0
- package/dist/providers/drive/api.js +177 -0
- package/dist/providers/drive/index.d.ts +1 -12
- package/dist/providers/drive/index.js +38 -1448
- package/dist/providers/drive/lib/constants.d.ts +6 -0
- package/dist/providers/drive/lib/constants.js +6 -0
- package/dist/providers/drive/lib/contracts.js +532 -0
- package/dist/providers/drive/lib/hatch.js +197 -0
- package/dist/providers/drive/openapi.generated.js +77 -0
- package/dist/providers/drive/safety.js +90 -0
- package/dist/providers/drive/skill.generated.js +2 -6
- package/dist/providers/drive/tools.js +1290 -0
- package/dist/providers/forms/api.js +71 -0
- package/dist/providers/forms/index.d.ts +1 -12
- package/dist/providers/forms/index.js +49 -1145
- package/dist/providers/forms/lib/constants.d.ts +14 -0
- package/dist/providers/forms/lib/constants.js +19 -0
- package/dist/providers/forms/lib/hatch.js +72 -0
- package/dist/providers/forms/openapi.generated.js +23 -0
- package/dist/providers/forms/safety.js +34 -0
- package/dist/providers/forms/skill.generated.js +2 -8
- package/dist/providers/forms/tools.js +1024 -0
- package/dist/providers/github/api.js +150 -0
- package/dist/providers/github/index.d.ts +4 -5
- package/dist/providers/github/index.js +30 -87
- package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
- package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
- package/dist/providers/github/lib/discovery.js +132 -0
- package/dist/providers/github/lib/operations.js +58 -0
- package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
- package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
- package/dist/providers/github/openapi.generated.js +1216 -0
- package/dist/providers/github/safety.js +216 -0
- package/dist/providers/github/skill.generated.js +2 -5
- package/dist/providers/github/tools.js +506 -0
- package/dist/providers/gmail/api.js +114 -0
- package/dist/providers/gmail/index.d.ts +1 -12
- package/dist/providers/gmail/index.js +73 -1621
- package/dist/providers/gmail/lib/constants.d.ts +13 -0
- package/dist/providers/gmail/lib/constants.js +16 -0
- package/dist/providers/gmail/lib/index.js +173 -0
- package/dist/providers/gmail/lib/schemas.js +176 -0
- package/dist/providers/gmail/lib/threading.js +120 -0
- package/dist/providers/gmail/openapi.generated.js +92 -0
- package/dist/providers/gmail/safety.js +21 -0
- package/dist/providers/gmail/skill.generated.js +2 -6
- package/dist/providers/gmail/tools.js +1435 -0
- package/dist/providers/overflow/api.js +1008 -0
- package/dist/providers/overflow/index.d.ts +1 -17
- package/dist/providers/overflow/index.js +60 -1488
- package/dist/providers/overflow/skill.generated.js +2 -5
- package/dist/providers/overflow/tools.js +873 -0
- package/dist/providers/planning-center/api.js +467 -0
- package/dist/providers/planning-center/index.d.ts +4 -35
- package/dist/providers/planning-center/index.js +69 -2445
- package/dist/providers/planning-center/lib/constants.d.ts +58 -0
- package/dist/providers/planning-center/lib/constants.js +83 -0
- package/dist/providers/planning-center/lib/discovery.js +40 -0
- package/dist/providers/planning-center/lib/guide.js +39 -0
- package/dist/providers/planning-center/lib/json-api.js +83 -0
- package/dist/providers/planning-center/lib/verdicts.js +1758 -0
- package/dist/providers/planning-center/openapi.generated.js +1764 -0
- package/dist/providers/planning-center/safety.js +188 -0
- package/dist/providers/planning-center/skill.generated.js +2 -27
- package/dist/providers/planning-center/tools.js +1010 -0
- package/dist/providers/sheets/api.js +133 -0
- package/dist/providers/sheets/index.d.ts +4 -16
- package/dist/providers/sheets/index.js +61 -1172
- package/dist/providers/sheets/lib/constants.d.ts +14 -0
- package/dist/providers/sheets/lib/constants.js +14 -0
- package/dist/providers/sheets/lib/hatch.js +163 -0
- package/dist/providers/sheets/lib/requests.js +105 -0
- package/dist/providers/sheets/lib/schemas.js +138 -0
- package/dist/providers/sheets/openapi.generated.js +30 -0
- package/dist/providers/sheets/safety.js +27 -0
- package/dist/providers/sheets/skill.generated.js +2 -7
- package/dist/providers/sheets/tools.js +734 -0
- package/dist/providers/slides/api.js +147 -0
- package/dist/providers/slides/index.d.ts +2 -10
- package/dist/providers/slides/index.js +48 -2555
- package/dist/providers/slides/lib/constants.d.ts +11 -0
- package/dist/providers/slides/lib/constants.js +11 -0
- package/dist/providers/slides/lib/requests.js +60 -0
- package/dist/providers/slides/lib/schemas.js +565 -0
- package/dist/providers/slides/openapi.generated.js +18 -0
- package/dist/providers/slides/skill.generated.js +2 -6
- package/dist/providers/slides/tools.js +1968 -0
- package/dist/providers/stripe/api.js +401 -0
- package/dist/providers/stripe/index.d.ts +5 -27
- package/dist/providers/stripe/index.js +74 -210
- package/dist/providers/stripe/lib/config.d.ts +4 -0
- package/dist/providers/stripe/lib/config.js +2 -0
- package/dist/providers/stripe/openapi.generated.js +4 -3
- package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
- package/dist/providers/stripe/skill.generated.js +2 -11
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +17 -0
- package/dist/providers/tithely/index.js +86 -965
- package/dist/providers/tithely/skill.generated.js +2 -5
- package/dist/providers/tithely/tools.js +426 -0
- package/dist/providers/vercel/api.js +551 -0
- package/dist/providers/vercel/index.d.ts +9 -25
- package/dist/providers/vercel/index.js +66 -260
- package/dist/providers/vercel/lib/config.d.ts +2 -0
- package/dist/providers/vercel/lib/config.js +2 -0
- package/dist/providers/vercel/lib/index.js +117 -0
- package/dist/providers/vercel/lib/logs.js +100 -0
- package/dist/providers/vercel/lib/pin.js +270 -0
- package/dist/providers/vercel/openapi.generated.js +10 -5
- package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
- package/dist/providers/vercel/skill.generated.js +2 -7
- package/dist/providers/vercel/tools.js +642 -0
- package/dist/redaction-identity.js +47 -0
- package/dist/redaction-marker.js +125 -0
- package/dist/registry.d.ts +21 -15
- package/dist/registry.js +409 -376
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/actor-labels.js +1 -1
- package/dist/routes/connect.js +1 -1
- package/dist/routes/dispatch.js +103 -0
- package/dist/routes/mcp.js +170 -79
- package/dist/routes/oauth.js +3 -1
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +5 -4
- package/dist/routes/shared.js +244 -177
- package/dist/routes/skills.js +10 -8
- package/dist/routes/ui-config.js +27 -3
- package/dist/routes/ui-data.js +21 -20
- package/dist/routes/ui.js +1 -1
- package/dist/runtime/call-admission.js +0 -26
- package/dist/runtime/call-entry.js +30 -0
- package/dist/runtime/call.js +556 -0
- package/dist/runtime/connector-scope.js +7 -0
- package/dist/runtime/credential-probe.js +34 -0
- package/dist/runtime/executor.js +40 -0
- package/dist/runtime/native-storage.js +63 -0
- package/dist/runtime/registry.js +27 -0
- package/dist/runtime/request-scope.js +72 -0
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/shared-read.js +34 -5
- package/dist/runtime/storage.js +34 -21
- package/dist/sent-secrets.d.ts +194 -37
- package/dist/sent-secrets.js +1743 -340
- package/dist/server.js +5 -56
- package/dist/skills.js +322 -222
- package/dist/sqlite.js +15 -14
- package/dist/storage/d1-copy.js +255 -0
- package/dist/storage/d1-driver.js +15 -0
- package/dist/storage/keys.js +28 -0
- package/dist/storage/sql-driver.js +88 -0
- package/dist/storage/sql-native.js +544 -0
- package/dist/storage/sql-schema.d.ts +6 -0
- package/dist/storage/sql-schema.js +15 -0
- package/dist/storage/sql.d.ts +14 -20
- package/dist/storage/sql.js +29 -535
- package/dist/timeout.js +0 -26
- package/dist/tool-safety.js +20 -10
- package/dist/types.d.ts +7 -109
- package/dist/upward.js +3 -0
- package/dist/usage-guide.js +3 -1
- package/dist/validate.d.ts +4 -2
- package/dist/validate.js +86 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +312 -292
- package/dist/workspace-subject.js +5 -1
- package/dist/write-outcome.js +454 -34
- package/documentation/README.md +27 -7
- package/documentation/architecture.md +2547 -226
- package/documentation/auth.md +821 -98
- package/documentation/code-mode.md +49 -16
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +129 -144
- package/documentation/meta-tools.md +409 -32
- package/documentation/operating.md +27 -3
- package/documentation/operator-tests.md +3 -0
- package/documentation/operator-ui.md +68 -45
- package/documentation/provider-auth-migration.md +16 -63
- package/documentation/provider-migration-0.29.md +10 -5
- package/documentation/provider-migration-0.35.md +975 -0
- package/documentation/providers.md +965 -0
- package/examples/worker/AGENTS.md +23 -9
- package/examples/worker/README.md +254 -135
- package/examples/worker/alchemy.run.ts +116 -0
- package/examples/worker/gitignore +10 -0
- package/examples/worker/src/connecta.config.ts +23 -21
- package/examples/worker/wrangler.jsonc +14 -5
- package/guides/connecting-agents.md +157 -0
- package/guides/how-it-works.md +156 -0
- package/guides/safety-and-trust.md +153 -0
- package/package.json +27 -46
- package/templates/node/.env.example +3 -0
- package/templates/node/README.md +22 -2
- package/templates/node/docker-compose.yml +1 -0
- package/templates/node/package.json +3 -2
- package/templates/node/src/connecta.config.ts +8 -0
- package/dist/catalog-drift.d.ts +0 -138
- package/dist/catalog-drift.js +0 -578
- package/dist/providers/_shared/request-token.js +0 -83
- package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
- package/dist/providers/_shared/rest/tools.d.ts +0 -141
- package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
- package/dist/providers/basecamp/index.d.ts +0 -45
- package/dist/providers/basecamp/index.js +0 -997
- package/dist/providers/basecamp/skill.generated.js +0 -9
- package/dist/providers/cloudflare/rest.d.ts +0 -37
- package/dist/providers/cloudflare/rest.js +0 -1097
- package/dist/providers/github/hosted.js +0 -218
- package/dist/providers/github/rest.js +0 -321
- package/dist/providers/infisical/index.d.ts +0 -7
- package/dist/providers/infisical/index.js +0 -713
- package/dist/providers/infisical/skill.generated.js +0 -8
- package/dist/providers/intercom/index.d.ts +0 -24
- package/dist/providers/intercom/index.js +0 -121
- package/dist/providers/intercom/skill.generated.js +0 -8
- package/dist/providers/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -537
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -59
- package/dist/providers/mixpanel/index.js +0 -472
- package/dist/providers/mixpanel/operations.js +0 -20
- package/dist/providers/mixpanel/rest.js +0 -603
- package/dist/providers/mixpanel/skill.generated.js +0 -10
- package/dist/providers/mixpanel/value-safety.js +0 -16
- package/dist/providers/notion/index.d.ts +0 -67
- package/dist/providers/notion/index.js +0 -368
- package/dist/providers/notion/openapi.generated.js +0 -75
- package/dist/providers/notion/rest.d.ts +0 -24
- package/dist/providers/notion/rest.js +0 -1226
- package/dist/providers/notion/skill.generated.js +0 -11
- package/dist/providers/notion/value-safety.js +0 -56
- package/dist/providers/revenuecat/index.d.ts +0 -51
- package/dist/providers/revenuecat/index.js +0 -697
- package/dist/providers/revenuecat/skill.generated.js +0 -11
- package/dist/providers/stripe/rest.d.ts +0 -14
- package/dist/providers/stripe/rest.js +0 -402
- package/dist/providers/vercel/rest.d.ts +0 -20
- package/dist/providers/vercel/rest.js +0 -1020
- package/dist/providers/wispr-flow/index.d.ts +0 -14
- package/dist/providers/wispr-flow/index.js +0 -117
- package/dist/providers/wispr-flow/skill.generated.js +0 -8
- /package/dist/providers/github/{key.js → lib/key.js} +0 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
// Provider kit (decision 0010, B.4). Unreachable from src/index.ts.
|
|
2
|
+
//
|
|
3
|
+
// Tool builders. A tool is a name, a description, an input Schema, an output
|
|
4
|
+
// Schema, and a program; the builder renders both Schemas as catalog JSON
|
|
5
|
+
// Schema and annotates the tool from which builder was called (never from an
|
|
6
|
+
// HTTP method). At run time the call's served classification decides, so a
|
|
7
|
+
// deployment that reclassifies a tool is obeyed by the transport and the
|
|
8
|
+
// write-outcome rule alike. Failures leave through the one mapping in
|
|
9
|
+
// src/http/errors.ts.
|
|
10
|
+
//
|
|
11
|
+
// The result is an ordinary `ApiTool`, so a kit tool sits in `api()` beside
|
|
12
|
+
// hand-written ones and keeps every guarantee `api()` already gives:
|
|
13
|
+
// argument validation, sent-secret redaction, withheld handler text. Its
|
|
14
|
+
// boundary also decides every write's outcome, from the core ledger the kit's
|
|
15
|
+
// transport records into, so a kit connector declares `tracksEveryRequest`.
|
|
16
|
+
import { Context, Effect, Schema } from "effect";
|
|
17
|
+
import { inLogicalCall } from "../provider-call.js";
|
|
18
|
+
import { registerToolEntry } from "../runtime/call-entry.js";
|
|
19
|
+
import { ConnectorCallError } from "../errors.js";
|
|
20
|
+
import { refuseWithheldValues } from "../providers/_shared/rest/value-safety.js";
|
|
21
|
+
import { causeError, runEdge } from "../runtime/run.js";
|
|
22
|
+
import { recordUnauthorized, Refusal, RequestRefused, toConnectorCallError, withheldDefect, } from "./errors.js";
|
|
23
|
+
import { fieldsInput, fieldsProblem, selectFields } from "./fields.js";
|
|
24
|
+
import { catalogSchema, Integer } from "./json-schema.js";
|
|
25
|
+
import { boundCursor, Cursor, PageInfo, StandardPage } from "./paging.js";
|
|
26
|
+
import { Classification, Dispatches, IdempotencyKey, Recovery, ReadTool, WriteCapacity, writeRequestKey, fromConnectorContext, } from "./services.js";
|
|
27
|
+
const inputs = new WeakMap();
|
|
28
|
+
const keyed = new WeakSet();
|
|
29
|
+
/** Whether a kit write builder declared an idempotency key for this tool. */
|
|
30
|
+
export function takesIdempotencyKey(tool) {
|
|
31
|
+
return keyed.has(tool);
|
|
32
|
+
}
|
|
33
|
+
/** Copy decoded values only; the input supplies key order, never values or admission. */
|
|
34
|
+
function originalOrder(decoded, original) {
|
|
35
|
+
const copies = new WeakMap();
|
|
36
|
+
const copy = (value, source) => {
|
|
37
|
+
if (value === source || value === null || typeof value !== "object")
|
|
38
|
+
return value;
|
|
39
|
+
if (copies.has(value))
|
|
40
|
+
return copies.get(value);
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
const out = [];
|
|
43
|
+
copies.set(value, out);
|
|
44
|
+
value.forEach((item, index) => out.push(copy(item, Array.isArray(source) ? source[index] : undefined)));
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
const values = value;
|
|
48
|
+
const keys = source !== null && typeof source === "object" && !Array.isArray(source) ? Object.keys(source) : [];
|
|
49
|
+
const present = keys.filter((key) => Object.hasOwn(values, key));
|
|
50
|
+
const ordered = new Set(present);
|
|
51
|
+
const out = {};
|
|
52
|
+
copies.set(value, out);
|
|
53
|
+
for (const key of [...present, ...Object.keys(values).filter((key) => !ordered.has(key))]) {
|
|
54
|
+
Object.defineProperty(out, key, {
|
|
55
|
+
value: copy(values[key], source !== null && typeof source === "object" ? source[key] : undefined),
|
|
56
|
+
enumerable: true,
|
|
57
|
+
writable: true,
|
|
58
|
+
configurable: true,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
};
|
|
63
|
+
return copy(decoded, original);
|
|
64
|
+
}
|
|
65
|
+
/** The input Schema a kit tool decodes with, for the catalog/decoder agreement check. */
|
|
66
|
+
export function kitInput(tool) {
|
|
67
|
+
return inputs.get(tool);
|
|
68
|
+
}
|
|
69
|
+
/** A Struct's own property names, for `fields`. */
|
|
70
|
+
function propertyNames(schema, what) {
|
|
71
|
+
const fields = schema.fields;
|
|
72
|
+
if (!fields)
|
|
73
|
+
throw new Error(`fields needs ${what} declared as a Schema.Struct.`);
|
|
74
|
+
return new Set(Object.keys(fields));
|
|
75
|
+
}
|
|
76
|
+
/** Refuse a bad `fields` path before anything is sent. */
|
|
77
|
+
function checkFields(fields, names, of) {
|
|
78
|
+
const error = fieldsProblem(fields, names, of);
|
|
79
|
+
return error ? Effect.fail(new Refusal({ error })) : Effect.void;
|
|
80
|
+
}
|
|
81
|
+
const fieldsExample = (names) => {
|
|
82
|
+
const first = [...names][0];
|
|
83
|
+
return first === undefined ? "" : `, e.g. ["${first}"]`;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* A read: admitted to call_tool and programs. `fields: true` (decision 0011
|
|
87
|
+
* S5) appends a `fields` argument that narrows the returned record; the input
|
|
88
|
+
* and output must then be Structs, and a path outside the output's properties
|
|
89
|
+
* is refused before anything is sent.
|
|
90
|
+
*/
|
|
91
|
+
export function read(connection, spec) {
|
|
92
|
+
if (spec.review !== undefined && !spec.review.trim())
|
|
93
|
+
throw new Error("A read review must be nonempty.");
|
|
94
|
+
if (spec.fields === undefined)
|
|
95
|
+
return build(connection, spec, "read", { readOnlyHint: true }, undefined, spec.review);
|
|
96
|
+
const names = propertyNames(spec.output, "the output");
|
|
97
|
+
propertyNames(spec.input, "the input");
|
|
98
|
+
const input = Schema.Struct({
|
|
99
|
+
...spec.input.fields,
|
|
100
|
+
fields: Schema.optionalKey(fieldsInput(`Dot paths to keep in the result${fieldsExample(names)}. It only narrows the result.`)),
|
|
101
|
+
});
|
|
102
|
+
return build(connection, {
|
|
103
|
+
...spec,
|
|
104
|
+
input,
|
|
105
|
+
run: (args) => Effect.gen(function* () {
|
|
106
|
+
yield* checkFields(args.fields, names, "the result");
|
|
107
|
+
const { fields, ...rest } = args;
|
|
108
|
+
return selectFields(yield* spec.run(rest), fields);
|
|
109
|
+
}),
|
|
110
|
+
// A selection may omit any property: the result is typed unknown and rendered sparse.
|
|
111
|
+
output: Schema.Unknown,
|
|
112
|
+
}, "read", { readOnlyHint: true }, undefined, spec.review, sparse(catalogSchema(spec.output, "output")));
|
|
113
|
+
}
|
|
114
|
+
/** Fetch one entity with the same read contract as a list. */
|
|
115
|
+
export function get(connection, spec) {
|
|
116
|
+
return read(connection, spec);
|
|
117
|
+
}
|
|
118
|
+
export function list(connection, spec) {
|
|
119
|
+
if (spec.cursor !== undefined && spec.cursor !== "plain" && spec.cursor !== "bound")
|
|
120
|
+
throw new Error('A list cursor must be "plain" or "bound".');
|
|
121
|
+
if (!("key" in spec))
|
|
122
|
+
return standardList(connection, spec);
|
|
123
|
+
if (!spec.key || spec.key === "page")
|
|
124
|
+
throw new Error("A list needs a row key distinct from page.");
|
|
125
|
+
return read(connection, {
|
|
126
|
+
...spec,
|
|
127
|
+
output: Schema.Struct({ [spec.key]: Schema.Array(spec.row), page: PageInfo }),
|
|
128
|
+
run: (args) => Effect.gen(function* () {
|
|
129
|
+
const fields = args;
|
|
130
|
+
const supplied = typeof fields.cursor === "string" ? fields.cursor : undefined;
|
|
131
|
+
const cursor = supplied !== undefined && spec.cursor === "bound"
|
|
132
|
+
? yield* boundCursor.decode(spec.name, fields, supplied)
|
|
133
|
+
: supplied;
|
|
134
|
+
const result = yield* spec.page(args, cursor);
|
|
135
|
+
const nextCursor = result.page.nextCursor !== null && spec.cursor === "bound"
|
|
136
|
+
? yield* boundCursor.encode(spec.name, fields, result.page.nextCursor)
|
|
137
|
+
: result.page.nextCursor;
|
|
138
|
+
return { [spec.key]: result.rows, page: { ...result.page, nextCursor } };
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function standardList(connection, spec) {
|
|
143
|
+
const { default: size, max } = spec.limit;
|
|
144
|
+
if (spec.total !== undefined && spec.total !== true)
|
|
145
|
+
throw new Error(`${spec.name} total must opt in with true for a vendor-stated exact count.`);
|
|
146
|
+
if (!Number.isSafeInteger(size) || !Number.isSafeInteger(max) || size < 1 || max < size)
|
|
147
|
+
throw new Error(`${spec.name} limit needs positive whole default and max values, with default <= max.`);
|
|
148
|
+
const values = spec.limit.values === undefined ? undefined : [...spec.limit.values];
|
|
149
|
+
if (values &&
|
|
150
|
+
(!values.length ||
|
|
151
|
+
new Set(values).size !== values.length ||
|
|
152
|
+
!values.includes(size) ||
|
|
153
|
+
values.some((value) => !Number.isSafeInteger(value) || value < 1 || value > max)))
|
|
154
|
+
throw new Error(`${spec.name} limit values must be unique positive whole sizes within max and include the default.`);
|
|
155
|
+
const limit = Integer.check(Schema.isBetween({ minimum: 1, maximum: max }));
|
|
156
|
+
const allowedLimit = values
|
|
157
|
+
? limit.check(Schema.makeFilter((value) => values.includes(value), {
|
|
158
|
+
expected: `one of ${values.join(", ")}`,
|
|
159
|
+
toJsonSchema: () => ({ enum: [...values] }),
|
|
160
|
+
}))
|
|
161
|
+
: limit;
|
|
162
|
+
for (const name of ["limit", "cursor", "fields"]) {
|
|
163
|
+
if (name in spec.filters)
|
|
164
|
+
throw new Error(`${spec.name} filters cannot declare ${name}; the kit supplies it.`);
|
|
165
|
+
}
|
|
166
|
+
const names = propertyNames(spec.row, "the row");
|
|
167
|
+
const input = Schema.Struct({
|
|
168
|
+
...spec.filters,
|
|
169
|
+
limit: Schema.optionalKey(allowedLimit.annotate({
|
|
170
|
+
description: spec.limit.description,
|
|
171
|
+
default: size,
|
|
172
|
+
})),
|
|
173
|
+
cursor: Schema.optionalKey(Cursor),
|
|
174
|
+
fields: Schema.optionalKey(fieldsInput(`Dot paths to keep in each item${fieldsExample(names)}. It only narrows the result.`)),
|
|
175
|
+
});
|
|
176
|
+
return build(connection, {
|
|
177
|
+
name: spec.name,
|
|
178
|
+
description: spec.description,
|
|
179
|
+
input,
|
|
180
|
+
// `fields` narrows each item, so items render sparse; the envelope keeps items and page required.
|
|
181
|
+
output: Schema.Unknown,
|
|
182
|
+
run: (args) => Effect.gen(function* () {
|
|
183
|
+
yield* checkFields(args.fields, names, "an item");
|
|
184
|
+
// A cursor stays valid when only the projection changes between pages.
|
|
185
|
+
const { fields, cursor: supplied, ...rest } = args;
|
|
186
|
+
const scope = spec.cursorScope === undefined
|
|
187
|
+
? rest
|
|
188
|
+
: {
|
|
189
|
+
arguments: Object.fromEntries(Object.entries(rest).filter(([key]) => key !== "limit")),
|
|
190
|
+
deploymentPolicy: spec.cursorScope,
|
|
191
|
+
};
|
|
192
|
+
const bound = spec.cursor === "bound";
|
|
193
|
+
const cursor = typeof supplied === "string" && bound ? yield* boundCursor.decode(spec.name, scope, supplied) : supplied;
|
|
194
|
+
const result = yield* spec.page({ ...rest, limit: typeof rest["limit"] === "number" ? rest["limit"] : size }, cursor);
|
|
195
|
+
const items = selectFields(result.rows, fields);
|
|
196
|
+
const total = spec.total ? result.page.total : undefined;
|
|
197
|
+
if (total !== undefined && (!Number.isSafeInteger(total) || total < 0))
|
|
198
|
+
return yield* Effect.fail(new Refusal({
|
|
199
|
+
error: new ConnectorCallError("connector_call_failed", "The vendor returned an invalid exact total.", {
|
|
200
|
+
retryable: false,
|
|
201
|
+
}),
|
|
202
|
+
}));
|
|
203
|
+
const count = total === undefined ? {} : { total };
|
|
204
|
+
if (!result.page.hasMore)
|
|
205
|
+
return { items, page: { hasMore: false, ...count } };
|
|
206
|
+
if (result.page.nextCursor === null)
|
|
207
|
+
return yield* Effect.fail(new Refusal({
|
|
208
|
+
error: new ConnectorCallError("connector_call_failed", "The vendor reported another page without a next cursor.", { retryable: false }),
|
|
209
|
+
}));
|
|
210
|
+
const nextCursor = bound
|
|
211
|
+
? yield* boundCursor.encode(spec.name, scope, result.page.nextCursor)
|
|
212
|
+
: result.page.nextCursor;
|
|
213
|
+
return { items, page: { hasMore: true, nextCursor, ...count } };
|
|
214
|
+
}),
|
|
215
|
+
}, "read", { readOnlyHint: true }, undefined, spec.review, sparseItems(catalogSchema(Schema.Struct({
|
|
216
|
+
items: Schema.Array(spec.row),
|
|
217
|
+
page: spec.total
|
|
218
|
+
? Schema.Struct({
|
|
219
|
+
...StandardPage.fields,
|
|
220
|
+
total: Schema.optionalKey(Integer.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
221
|
+
})
|
|
222
|
+
: StandardPage,
|
|
223
|
+
}), "output")));
|
|
224
|
+
}
|
|
225
|
+
/** What a `fields` selection may return: the declared shape with no property required, at any depth. */
|
|
226
|
+
function sparse(schema) {
|
|
227
|
+
// Walk schema positions deliberately: drop each `required` keyword, keep property names (a
|
|
228
|
+
// property may be called "required") and literal data (enum, const, default, examples) as is.
|
|
229
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
230
|
+
const map = (value) => object(value) ? Object.fromEntries(Object.entries(value).map(([name, child]) => [name, walk(child)])) : value;
|
|
231
|
+
const walk = (node) => {
|
|
232
|
+
if (!object(node))
|
|
233
|
+
return node;
|
|
234
|
+
return Object.fromEntries(Object.entries(node).flatMap(([key, value]) => {
|
|
235
|
+
if (key === "required")
|
|
236
|
+
return [];
|
|
237
|
+
if (["properties", "patternProperties", "$defs", "definitions", "dependentSchemas"].includes(key))
|
|
238
|
+
return [[key, map(value)]];
|
|
239
|
+
if (["anyOf", "oneOf", "allOf", "prefixItems"].includes(key))
|
|
240
|
+
return [[key, Array.isArray(value) ? value.map(walk) : value]];
|
|
241
|
+
if (["items", "additionalProperties", "not", "if", "then", "else", "contains", "propertyNames"].includes(key))
|
|
242
|
+
return [[key, Array.isArray(value) ? value.map(walk) : walk(value)]];
|
|
243
|
+
return [[key, value]];
|
|
244
|
+
}));
|
|
245
|
+
};
|
|
246
|
+
return walk(schema);
|
|
247
|
+
}
|
|
248
|
+
/** A standard list's output with each item sparse. */
|
|
249
|
+
function sparseItems(schema) {
|
|
250
|
+
const root = schema;
|
|
251
|
+
return {
|
|
252
|
+
...schema,
|
|
253
|
+
properties: {
|
|
254
|
+
...root.properties,
|
|
255
|
+
items: { ...root.properties.items, items: sparse(root.properties.items.items) },
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/** A write: approval-gated, dispatched once, and its failures reported by the write-outcome rule. */
|
|
260
|
+
export function write(connection, spec) {
|
|
261
|
+
return build(connection, spec, "write", {
|
|
262
|
+
readOnlyHint: false,
|
|
263
|
+
destructiveHint: spec.destructive,
|
|
264
|
+
...(spec.idempotent === undefined ? {} : { idempotentHint: spec.idempotent }),
|
|
265
|
+
}, spec.idempotency);
|
|
266
|
+
}
|
|
267
|
+
/** Money, mail, and refunds are always destructive and never idempotent hints. */
|
|
268
|
+
export function action(connection, spec) {
|
|
269
|
+
return build(connection, spec, "write", { readOnlyHint: false, destructiveHint: true, idempotentHint: false });
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* A key the transport may send: not blank, and only characters an HTTP
|
|
273
|
+
* header carries. The REST layer refuses the same keys before dispatch.
|
|
274
|
+
*/
|
|
275
|
+
function unsendableKey(key) {
|
|
276
|
+
return key.trim() === "" || !/^[\t\x20-\x7e\x80-\xff]*$/.test(key);
|
|
277
|
+
}
|
|
278
|
+
function build(connection, spec, verdict, annotations, idempotency, review,
|
|
279
|
+
/** A rendered output schema that replaces the Schema's, for sparse `fields` results. */
|
|
280
|
+
rendered) {
|
|
281
|
+
// One key protects one request: a tool with several write steps cannot share it.
|
|
282
|
+
const declared = Object.entries(spec.writeSteps ?? { write: "once" });
|
|
283
|
+
if (idempotency && (declared.length !== 1 || declared[0]?.[1] !== "once" || !idempotency.header.trim())) {
|
|
284
|
+
throw new Error("An idempotency key needs a header name and a tool with one write step sent once.");
|
|
285
|
+
}
|
|
286
|
+
if (idempotency) {
|
|
287
|
+
try {
|
|
288
|
+
new Headers([[idempotency.header, "key"]]);
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
throw new Error("An idempotency key needs a valid HTTP header name.");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Snapshot the declaration now; mutations and retries cannot add step names or increase bounds.
|
|
295
|
+
const steps = Object.entries(spec.writeSteps ?? { write: "once" }).map(([name, declaration]) => {
|
|
296
|
+
if (!name ||
|
|
297
|
+
(declaration !== "once" &&
|
|
298
|
+
(!Number.isSafeInteger(declaration.maxItems) ||
|
|
299
|
+
declaration.maxItems < 1 ||
|
|
300
|
+
typeof declaration.items !== "function" ||
|
|
301
|
+
typeof declaration.request !== "function"))) {
|
|
302
|
+
throw new Error("Write steps need nonempty names and a positive whole maxItems bound.");
|
|
303
|
+
}
|
|
304
|
+
return [name, declaration === "once" ? declaration : { ...declaration }];
|
|
305
|
+
});
|
|
306
|
+
const capacity = (args) => {
|
|
307
|
+
const identities = new Set();
|
|
308
|
+
return new Map(steps.map(([name, declaration]) => {
|
|
309
|
+
if (declaration === "once")
|
|
310
|
+
return [name, new Map([[undefined, undefined]])];
|
|
311
|
+
const items = [...declaration.items(args)];
|
|
312
|
+
if (items.length > declaration.maxItems ||
|
|
313
|
+
items.some((item) => typeof item !== "string" || item.length === 0) ||
|
|
314
|
+
new Set(items).size !== items.length) {
|
|
315
|
+
throw new Error("Write item identities must be unique nonempty strings within the declared maxItems bound.");
|
|
316
|
+
}
|
|
317
|
+
const requests = items.map((item) => {
|
|
318
|
+
// Copy identifying content: a retained mutable object cannot change its request later.
|
|
319
|
+
const request = structuredClone(declaration.request(item, args));
|
|
320
|
+
const key = writeRequestKey(request);
|
|
321
|
+
if (identities.has(key))
|
|
322
|
+
throw new Error("Write item aliases must not identify identical request content.");
|
|
323
|
+
identities.add(key);
|
|
324
|
+
return [item, request];
|
|
325
|
+
});
|
|
326
|
+
return [name, new Map(requests)];
|
|
327
|
+
}));
|
|
328
|
+
};
|
|
329
|
+
const decode = Schema.decodeUnknownEffect(spec.input, { onExcessProperty: "error" });
|
|
330
|
+
const tool = {
|
|
331
|
+
name: spec.name,
|
|
332
|
+
description: spec.description,
|
|
333
|
+
inputSchema: catalogSchema(spec.input, "input"),
|
|
334
|
+
outputSchema: rendered ?? catalogSchema(spec.output, "output"),
|
|
335
|
+
annotations,
|
|
336
|
+
handler: (args, ctx) => runEdge(program(args, ctx), { signal: ctx.signal }),
|
|
337
|
+
};
|
|
338
|
+
const program = (args, ctx, served = false) => Effect.suspend(() => {
|
|
339
|
+
// The builder's verdict only stands in for a call no invocation dispatched.
|
|
340
|
+
const lease = fromConnectorContext(ctx, verdict);
|
|
341
|
+
const classification = Context.get(lease.services, Classification);
|
|
342
|
+
const dispatches = Context.get(lease.services, Dispatches);
|
|
343
|
+
const map = { classification, dispatches, withholds: connection.safety.withholdsErrors, signal: ctx.signal };
|
|
344
|
+
return Effect.try({
|
|
345
|
+
try: () => {
|
|
346
|
+
if (verdict === "write" || classification === "write")
|
|
347
|
+
refuseWithheldValues(args, spec.name);
|
|
348
|
+
},
|
|
349
|
+
catch: (error) => error,
|
|
350
|
+
}).pipe(Effect.catch((error) => error instanceof ConnectorCallError ? Effect.fail(new Refusal({ error })) : Effect.die(error)),
|
|
351
|
+
// Its message can quote the argument; as a defect, api() withholds it.
|
|
352
|
+
Effect.flatMap(() => decode(args).pipe(Effect.orDie)), Effect.map((decoded) => (spec.inputOrder === "original" ? originalOrder(decoded, args) : decoded)), Effect.flatMap((decoded) => Effect.gen(function* () {
|
|
353
|
+
const key = idempotency?.key(decoded);
|
|
354
|
+
if (key !== undefined && unsendableKey(key)) {
|
|
355
|
+
return yield* Effect.fail(new RequestRefused({
|
|
356
|
+
message: "The idempotency key is blank or holds a character an HTTP header cannot carry.",
|
|
357
|
+
}));
|
|
358
|
+
}
|
|
359
|
+
// Recorded before anything leaves: a deadline that interrupts
|
|
360
|
+
// the call still returns the key for reconciliation.
|
|
361
|
+
if (key !== undefined)
|
|
362
|
+
yield* (yield* Recovery).record({ idempotencyKey: key });
|
|
363
|
+
return yield* Effect.suspend(() => spec.run(decoded)).pipe(Effect.provideService(ReadTool, verdict === "read" ? (review ? { review } : {}) : undefined), Effect.provideService(WriteCapacity, capacity(decoded)), Effect.provideService(IdempotencyKey, key === undefined ? undefined : { header: idempotency.header, key }));
|
|
364
|
+
})),
|
|
365
|
+
// A native served call shares its invocation's cancellation. The
|
|
366
|
+
// invocation settles that interruption from the raw ledger before
|
|
367
|
+
// any completed handler verdict exists, just as at the Promise edge.
|
|
368
|
+
Effect.mapError((failure) => served && ctx.signal?.aborted ? ctx.signal.reason : toConnectorCallError(failure, connection.errors, map)), Effect.catchDefect((defect) => Effect.die(withheldDefect(defect, map))), Effect.provideContext(lease.services),
|
|
369
|
+
// The call's finalizer: every capability kept from it refuses after this.
|
|
370
|
+
Effect.ensuring(Effect.sync(lease.close)), Effect.onError((cause) => Effect.sync(() => {
|
|
371
|
+
if (!served &&
|
|
372
|
+
ctx.signal?.aborted === true &&
|
|
373
|
+
causeError(cause, ctx.signal) === ctx.signal.reason &&
|
|
374
|
+
map.classification === "write")
|
|
375
|
+
recordUnauthorized(connection.errors, map);
|
|
376
|
+
})));
|
|
377
|
+
});
|
|
378
|
+
registerToolEntry(tool, (args, ctx) => program(args, ctx, inLogicalCall(ctx)));
|
|
379
|
+
inputs.set(tool, spec.input);
|
|
380
|
+
if (idempotency)
|
|
381
|
+
keyed.add(tool);
|
|
382
|
+
return tool;
|
|
383
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Internal provider kit (decision 0011 S1). Unreachable from src/index.ts.
|
|
2
|
+
//
|
|
3
|
+
// The standard hatch validates every request against an operation index. A
|
|
4
|
+
// vendor with a pinned OpenAPI document compiles one at generation time; a
|
|
5
|
+
// kit vendor without one (its reference is per-page prose, as Tithe.ly's is)
|
|
6
|
+
// derives it here from its own declarations, through `manifest(vendor)`. The
|
|
7
|
+
// vendor stays the one operation list: what the typed client sends, what drift
|
|
8
|
+
// checks compare and what the hatch admits are the same declarations.
|
|
9
|
+
import { OperationIndex, } from "../providers/_shared/rest/operation-index.js";
|
|
10
|
+
import { manifest } from "./vendor.js";
|
|
11
|
+
const METHODS = ["get", "head", "post", "put", "patch", "delete"];
|
|
12
|
+
/** How the manifest renders a number carried in a query or form string. */
|
|
13
|
+
const NUMERIC_STRING = "^[+-]?\\d*\\.?\\d+(?:[Ee][+-]?\\d+)?$";
|
|
14
|
+
function record(value) {
|
|
15
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
16
|
+
}
|
|
17
|
+
/** Compact one manifest schema into the index's node form, resolving local references. */
|
|
18
|
+
function node(schema, components, seen = new Set()) {
|
|
19
|
+
const source = record(schema);
|
|
20
|
+
const ref = typeof source["$ref"] === "string" ? source["$ref"] : undefined;
|
|
21
|
+
if (ref !== undefined) {
|
|
22
|
+
const name = ref.replace(/^#\/components\/schemas\//, "");
|
|
23
|
+
// A recursive reference stops validating below itself rather than looping.
|
|
24
|
+
return seen.has(name) ? { x: 1 } : node(components[name], components, new Set([...seen, name]));
|
|
25
|
+
}
|
|
26
|
+
const out = {};
|
|
27
|
+
const type = source["type"];
|
|
28
|
+
// A numeric query or form string is a number to the caller; the transport stringifies it.
|
|
29
|
+
if (type === "string" && source["pattern"] === NUMERIC_STRING)
|
|
30
|
+
out.t = "number";
|
|
31
|
+
else if (typeof type === "string" || Array.isArray(type))
|
|
32
|
+
out.t = type;
|
|
33
|
+
if (Array.isArray(source["enum"]))
|
|
34
|
+
out.e = source["enum"];
|
|
35
|
+
if (typeof source["description"] === "string")
|
|
36
|
+
out.d = source["description"];
|
|
37
|
+
// A property's key annotation renders as an `allOf` member beside its encoded type.
|
|
38
|
+
for (const member of Array.isArray(source["allOf"]) ? source["allOf"] : []) {
|
|
39
|
+
if (out.d === undefined && typeof record(member)["description"] === "string")
|
|
40
|
+
out.d = record(member)["description"];
|
|
41
|
+
}
|
|
42
|
+
const branches = source["anyOf"] ?? source["oneOf"];
|
|
43
|
+
if (Array.isArray(branches))
|
|
44
|
+
out.a = branches.map((branch) => node(branch, components, seen));
|
|
45
|
+
const properties = record(source["properties"]);
|
|
46
|
+
if (Object.keys(properties).length > 0) {
|
|
47
|
+
out.p = Object.fromEntries(Object.entries(properties).map(([key, value]) => [key, node(value, components, seen)]));
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(source["required"]) && source["required"].length > 0)
|
|
50
|
+
out.r = source["required"];
|
|
51
|
+
if (source["items"] !== undefined)
|
|
52
|
+
out.i = node(source["items"], components, seen);
|
|
53
|
+
if (record(source["additionalProperties"]) === source["additionalProperties"]) {
|
|
54
|
+
out.m = node(source["additionalProperties"], components, seen);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/** Every property path a schema declares, local references resolved once each. */
|
|
59
|
+
function propertyPaths(schema, components, prefix = "", seen = new Set()) {
|
|
60
|
+
const source = record(schema);
|
|
61
|
+
const ref = typeof source["$ref"] === "string" ? source["$ref"].replace(/^#\/components\/schemas\//, "") : undefined;
|
|
62
|
+
if (ref !== undefined)
|
|
63
|
+
return seen.has(ref) ? [] : propertyPaths(components[ref], components, prefix, new Set([...seen, ref]));
|
|
64
|
+
const branches = [source["anyOf"], source["oneOf"], source["allOf"]].flatMap((list) => Array.isArray(list) ? list : []);
|
|
65
|
+
return [
|
|
66
|
+
...Object.entries(record(source["properties"])).flatMap(([name, child]) => [
|
|
67
|
+
`${prefix}${name}`,
|
|
68
|
+
...propertyPaths(child, components, `${prefix}${name}.`, seen),
|
|
69
|
+
]),
|
|
70
|
+
...(source["items"] === undefined
|
|
71
|
+
? []
|
|
72
|
+
: propertyPaths(source["items"], components, prefix ? `${prefix.slice(0, -1)}[].` : "[].", seen)),
|
|
73
|
+
...branches.flatMap((branch) => propertyPaths(branch, components, prefix, seen)),
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* An operation index over a kit vendor's own declarations: every operation's
|
|
78
|
+
* method, path, `operationId`, `Summary`, path and query parameters, and
|
|
79
|
+
* request body with its content type, plus the request and response
|
|
80
|
+
* properties `OperationIndex.fields` answers. `prefix` names the hatch tools in
|
|
81
|
+
* refusals; `revision` identifies the declared API version.
|
|
82
|
+
*/
|
|
83
|
+
export function vendorIndex(vendor, names) {
|
|
84
|
+
const document = manifest(vendor);
|
|
85
|
+
const components = record(record(document["components"])["schemas"]);
|
|
86
|
+
const ops = [];
|
|
87
|
+
const details = [];
|
|
88
|
+
const fields = [];
|
|
89
|
+
for (const [path, item] of Object.entries(record(document["paths"]))) {
|
|
90
|
+
for (const method of METHODS) {
|
|
91
|
+
const operation = record(record(item)[method]);
|
|
92
|
+
if (Object.keys(operation).length === 0)
|
|
93
|
+
continue;
|
|
94
|
+
const operationId = typeof operation["operationId"] === "string" ? operation["operationId"] : "";
|
|
95
|
+
const summary = typeof operation["summary"] === "string" ? operation["summary"] : "";
|
|
96
|
+
ops.push([method.toUpperCase(), path, operationId, summary, 0]);
|
|
97
|
+
const params = (Array.isArray(operation["parameters"]) ? operation["parameters"] : [])
|
|
98
|
+
.map(record)
|
|
99
|
+
.filter((param) => param["in"] === "path" || param["in"] === "query")
|
|
100
|
+
.map((param) => {
|
|
101
|
+
// The parameter row carries the description once, beside its schema.
|
|
102
|
+
const { d, ...schema } = node(param["schema"], components);
|
|
103
|
+
const description = typeof param["description"] === "string" ? param["description"] : d;
|
|
104
|
+
return [
|
|
105
|
+
String(param["name"]),
|
|
106
|
+
param["in"],
|
|
107
|
+
param["required"] === true ? 1 : 0,
|
|
108
|
+
schema,
|
|
109
|
+
...(description === undefined ? [] : [description]),
|
|
110
|
+
];
|
|
111
|
+
});
|
|
112
|
+
const requestBody = record(operation["requestBody"]);
|
|
113
|
+
const [contentType, media] = Object.entries(record(requestBody["content"]))[0] ?? [];
|
|
114
|
+
const body = contentType === undefined
|
|
115
|
+
? 0
|
|
116
|
+
: [contentType, node(record(media)["schema"], components), ...(requestBody["required"] === true ? [1] : [])];
|
|
117
|
+
details.push(params.length === 0 && body === 0 ? 0 : [params, body]);
|
|
118
|
+
const success = Object.entries(record(operation["responses"])).find(([status]) => status.startsWith("2"))?.[1];
|
|
119
|
+
const paths = (where, schema) => [...new Set(propertyPaths(schema, components))].map((path) => ({ in: where, path }));
|
|
120
|
+
fields.push([
|
|
121
|
+
...paths("response", record(record(record(success)["content"])["application/json"])["schema"]),
|
|
122
|
+
...paths("body", record(media)["schema"]),
|
|
123
|
+
]);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const data = {
|
|
127
|
+
source: `${names.title} kit vendor declarations`,
|
|
128
|
+
revision: names.revision,
|
|
129
|
+
digest: "",
|
|
130
|
+
version: names.revision,
|
|
131
|
+
servers: [],
|
|
132
|
+
tags: [""],
|
|
133
|
+
ops,
|
|
134
|
+
details: JSON.stringify({ d: [], o: details }),
|
|
135
|
+
fields,
|
|
136
|
+
};
|
|
137
|
+
return new OperationIndex(data, { vendor: names.prefix, title: names.title });
|
|
138
|
+
}
|