@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
package/dist/invocation-auth.js
CHANGED
|
@@ -1,31 +1,39 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { authCall, authFlow, registerAuth } from "./auth/native.js";
|
|
2
|
+
import { upwardStep } from "./upward.js";
|
|
3
|
+
import { runEdge } from "./runtime/run.js";
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
import { requestOwner } from "./runtime/request-scope.js";
|
|
6
|
+
import { cloneTool, sourceClassification } from "./tool-safety.js";
|
|
2
7
|
// Only built-in factories register local credential checks. This hook is not
|
|
3
8
|
// part of Connector, and a replacement callTool cannot inherit its proof.
|
|
4
9
|
const checks = new WeakMap();
|
|
5
10
|
export function registerInvocationAuth(connector, check) {
|
|
6
11
|
checks.set(connector, { callTool: connector.callTool, check });
|
|
7
12
|
}
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Local credential resolution only, before entering any connector call.
|
|
15
|
+
* `callTool` is the function the invocation will invoke, read once.
|
|
16
|
+
*/
|
|
17
|
+
export function resolveInvocationAuthEffect(connector, callTool, ctx) {
|
|
18
|
+
return Effect.suspend(() => {
|
|
19
|
+
const entry = checks.get(connector);
|
|
20
|
+
return entry?.callTool === callTool ? authCall(entry.check, [ctx]) : Effect.void;
|
|
21
|
+
});
|
|
13
22
|
}
|
|
14
|
-
const requests = new WeakMap();
|
|
15
23
|
function requestFor(scope) {
|
|
16
|
-
|
|
17
|
-
if (!facts) {
|
|
18
|
-
facts = { entered: [], authFailures: new Map(), retryReads: new Map() };
|
|
19
|
-
requests.set(scope, facts);
|
|
20
|
-
}
|
|
21
|
-
return facts;
|
|
24
|
+
return requestOwner(scope).retained.invocations;
|
|
22
25
|
}
|
|
23
26
|
/** Bind the verdict, annotation facts and schemas used for this call. */
|
|
24
27
|
export async function classificationDigest(definition) {
|
|
25
28
|
try {
|
|
26
|
-
const schema =
|
|
29
|
+
const schema = { inputSchema: definition.inputSchema, outputSchema: definition.outputSchema };
|
|
27
30
|
const annotations = Object.entries(definition.annotations ?? {}).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
28
|
-
const bytes = new TextEncoder().encode(JSON.stringify({
|
|
31
|
+
const bytes = new TextEncoder().encode(JSON.stringify({
|
|
32
|
+
classification: definition.classification,
|
|
33
|
+
source: sourceClassification(definition),
|
|
34
|
+
annotations,
|
|
35
|
+
schema,
|
|
36
|
+
}));
|
|
29
37
|
return Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
30
38
|
}
|
|
31
39
|
catch {
|
|
@@ -37,7 +45,7 @@ export async function classificationDigest(definition) {
|
|
|
37
45
|
export function recordCallEntry(scope, call, definition) {
|
|
38
46
|
// Snapshot synchronously. Recovery hashes this private copy only when it
|
|
39
47
|
// needs a replay binding, without changing ordinary admission or completion.
|
|
40
|
-
requestFor(scope).entered.push({ ...call, definition:
|
|
48
|
+
requestFor(scope).entered.push({ ...call, definition: cloneTool(definition) });
|
|
41
49
|
}
|
|
42
50
|
export function bindReplayReads(scope, reads) {
|
|
43
51
|
requestFor(scope).retryReads = new Map(reads.map((read) => [read.address, read.digest]));
|
|
@@ -50,13 +58,16 @@ export function recordAuthFailure(scope, connector, eligible) {
|
|
|
50
58
|
const failures = requestFor(scope).authFailures;
|
|
51
59
|
failures.set(connector, eligible && failures.get(connector) !== false);
|
|
52
60
|
}
|
|
53
|
-
export
|
|
61
|
+
export function authRecoveryFacts(scope, connector) {
|
|
62
|
+
return runEdge(authRecoveryFactsNative(scope, connector));
|
|
63
|
+
}
|
|
64
|
+
const authRecoveryFactsNative = /* @__PURE__ */ registerAuth(authRecoveryFacts, (scope, connector) => authFlow(function* () {
|
|
54
65
|
const facts = requestFor(scope);
|
|
55
66
|
const writeEntered = facts.entered.some((call) => call.classification === "write");
|
|
56
67
|
const reads = new Map();
|
|
57
68
|
let unsafeEntered = writeEntered;
|
|
58
69
|
for (const call of facts.entered) {
|
|
59
|
-
const digest =
|
|
70
|
+
const digest = yield* upwardStep(authCall(classificationDigest, [call.definition]));
|
|
60
71
|
unsafeEntered ||= !call.fresh || !digest || (reads.has(call.address) && reads.get(call.address) !== digest);
|
|
61
72
|
reads.set(call.address, digest);
|
|
62
73
|
}
|
|
@@ -66,4 +77,4 @@ export async function authRecoveryFacts(scope, connector) {
|
|
|
66
77
|
eligible: !unsafeEntered && facts.authFailures.get(connector) === true,
|
|
67
78
|
reads: [...reads].map(([address, digest]) => ({ address, digest })),
|
|
68
79
|
};
|
|
69
|
-
}
|
|
80
|
+
}));
|
package/dist/invocation.js
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { authCall, registerAuthMethod } from "./auth/native.js";
|
|
2
|
+
import { withAuthorizationHandoffEffect } from "./authorization-handoff.js";
|
|
2
3
|
import { hasControlCharacters } from "./tool-name.js";
|
|
3
|
-
import { writeRefusal } from "./tool-safety.js";
|
|
4
|
-
import { Cause, Effect, Exit } from "effect";
|
|
5
|
-
import { isCallAdmissionError } from "./call-admission.js";
|
|
6
|
-
import {
|
|
7
|
-
import { boundedEchoText, classificationCode, classifyCallError, ConnectorCallError, echoedCallArgs, framingError, } from "./errors.js";
|
|
4
|
+
import { cloneTool, isWriteLike, writeRefusal } from "./tool-safety.js";
|
|
5
|
+
import { Cause, Context, Effect, Exit } from "effect";
|
|
6
|
+
import { CallAdmissionError, isCallAdmissionError } from "./call-admission.js";
|
|
7
|
+
import { boundedEchoText, classificationCode, classifyCallError, classifyWithoutAdvice, ConnectorCallError, echoedCallArgs, framingError, keyResendAdviceOf, resendAdviceOf, withResendAdvice, } from "./errors.js";
|
|
8
8
|
import { downstreamValue } from "./mcp-result.js";
|
|
9
9
|
import { carryFailureFacts, classifiedFailure, failureRecord, logFailure, recordedToolName, } from "./operator-record.js";
|
|
10
10
|
import { splitAddress } from "./registry.js";
|
|
11
|
-
import { runEdge, withDeadlineEffect } from "./runtime/run.js";
|
|
11
|
+
import { causeError, runEdge, withDeadlineEffect } from "./runtime/run.js";
|
|
12
12
|
import { validateCatalogToolInput } from "./validate.js";
|
|
13
|
-
import { sentSecretsFor, sentSecretsForRequest
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
13
|
+
import { sentSecretsFor, sentSecretsForRequest } from "./sent-secrets.js";
|
|
14
|
+
import { recoveryRecord, recordRecoveryFacts } from "./call-recovery.js";
|
|
15
|
+
import { callFromContext, contextView, viewServices, Credentials } from "./runtime/call.js";
|
|
16
|
+
import { chargeCallBudget } from "./runtime/call-admission.js";
|
|
17
|
+
import { callEntry } from "./runtime/call-entry.js";
|
|
18
|
+
import { forgetProviderFailure, isKnownProviderFailure, isSettledWrite, keyProtectsCall, knownCallFailure, onlyAuxiliaryRequests, knownProviderFailure, openLogicalCall, } from "./provider-call.js";
|
|
19
|
+
import { classifyWriteOutcome, settleWrite, settleWriteResult } from "./write-outcome.js";
|
|
20
|
+
import { classificationDigest, recordAuthFailure, recordCallEntry, replayClassificationDigest, resolveInvocationAuthEffect, } from "./invocation-auth.js";
|
|
17
21
|
import { downstreamContinuation, recordDownstreamArgumentEcho, isDownstreamInputResult, assertDownstreamOutputSafe, } from "./downstream-input-context.js";
|
|
22
|
+
import { keepErrorName } from "./redaction-identity.js";
|
|
18
23
|
function defined(values) {
|
|
19
24
|
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
|
|
20
25
|
}
|
|
@@ -74,8 +79,8 @@ function isCallerCancellation(error, signal) {
|
|
|
74
79
|
}
|
|
75
80
|
/**
|
|
76
81
|
* A downstream MCP tool answered with `isError`. Classified exactly as the
|
|
77
|
-
* plain `Error`: untyped
|
|
78
|
-
* a
|
|
82
|
+
* plain `Error`: untyped and non-retryable. It is a read's answer; for a
|
|
83
|
+
* write it is a 2xx reply after which the handler may have applied it.
|
|
79
84
|
*/
|
|
80
85
|
class DownstreamToolError extends Error {
|
|
81
86
|
}
|
|
@@ -97,13 +102,18 @@ function assertRawMcpSuccess(kind, result) {
|
|
|
97
102
|
.join("") || "Downstream tool call failed"));
|
|
98
103
|
}
|
|
99
104
|
/**
|
|
100
|
-
* Whether a failed attempt is the downstream tool's own answer
|
|
101
|
-
* `isError
|
|
102
|
-
*
|
|
103
|
-
*
|
|
105
|
+
* Whether a failed attempt is the downstream tool's own answer. For a read:
|
|
106
|
+
* an MCP `isError`, or a boundary's known refusal/unsent failure. For a
|
|
107
|
+
* write, only refusal provenance that holds for the whole logical call
|
|
108
|
+
* (`knownCallFailure`); an `isError` arrives over a 2xx after the handler
|
|
109
|
+
* ran. A typed error alone is not proof: a body ceiling, refused redirect, or
|
|
110
|
+
* handler error may follow execution. Boundaries supply provenance, never
|
|
111
|
+
* wording.
|
|
104
112
|
*/
|
|
105
|
-
function answeredFailure(error) {
|
|
106
|
-
|
|
113
|
+
function answeredFailure(error, write, call) {
|
|
114
|
+
if (!write)
|
|
115
|
+
return error instanceof DownstreamToolError || isKnownProviderFailure(error);
|
|
116
|
+
return knownCallFailure(call, error);
|
|
107
117
|
}
|
|
108
118
|
export class InvocationFailure extends Error {
|
|
109
119
|
details;
|
|
@@ -131,6 +141,7 @@ export class InvocationFailure extends Error {
|
|
|
131
141
|
this.data = details;
|
|
132
142
|
}
|
|
133
143
|
}
|
|
144
|
+
keepErrorName(InvocationFailure, "InvocationFailure");
|
|
134
145
|
/**
|
|
135
146
|
* Shared downstream invocation engine. Address/catalog resolution is delegated
|
|
136
147
|
* to the request-local CatalogService; every remaining call semantic lives
|
|
@@ -149,6 +160,7 @@ export class InvocationService {
|
|
|
149
160
|
this.registry = registry;
|
|
150
161
|
this.catalog = catalog;
|
|
151
162
|
this.activity = activity;
|
|
163
|
+
void InvocationService.nativePrograms;
|
|
152
164
|
}
|
|
153
165
|
invoke(address, args, context) {
|
|
154
166
|
return runEdge(this.pipeline(address, args, context));
|
|
@@ -169,11 +181,20 @@ export class InvocationService {
|
|
|
169
181
|
let attempts = 0;
|
|
170
182
|
let resultBytes;
|
|
171
183
|
let dispatchedToConnector = false;
|
|
172
|
-
// The
|
|
173
|
-
|
|
174
|
-
|
|
184
|
+
// The logical call bridge; recovery is a separate invocation-owned record.
|
|
185
|
+
let logicalCall;
|
|
186
|
+
// The served class owns gating and presentation. Physical source-write
|
|
187
|
+
// provenance additionally owns failure settlement, after dispatch only.
|
|
188
|
+
const sentSourceWrite = () => logicalCall?.sent.some((request) => request.sourceWrite === true) === true;
|
|
189
|
+
const recoveryState = recoveryRecord();
|
|
175
190
|
let preInvocationAuthFailure = false;
|
|
176
191
|
let answered = false;
|
|
192
|
+
// A dispatched failure's advice to send it again, composed by enrich
|
|
193
|
+
// only once the outcome allows it (resendAdvice), and its advice to
|
|
194
|
+
// reuse an idempotency key, only where that key protects the whole
|
|
195
|
+
// logical call (keyResendAdvice, keyProtectsCall).
|
|
196
|
+
let failureAdvice;
|
|
197
|
+
let failureKeyAdvice;
|
|
177
198
|
const sentSecrets = sentSecretsForRequest(this.catalog.requestScope);
|
|
178
199
|
context.sentSecrets?.include(sentSecrets);
|
|
179
200
|
// A write gate's refusal that is no attempt; see WriteGateDecision.
|
|
@@ -207,7 +228,7 @@ export class InvocationService {
|
|
|
207
228
|
connectorId: identity.connectorId,
|
|
208
229
|
toolName,
|
|
209
230
|
address: `${identity.connectorId}.${toolName}`,
|
|
210
|
-
source: context.source,
|
|
231
|
+
source: context.activitySource ?? context.source,
|
|
211
232
|
outcome,
|
|
212
233
|
...(activityTarget?.connector.authScope === "personal" ? { personal: true } : {}),
|
|
213
234
|
durationMs: Date.now() - started,
|
|
@@ -220,20 +241,33 @@ export class InvocationService {
|
|
|
220
241
|
}),
|
|
221
242
|
});
|
|
222
243
|
};
|
|
223
|
-
const enrich = (error, target) => {
|
|
244
|
+
const enrich = (error, target, settled) => {
|
|
224
245
|
if (!target)
|
|
225
246
|
return error;
|
|
226
|
-
const recovery =
|
|
227
|
-
const
|
|
247
|
+
const recovery = recoveryState.facts;
|
|
248
|
+
const enteredWrite = dispatchedToConnector &&
|
|
249
|
+
((resolved !== undefined && resolved.definition.classification !== "read") || sentSourceWrite());
|
|
250
|
+
// A known answer alone is not dispatch evidence. Require an owned,
|
|
251
|
+
// complete ledger with a positively recorded auxiliary resource read;
|
|
252
|
+
// an empty ledger, custom entry or interruption proves nothing.
|
|
253
|
+
const mayHaveWritten = enteredWrite && (error.writeOutcome === "unknown" || !(answered && onlyAuxiliaryRequests(logicalCall)));
|
|
254
|
+
// A recorded key is a diagnostic; advice to reuse it needs proof that
|
|
255
|
+
// it protects the whole logical call, not one request inside it. Q8's
|
|
256
|
+
// settled verdict is final: no enricher adds advice to it.
|
|
257
|
+
const recoveryRetry = !settled && keyProtectsCall(logicalCall, recovery?.["idempotencyKey"])
|
|
228
258
|
? " A deliberate retry with uncertainCall.recovery.idempotencyKey and the exact original arguments cannot repeat the write while the vendor retains that key (Stripe keeps v1 keys at least 24 hours); after that, look the object up before retrying."
|
|
229
259
|
: "";
|
|
230
|
-
if (isTimeoutFailure(error) &&
|
|
260
|
+
if (isTimeoutFailure(error) && mayHaveWritten) {
|
|
261
|
+
// An empty ledger's known mark cannot settle this timeout. Keep
|
|
262
|
+
// program accounting conservative as well as its displayed code.
|
|
263
|
+
answered = false;
|
|
231
264
|
const echoed = argumentEcho;
|
|
232
265
|
return {
|
|
233
266
|
...error,
|
|
234
267
|
code: "write_outcome_unknown",
|
|
235
268
|
message: `${error.message} Its outcome is unknown. Check the target before repeating this call.`,
|
|
236
269
|
retryable: false,
|
|
270
|
+
...(sentSourceWrite() ? { writeOutcome: "unknown" } : {}),
|
|
237
271
|
connector: target.connector.id,
|
|
238
272
|
operation: `${target.connector.id}.${target.toolName}`,
|
|
239
273
|
uncertainCall: {
|
|
@@ -251,14 +285,35 @@ export class InvocationService {
|
|
|
251
285
|
: " The arguments could not be echoed safely; use the exact arguments you sent."),
|
|
252
286
|
};
|
|
253
287
|
}
|
|
288
|
+
if (mayHaveWritten &&
|
|
289
|
+
(error.code === "auth_required" || error.code === "downstream_oauth_required") &&
|
|
290
|
+
!logicalCall?.sent.some((request) => request.dispatched)) {
|
|
291
|
+
// An empty resource ledger cannot settle this auth failure. Keep
|
|
292
|
+
// actual resource-refusal provenance, but withdraw an empty ledger's
|
|
293
|
+
// known mark before composing advice and counting program writes.
|
|
294
|
+
answered = false;
|
|
295
|
+
forgetProviderFailure(error);
|
|
296
|
+
}
|
|
297
|
+
// A dispatched write without refusal provenance is unknown whatever
|
|
298
|
+
// its connector's code says, a custom connector's included: never
|
|
299
|
+
// retryable, and no advice below may tell the agent to retry it.
|
|
300
|
+
const unknownWrite = mayHaveWritten && classifyWriteOutcome({ ok: false, dispatched: true, answered, error }) === "unknown";
|
|
301
|
+
if (unknownWrite)
|
|
302
|
+
error = { ...error, retryable: false, writeOutcome: "unknown" };
|
|
303
|
+
// The failure's advice to send it again was kept apart until now, and
|
|
304
|
+
// joins its message only where the outcome allows it: never on a write
|
|
305
|
+
// whose outcome is unknown, whatever redaction did to the message.
|
|
306
|
+
// Key-reuse advice also needs its key to protect the logical call.
|
|
307
|
+
else if (!settled) {
|
|
308
|
+
if (failureKeyAdvice && keyProtectsCall(logicalCall, failureKeyAdvice.key))
|
|
309
|
+
error = { ...error, message: withResendAdvice(error.message, failureKeyAdvice.advice) };
|
|
310
|
+
if (failureAdvice !== undefined)
|
|
311
|
+
error = { ...error, message: withResendAdvice(error.message, failureAdvice) };
|
|
312
|
+
}
|
|
254
313
|
// Any other dispatched write whose outcome is unknown (a lost response
|
|
255
314
|
// body, a 5xx, an unreadable reply) keeps the facts its connector
|
|
256
315
|
// recorded before dispatch, here and through program write accounting.
|
|
257
|
-
if (recovery &&
|
|
258
|
-
!error.uncertainCall &&
|
|
259
|
-
dispatchedToConnector &&
|
|
260
|
-
resolved?.definition.classification === "write" &&
|
|
261
|
-
classifyWriteOutcome({ ok: false, dispatched: true, answered, error }) === "unknown") {
|
|
316
|
+
if (recovery && !error.uncertainCall && unknownWrite) {
|
|
262
317
|
const echoed = argumentEcho;
|
|
263
318
|
error = {
|
|
264
319
|
...error,
|
|
@@ -274,11 +329,16 @@ export class InvocationService {
|
|
|
274
329
|
if (error.code === "auth_required" && target.connector.startAuth) {
|
|
275
330
|
error = { ...error, code: "downstream_oauth_required" };
|
|
276
331
|
}
|
|
277
|
-
const enteredWrite = resolved?.definition.classification === "write" && dispatchedToConnector;
|
|
278
332
|
switch (error.code) {
|
|
279
333
|
case "input_required_unsupported":
|
|
280
334
|
if (context.processInputRequired)
|
|
281
335
|
return error;
|
|
336
|
+
// Resending could apply the write twice: reconcile first.
|
|
337
|
+
if (unknownWrite)
|
|
338
|
+
return {
|
|
339
|
+
...error,
|
|
340
|
+
retry: "Do not resend this write. Re-read its target to check whether it took effect before any fresh call.",
|
|
341
|
+
};
|
|
282
342
|
return {
|
|
283
343
|
...error,
|
|
284
344
|
nextAction: {
|
|
@@ -293,6 +353,8 @@ export class InvocationService {
|
|
|
293
353
|
},
|
|
294
354
|
};
|
|
295
355
|
case "destructive_tool_requires_approval": {
|
|
356
|
+
if (settled)
|
|
357
|
+
return error;
|
|
296
358
|
const echoed = argumentEcho;
|
|
297
359
|
return {
|
|
298
360
|
...error,
|
|
@@ -318,17 +380,22 @@ export class InvocationService {
|
|
|
318
380
|
connector: target.connector.id,
|
|
319
381
|
operation: `${target.connector.id}.${target.toolName}`,
|
|
320
382
|
recovery: recoveryMode(this.registry, target.connector, this.catalog.baseUrl),
|
|
321
|
-
...(
|
|
383
|
+
...(unknownWrite ? { reconciliationRequired: true, retryable: false } : {}),
|
|
322
384
|
nextAction: {
|
|
323
385
|
tool: "authorize_connector",
|
|
324
386
|
arguments: { connector: target.connector.id },
|
|
325
387
|
operatorHandoff: "Give the URL and instructions it returns to the operator.",
|
|
326
388
|
},
|
|
327
|
-
retry:
|
|
389
|
+
retry: unknownWrite
|
|
328
390
|
? "This write may have partially run. Reconcile its target before retrying after the operator completes recovery."
|
|
329
|
-
:
|
|
391
|
+
: enteredWrite && logicalCall?.sent.some((request) => request.dispatched)
|
|
392
|
+
? `Fix the credentials, then retry ${target.connector.id}.${target.toolName}.`
|
|
393
|
+
: `Retry ${target.connector.id}.${target.toolName} after the operator completes recovery.`,
|
|
330
394
|
};
|
|
331
395
|
case "provider_permission_denied":
|
|
396
|
+
// A write whose outcome is unknown is never told to retry.
|
|
397
|
+
if (error.writeOutcome === "unknown")
|
|
398
|
+
return error;
|
|
332
399
|
return {
|
|
333
400
|
...error,
|
|
334
401
|
connector: target.connector.id,
|
|
@@ -336,7 +403,7 @@ export class InvocationService {
|
|
|
336
403
|
retry: "Ask the provider's resource owner or administrator to grant the required permission or scope, then retry. Repeating the call or reconnecting alone will not grant access.",
|
|
337
404
|
};
|
|
338
405
|
case "invalid_args":
|
|
339
|
-
if (!error.validation)
|
|
406
|
+
if (!error.validation || error.writeOutcome === "unknown")
|
|
340
407
|
return error;
|
|
341
408
|
return {
|
|
342
409
|
...error,
|
|
@@ -354,13 +421,27 @@ export class InvocationService {
|
|
|
354
421
|
};
|
|
355
422
|
let stage = "catalog";
|
|
356
423
|
const failed = (error) => {
|
|
424
|
+
// Q8 (settleWrite): this invocation's deadline or its caller cut off
|
|
425
|
+
// a write. No connector failure arrives, so the logical call's
|
|
426
|
+
// recorded requests alone decide whether a 401 left it unknown.
|
|
427
|
+
if (dispatchedToConnector &&
|
|
428
|
+
(resolved?.definition.classification !== "read" || sentSourceWrite()) &&
|
|
429
|
+
(error.code === "cancelled" || isTimeoutFailure(error))) {
|
|
430
|
+
const departed = settleWrite(logicalCall?.sent ?? [], undefined, true);
|
|
431
|
+
if (departed !== undefined) {
|
|
432
|
+
failureAdvice = failureKeyAdvice = undefined;
|
|
433
|
+
error = carryFailureFacts(departed, classifyWithoutAdvice(sentSecrets.redact(departed)));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
357
436
|
const diagnostics = timing();
|
|
358
437
|
const target = resolved ?? activityTarget;
|
|
359
438
|
if (target && (error.code === "auth_required" || error.code === "downstream_oauth_required")) {
|
|
360
|
-
recordAuthFailure(this.catalog.requestScope, target.connector.id, (resolved
|
|
439
|
+
recordAuthFailure(this.catalog.requestScope, target.connector.id, (resolved !== undefined && !isWriteLike(resolved.definition) && resolved.classificationFresh === true) ||
|
|
361
440
|
preInvocationAuthFailure ||
|
|
362
441
|
(!resolved && context.source === "call_tool"));
|
|
363
442
|
}
|
|
443
|
+
// Read before any step below rebuilds the details: Q8's verdict is final.
|
|
444
|
+
const settled = isSettledWrite(error);
|
|
364
445
|
// Attach host deadline facts before write recovery changes the code.
|
|
365
446
|
if (isTimeoutFailure(error)) {
|
|
366
447
|
const elapsedMs = Date.now() - started;
|
|
@@ -372,7 +453,7 @@ export class InvocationService {
|
|
|
372
453
|
};
|
|
373
454
|
}
|
|
374
455
|
// Downstream diagnostics were redacted under payload rules where they entered.
|
|
375
|
-
const details = sentSecrets.redact(enrich(error, target), "envelope");
|
|
456
|
+
const details = carryFailureFacts(error, sentSecrets.redact(enrich(error, target, settled), "envelope"));
|
|
376
457
|
const outcome = () => ({
|
|
377
458
|
ok: false,
|
|
378
459
|
durationMs: Date.now() - started,
|
|
@@ -395,7 +476,7 @@ export class InvocationService {
|
|
|
395
476
|
connector: target.connector.id,
|
|
396
477
|
// Named only when the catalog listed it (src/operator-record.ts).
|
|
397
478
|
tool: resolved?.definition,
|
|
398
|
-
source: context.source,
|
|
479
|
+
source: context.activitySource ?? context.source,
|
|
399
480
|
attempts,
|
|
400
481
|
durationMs: Date.now() - started,
|
|
401
482
|
// Every refusal reaching `failed` is one connecta built: a
|
|
@@ -449,6 +530,7 @@ export class InvocationService {
|
|
|
449
530
|
argumentEcho = echoedCallArgs(args ?? {}, target.definition.inputSchema);
|
|
450
531
|
sentSecrets.arguments(args ?? {}, target.definition.inputSchema);
|
|
451
532
|
const write = target.definition.classification !== "read";
|
|
533
|
+
const writeLike = isWriteLike(target.definition);
|
|
452
534
|
const canonicalAddress = `${target.connector.id}.${target.toolName}`;
|
|
453
535
|
const input = context.source !== "execute_code"
|
|
454
536
|
? downstreamContinuation(this.catalog.requestScope, target.connector.id, canonicalAddress)
|
|
@@ -457,16 +539,18 @@ export class InvocationService {
|
|
|
457
539
|
recordDownstreamArgumentEcho(this.catalog.requestScope, argumentEcho);
|
|
458
540
|
const expectedDigest = replayClassificationDigest(this.catalog.requestScope, canonicalAddress);
|
|
459
541
|
if (expectedDigest !== undefined &&
|
|
460
|
-
(
|
|
542
|
+
(writeLike ||
|
|
543
|
+
target.classificationFresh !== true ||
|
|
461
544
|
(yield* Effect.promise(() => classificationDigest(target.definition))) !== expectedDigest)) {
|
|
462
545
|
return {
|
|
463
546
|
...framingError("auth_replay_refused", "An entered read no longer has the same fresh classification. Reconcile its target before starting a new request."),
|
|
464
547
|
reconciliationRequired: true,
|
|
465
548
|
};
|
|
466
549
|
}
|
|
467
|
-
const
|
|
550
|
+
const connectorReadOnly = target.connector.readOnly === true;
|
|
551
|
+
const refusal = writeRefusal(target.definition.classification, context.source, context.readOnly || connectorReadOnly);
|
|
468
552
|
if (refusal === "read_only_endpoint") {
|
|
469
|
-
return framingError("read_only_endpoint", `Tool "${canonicalAddress}" is a write, and this endpoint is read-only. Nothing here can send it.`);
|
|
553
|
+
return framingError("read_only_endpoint", `Tool "${canonicalAddress}" is a write, and ${connectorReadOnly ? `connector "${target.connector.id}"` : "this endpoint"} is read-only. Nothing here can send it.`);
|
|
470
554
|
}
|
|
471
555
|
if (refusal === "destructive_tool_requires_approval") {
|
|
472
556
|
return framingError("destructive_tool_requires_approval", `Tool "${canonicalAddress}" is a write. Invoke it through call_destructive_tool so the MCP host can request approval.`);
|
|
@@ -485,7 +569,8 @@ export class InvocationService {
|
|
|
485
569
|
if (invalid)
|
|
486
570
|
return classifyCallError(invalid);
|
|
487
571
|
}
|
|
488
|
-
|
|
572
|
+
// Source writes keep program write accounting; only effective writes spend W10 budget.
|
|
573
|
+
if (writeLike && context.beforeWrite) {
|
|
489
574
|
const decision = yield* context.beforeWrite(target, args ?? {});
|
|
490
575
|
if (decision.kind === "refuse") {
|
|
491
576
|
unrecorded = decision.unrecorded === true;
|
|
@@ -495,44 +580,71 @@ export class InvocationService {
|
|
|
495
580
|
if (callSignal?.aborted)
|
|
496
581
|
return yield* Effect.fail(callSignal.reason);
|
|
497
582
|
attempts = 1;
|
|
498
|
-
const call =
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
583
|
+
const call = (permit) => Effect.gen({ self: this }, function* () {
|
|
584
|
+
const operationContext = this.registry.contextFor(target.connector.id, this.catalog.baseUrl, this.catalog.requestScope, defined({ signal: callSignal, timeoutMs: context.timeoutMs, defer: this.catalog.defer }));
|
|
585
|
+
const lease = callFromContext(operationContext, write ? "write" : "read", {
|
|
586
|
+
admitted: true,
|
|
587
|
+
// An invocation deadline aborts when its call finishes. A
|
|
588
|
+
// request-owned auxiliary must retain only request cancellation.
|
|
589
|
+
requestSourceSignal: context.requestSignal,
|
|
590
|
+
served: write ? "write" : "read",
|
|
591
|
+
charge: (signal) => runEdge(chargeCallBudget(permit, signal)),
|
|
592
|
+
chargeEffect: (signal) => chargeCallBudget(permit, signal),
|
|
593
|
+
chargeClosed: () => new CallAdmissionError("closed", "connector_call_failed", "This call has ended; its admission permit can no longer be charged."),
|
|
594
|
+
record: (facts) => recordRecoveryFacts(recoveryState, facts),
|
|
595
|
+
});
|
|
596
|
+
yield* Effect.addFinalizer(() => Effect.sync(lease.close));
|
|
597
|
+
const connectorContext = contextView(lease, operationContext);
|
|
502
598
|
sentSecrets.include(sentSecretsFor(connectorContext));
|
|
503
|
-
|
|
504
|
-
|
|
599
|
+
// The one object and function this call invokes, read once: its
|
|
600
|
+
// logical call, auth check, and dispatch all name them, so a
|
|
601
|
+
// connector that reassigns its own callTool mid-call changes none.
|
|
602
|
+
const connector = target.connector;
|
|
603
|
+
const callTool = connector.callTool;
|
|
604
|
+
logicalCall = openLogicalCall(connectorContext, connector, callTool);
|
|
605
|
+
yield* Effect.gen(function* () {
|
|
505
606
|
if (target.connector.credential) {
|
|
506
607
|
if (!connectorContext.credential) {
|
|
507
608
|
throw new ConnectorCallError("auth_required", "Operator-managed credential storage is not configured. Call " +
|
|
508
609
|
`authorize_connector({ connector: "${target.connector.id}" }).`);
|
|
509
610
|
}
|
|
510
|
-
if (!(
|
|
611
|
+
if (!(yield* Context.get(lease.services, Credentials).values)) {
|
|
511
612
|
throw new ConnectorCallError("auth_required", `Connector "${target.connector.id}" has no stored credential. Call authorize_connector.`);
|
|
512
613
|
}
|
|
513
614
|
}
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const code = classifyCallError(error).code;
|
|
615
|
+
yield* resolveInvocationAuthEffect(connector, callTool, connectorContext);
|
|
616
|
+
}).pipe(Effect.onError((cause) => Effect.sync(() => {
|
|
617
|
+
const code = classifyCallError(causeError(cause, connectorContext.signal)).code;
|
|
518
618
|
preInvocationAuthFailure = code === "auth_required" || code === "downstream_oauth_required";
|
|
519
|
-
|
|
520
|
-
}
|
|
619
|
+
})));
|
|
521
620
|
// Cancellation can arrive during admission or context construction.
|
|
522
621
|
if (admissionSignal?.aborted)
|
|
523
622
|
throw admissionSignal.reason;
|
|
524
623
|
dispatchedToConnector = true;
|
|
525
624
|
recordCallEntry(this.catalog.requestScope, {
|
|
526
625
|
address: canonicalAddress,
|
|
527
|
-
classification:
|
|
626
|
+
classification: writeLike ? "write" : "read",
|
|
528
627
|
fresh: target.classificationFresh === true,
|
|
529
628
|
}, target.definition);
|
|
530
|
-
|
|
531
|
-
//
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
629
|
+
// Reflect.apply, never the function's own `call` or `apply`
|
|
630
|
+
// property, which anyone holding the function could replace.
|
|
631
|
+
const options = { definition: cloneTool(target.definition), ...defined({ input }) };
|
|
632
|
+
const entry = callEntry(connector, callTool);
|
|
633
|
+
if (entry)
|
|
634
|
+
return yield* entry(target.toolName, args ?? {}, options).pipe(Effect.provideContext(viewServices(connectorContext)));
|
|
635
|
+
return yield* Effect.tryPromise({
|
|
636
|
+
try: () => Reflect.apply(callTool, connector, [
|
|
637
|
+
target.toolName,
|
|
638
|
+
args ?? {},
|
|
639
|
+
connectorContext,
|
|
640
|
+
// The connector may retain or mutate its definition. Keep the
|
|
641
|
+
// invocation's classification and schema private, even during
|
|
642
|
+
// this call, and give every dispatch its own deep copy.
|
|
643
|
+
options,
|
|
644
|
+
]),
|
|
645
|
+
catch: (error) => error,
|
|
646
|
+
});
|
|
647
|
+
});
|
|
536
648
|
// The permit belongs to this scope, so success, failure, and the
|
|
537
649
|
// deadline's interruption all release it. Interruption stops the
|
|
538
650
|
// wait instead of waiting for the connector, so an uncooperative one
|
|
@@ -545,17 +657,16 @@ export class InvocationService {
|
|
|
545
657
|
stage = "downstream";
|
|
546
658
|
const reply = yield* timed((elapsed) => {
|
|
547
659
|
connectorMs += elapsed;
|
|
548
|
-
},
|
|
549
|
-
try: () => Promise.resolve(call(permit)),
|
|
550
|
-
catch: (error) => error,
|
|
551
|
-
}));
|
|
660
|
+
}, call(permit));
|
|
552
661
|
if (target.connector.kind === "mcp" && isDownstreamInputResult(reply)) {
|
|
662
|
+
// The downstream suspended for input. A handler asks for
|
|
663
|
+
// input from inside its run, so a write may already have
|
|
664
|
+
// applied: only a read's suspension is a known answer.
|
|
553
665
|
if (!context.processInputRequired)
|
|
554
|
-
throw
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
});
|
|
666
|
+
throw write
|
|
667
|
+
? new ConnectorCallError("input_required_unsupported", "The downstream returned input_required after this write was sent, and this call cannot provide input. Whether the write took effect is unknown.", { writeOutcome: "unknown" })
|
|
668
|
+
: knownProviderFailure(new ConnectorCallError("input_required_unsupported", "The downstream returned input_required. Use the equivalent direct MCP call to provide input."));
|
|
669
|
+
const value = yield* authCall(context.processInputRequired, [reply, target, sentSecrets]);
|
|
559
670
|
return { inputRequired: true, value };
|
|
560
671
|
}
|
|
561
672
|
assertDownstreamOutputSafe(this.catalog.requestScope, reply);
|
|
@@ -571,11 +682,23 @@ export class InvocationService {
|
|
|
571
682
|
return yield* Effect.fail(callSignal.reason);
|
|
572
683
|
if (!dispatchedToConnector && admissionSignal?.aborted)
|
|
573
684
|
return callerCancelledDetails();
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
685
|
+
const thrown = Cause.squash(attempt.cause);
|
|
686
|
+
if (isCallerCancellation(thrown, context.requestSignal))
|
|
687
|
+
return callerCancelledDetails();
|
|
688
|
+
const failureWrite = write || sentSourceWrite();
|
|
689
|
+
answered = answeredFailure(thrown, failureWrite, logicalCall);
|
|
690
|
+
// Q8 is decided here for every served write, whatever its
|
|
691
|
+
// connector made of the 401: over the logical call's whole ledger.
|
|
692
|
+
const attemptError = failureWrite ? settleWrite(logicalCall?.sent ?? [], thrown, answered) : thrown;
|
|
693
|
+
if (attemptError !== thrown)
|
|
694
|
+
answered = false;
|
|
695
|
+
failureAdvice = resendAdviceOf(attemptError);
|
|
696
|
+
failureKeyAdvice = keyResendAdviceOf(attemptError);
|
|
697
|
+
const details = carryFailureFacts(attemptError, classifyWithoutAdvice(sentSecrets.redact(attemptError)));
|
|
698
|
+
// An inner call's verdict that does not hold for this one is withdrawn.
|
|
699
|
+
if (failureWrite && !answered)
|
|
700
|
+
forgetProviderFailure(details);
|
|
701
|
+
return details;
|
|
579
702
|
}
|
|
580
703
|
if ("inputRequired" in attempt.value) {
|
|
581
704
|
inputRequiredValue = { value: attempt.value.value };
|
|
@@ -612,9 +735,11 @@ export class InvocationService {
|
|
|
612
735
|
: dispatch());
|
|
613
736
|
if (Exit.isFailure(dispatched)) {
|
|
614
737
|
const failure = Cause.squash(dispatched.cause);
|
|
738
|
+
failureAdvice = resendAdviceOf(failure);
|
|
739
|
+
failureKeyAdvice = keyResendAdviceOf(failure);
|
|
615
740
|
const details = context.requestSignal?.aborted
|
|
616
741
|
? callerCancelledDetails()
|
|
617
|
-
: carryFailureFacts(failure,
|
|
742
|
+
: carryFailureFacts(failure, classifyWithoutAdvice(sentSecrets.redact(failure)));
|
|
618
743
|
return failed(details);
|
|
619
744
|
}
|
|
620
745
|
if (dispatched.value)
|
|
@@ -624,14 +749,21 @@ export class InvocationService {
|
|
|
624
749
|
if (!completed) {
|
|
625
750
|
return yield* Effect.die(new Error("Invocation completed without a resolved tool"));
|
|
626
751
|
}
|
|
752
|
+
// Q8 settles a result too: a write an undeclared 401 left unknown is no
|
|
753
|
+
// success, whatever its handler made of the 401, and its result is not
|
|
754
|
+
// processed.
|
|
755
|
+
if (dispatchedToConnector && (completed.definition.classification !== "read" || sentSourceWrite())) {
|
|
756
|
+
const unknown = settleWriteResult(logicalCall?.sent ?? []);
|
|
757
|
+
if (unknown) {
|
|
758
|
+
failureAdvice = failureKeyAdvice = undefined;
|
|
759
|
+
return failed(carryFailureFacts(unknown, classifyWithoutAdvice(sentSecrets.redact(unknown))));
|
|
760
|
+
}
|
|
761
|
+
}
|
|
627
762
|
const processResult = context.processResult;
|
|
628
763
|
const processing = inputRequiredValue
|
|
629
764
|
? Effect.succeed(inputRequiredValue.value)
|
|
630
765
|
: processResult
|
|
631
|
-
?
|
|
632
|
-
try: () => Promise.resolve(processResult(sentSecrets.redact(result), completed, sentSecrets, valueFormat, rawResult)),
|
|
633
|
-
catch: (error) => error,
|
|
634
|
-
})
|
|
766
|
+
? authCall(processResult, [sentSecrets.redact(result), completed, sentSecrets, valueFormat, rawResult])
|
|
635
767
|
: Effect.succeed(result);
|
|
636
768
|
const processed = yield* Effect.exit(timed((elapsed) => {
|
|
637
769
|
resultProcessingMs += elapsed;
|
|
@@ -670,9 +802,11 @@ export class InvocationService {
|
|
|
670
802
|
}
|
|
671
803
|
}).pipe(Effect.flatMap((outcome) => outcome.ok
|
|
672
804
|
? Effect.succeed(outcome)
|
|
673
|
-
: Effect.
|
|
674
|
-
...outcome,
|
|
675
|
-
error: await withAuthorizationHandoff(this.catalog.requestScope, outcome.error),
|
|
676
|
-
}))));
|
|
805
|
+
: withAuthorizationHandoffEffect(this.catalog.requestScope, outcome.error).pipe(Effect.map((error) => ({ ...outcome, error })))));
|
|
677
806
|
}
|
|
807
|
+
static nativePrograms = /* @__PURE__ */ (() => {
|
|
808
|
+
registerAuthMethod(InvocationService.prototype, "invoke", function (address, args, context) {
|
|
809
|
+
return this.pipeline(address, args, context);
|
|
810
|
+
}, InvocationService.prototype.invoke);
|
|
811
|
+
})();
|
|
678
812
|
}
|