@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/meta-tools.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { upwardStep } from "./upward.js";
|
|
2
|
+
import { authCall, authInvoke, authFlow, authMethod, authStorage, registerAuth } from "./auth/native.js";
|
|
3
|
+
import { registryProgram } from "./runtime/registry.js";
|
|
4
|
+
import { runEdge } from "./runtime/run.js";
|
|
5
|
+
import { authorizationHandoff, authorizeConnector, bindAuthorizationHandoffs } from "./authorization-handoff.js";
|
|
2
6
|
import { bindMcpClient } from "./mcp-client-context.js";
|
|
3
7
|
import { CONFIG_DEFAULTS } from "./config-defaults.js";
|
|
4
|
-
import
|
|
8
|
+
import * as z from "zod";
|
|
5
9
|
import { advertisedSchema } from "./advertised-schema.js";
|
|
6
10
|
import { boundedDiscoveryText, CatalogService, DEFAULT_SEARCH_LIMIT, DiscoveryPolicyError, flatSearchResult, MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT, } from "./catalog-service.js";
|
|
7
11
|
import { resolveDiscoveryConcurrency } from "./concurrency.js";
|
|
@@ -43,24 +47,26 @@ function discoveryErrorResult(error) {
|
|
|
43
47
|
result.isError = true;
|
|
44
48
|
return result;
|
|
45
49
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
const discoveryResult = function (operation, hint) {
|
|
51
|
+
return authFlow(function* () {
|
|
52
|
+
try {
|
|
53
|
+
const value = yield* upwardStep(authCall(operation, []));
|
|
54
|
+
const text = boundedDiscoveryText(value, hint);
|
|
55
|
+
const result = jsonResult(value, text);
|
|
56
|
+
const bytes = enc.encode(JSON.stringify(result)).length;
|
|
57
|
+
if (bytes > MAX_DISCOVERY_RESULT_BYTES) {
|
|
58
|
+
throw new DiscoveryPolicyError("result_too_large", `Discovery result is ${bytes} UTF-8 bytes, over the ${MAX_DISCOVERY_RESULT_BYTES}-byte ceiling. ${hint}`);
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
54
61
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (err instanceof DiscoveryPolicyError) {
|
|
64
|
+
return discoveryErrorResult(err);
|
|
65
|
+
}
|
|
66
|
+
throw err;
|
|
60
67
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
68
|
+
});
|
|
69
|
+
};
|
|
64
70
|
/** True if `b` is a UTF-8 continuation byte (0b10xxxxxx). */
|
|
65
71
|
function isContinuationByte(b) {
|
|
66
72
|
return b !== undefined && (b & 0xc0) === 0x80;
|
|
@@ -84,7 +90,7 @@ function isValidResultOffset(value) {
|
|
|
84
90
|
* (issue #38). An offset at or past `bytes.length` is left alone — there is no
|
|
85
91
|
* character there to split.
|
|
86
92
|
*/
|
|
87
|
-
|
|
93
|
+
function alignStartToCharBoundary(bytes, offset) {
|
|
88
94
|
let o = offset;
|
|
89
95
|
while (o > 0 && isContinuationByte(bytes[o]))
|
|
90
96
|
o--;
|
|
@@ -92,7 +98,7 @@ export function alignStartToCharBoundary(bytes, offset) {
|
|
|
92
98
|
}
|
|
93
99
|
/** End boundary for UTF-8-safe, forward-progressing result pages. See
|
|
94
100
|
* documentation/meta-tools.md#result-representation. */
|
|
95
|
-
|
|
101
|
+
function alignEndToCharBoundary(bytes, offset, end, total) {
|
|
96
102
|
if (end >= total)
|
|
97
103
|
return total;
|
|
98
104
|
// A window that reaches no further than `offset` yields no bytes and no
|
|
@@ -172,6 +178,14 @@ function isWellFormedText(text) {
|
|
|
172
178
|
*/
|
|
173
179
|
const READ_REDUCE_FIRST = "To find something specific, repeat this read inside execute_code with connecta.call and filter or search the result there, returning only what matters";
|
|
174
180
|
function pagingHint(results, totalBytes, preview) {
|
|
181
|
+
if (results.pagingTool === "read_result") {
|
|
182
|
+
const shown = preview.kind === "prefix"
|
|
183
|
+
? `Bytes 0-${preview.bytes} of ${totalBytes} follow; page the rest`
|
|
184
|
+
: preview.kind === "text-of-envelope"
|
|
185
|
+
? `Its text follows; page the full ${totalBytes}-byte content array as JSON`
|
|
186
|
+
: `Page all ${totalBytes} bytes`;
|
|
187
|
+
return `${results.write ? WRITE_ALREADY_RAN + " " : ""}${shown} with read_result using nextAction.`;
|
|
188
|
+
}
|
|
175
189
|
if (!results.write) {
|
|
176
190
|
if (results.binding.program)
|
|
177
191
|
return "filter/map/slice data inside execute_code and return only what you need; page this completed return with connecta.result using nextAction.";
|
|
@@ -199,136 +213,155 @@ function pagingHint(results, totalBytes, preview) {
|
|
|
199
213
|
* documentation/meta-tools.md#result-representation). It is one compact JSON
|
|
200
214
|
* line with no raw newline, so the preview starts after the first `\n`.
|
|
201
215
|
*/
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// Base64 permits byte-range decoding, and splitting the envelope across
|
|
216
|
-
// keys keeps a page's storage read proportional to the page instead of to
|
|
217
|
-
// the whole result (issue #540). Chunk 0 carries the header; the connecta.result
|
|
218
|
-
// reader below maps a byte offset back to chunk index and base64 quad.
|
|
219
|
-
const chunkBytes = resultChunkBytes(totalBytes);
|
|
220
|
-
const binding = base64Of(enc.encode(JSON.stringify(results.binding)));
|
|
221
|
-
const chunks = [`${RESULT_ENVELOPE_V4}${binding}:${totalBytes}:${chunkBytes}:${results.cap}:`];
|
|
222
|
-
for (let offset = 0; offset < bytes.length; offset += chunkBytes) {
|
|
223
|
-
const chunk = base64Of(bytes.subarray(offset, offset + chunkBytes));
|
|
224
|
-
if (offset === 0)
|
|
225
|
-
chunks[0] += chunk;
|
|
226
|
-
else
|
|
227
|
-
chunks.push(chunk);
|
|
228
|
-
}
|
|
229
|
-
if (!(await results.set(id, chunks, RESULT_TTL_SECONDS))) {
|
|
230
|
-
throw new Error("Result stash capacity exhausted");
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
catch {
|
|
234
|
-
// Paging is advisory after a completed call, including an approved write.
|
|
235
|
-
// Neither backend prose nor a retry hint belongs in this successful result.
|
|
216
|
+
const stashResult = function (bytes, results, preview, rawText) {
|
|
217
|
+
return authFlow(function* () {
|
|
218
|
+
const totalBytes = bytes.length;
|
|
219
|
+
// Direct pages promise offsets into the original UTF-8 text. JSON-escaping
|
|
220
|
+
// raw text would change that contract; never stash the encoder's replacement
|
|
221
|
+
// characters as if they were the completed call's output.
|
|
222
|
+
if (rawText !== undefined && !isWellFormedText(rawText))
|
|
223
|
+
return {
|
|
224
|
+
truncated: true,
|
|
225
|
+
totalBytes,
|
|
226
|
+
hint: `${results.write ? WRITE_ALREADY_RAN + " " : ""}Paging is unavailable: text contains unpaired surrogates and can't be paged as text; request the value as JSON (value mode or a JSON-returning call) to recover it.`,
|
|
227
|
+
};
|
|
228
|
+
const id = crypto.randomUUID();
|
|
236
229
|
try {
|
|
237
|
-
|
|
230
|
+
// Base64 permits byte-range decoding, and splitting the envelope across
|
|
231
|
+
// keys keeps a page's storage read proportional to the page instead of to
|
|
232
|
+
// the whole result (issue #540). Chunk 0 carries the header; the connecta.result
|
|
233
|
+
// reader below maps a byte offset back to chunk index and base64 quad.
|
|
234
|
+
const chunkBytes = resultChunkBytes(totalBytes);
|
|
235
|
+
const binding = base64Of(enc.encode(JSON.stringify(results.binding)));
|
|
236
|
+
const chunks = [`${RESULT_ENVELOPE_V4}${binding}:${totalBytes}:${chunkBytes}:${results.cap}:`];
|
|
237
|
+
for (let offset = 0; offset < bytes.length; offset += chunkBytes) {
|
|
238
|
+
const chunk = base64Of(bytes.subarray(offset, offset + chunkBytes));
|
|
239
|
+
if (offset === 0)
|
|
240
|
+
chunks[0] += chunk;
|
|
241
|
+
else
|
|
242
|
+
chunks.push(chunk);
|
|
243
|
+
}
|
|
244
|
+
if (!(yield* upwardStep(authInvoke(results, "set", [id, chunks, RESULT_TTL_SECONDS])))) {
|
|
245
|
+
throw new Error("Result stash capacity exhausted");
|
|
246
|
+
}
|
|
238
247
|
}
|
|
239
248
|
catch {
|
|
240
|
-
//
|
|
249
|
+
// Paging is advisory after a completed call, including an approved write.
|
|
250
|
+
// Neither backend prose nor a retry hint belongs in this successful result.
|
|
251
|
+
try {
|
|
252
|
+
results.warn();
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// Logging cannot change the call either.
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
truncated: true,
|
|
259
|
+
totalBytes,
|
|
260
|
+
hint: results.write
|
|
261
|
+
? `Paging is unavailable. ${WRITE_ALREADY_RAN}`
|
|
262
|
+
: results.pagingTool
|
|
263
|
+
? "Paging is unavailable."
|
|
264
|
+
: "Paging is unavailable. Use execute_code to reduce read-only results before returning them.",
|
|
265
|
+
};
|
|
241
266
|
}
|
|
242
267
|
return {
|
|
243
268
|
truncated: true,
|
|
269
|
+
resultId: id,
|
|
244
270
|
totalBytes,
|
|
245
|
-
hint: results
|
|
246
|
-
|
|
247
|
-
|
|
271
|
+
hint: pagingHint(results, totalBytes, preview),
|
|
272
|
+
nextOffset: preview.kind === "prefix" ? preview.bytes : 0,
|
|
273
|
+
nextAction: results.pagingTool === "read_result"
|
|
274
|
+
? {
|
|
275
|
+
tool: "read_result",
|
|
276
|
+
arguments: { id, offset: preview.kind === "prefix" ? preview.bytes : 0 },
|
|
277
|
+
}
|
|
278
|
+
: {
|
|
279
|
+
tool: "execute_code",
|
|
280
|
+
// A byte may cost six JSON characters when escaped. Leave room for the
|
|
281
|
+
// result id, page counters, and continuation metadata in the returned page.
|
|
282
|
+
arguments: {
|
|
283
|
+
code: `async () => await connecta.result(${JSON.stringify(id)}, { offset: ${preview.kind === "prefix" ? preview.bytes : 0}, maxBytes: ${Math.floor((MAX_EXECUTE_RESULT_CHARS - 1024) / 6)} })`,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
248
286
|
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
truncated: true,
|
|
252
|
-
resultId: id,
|
|
253
|
-
totalBytes,
|
|
254
|
-
hint: pagingHint(results, totalBytes, preview),
|
|
255
|
-
nextOffset: preview.kind === "prefix" ? preview.bytes : 0,
|
|
256
|
-
nextAction: {
|
|
257
|
-
tool: "execute_code",
|
|
258
|
-
// A byte may cost six JSON characters when escaped. Leave room for the
|
|
259
|
-
// result id, page counters, and continuation metadata in the returned page.
|
|
260
|
-
arguments: {
|
|
261
|
-
code: `async () => await connecta.result(${JSON.stringify(id)}, { offset: ${preview.kind === "prefix" ? preview.bytes : 0}, maxBytes: ${Math.floor((MAX_EXECUTE_RESULT_CHARS - 1024) / 6)} })`,
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
};
|
|
265
|
-
}
|
|
287
|
+
});
|
|
288
|
+
};
|
|
266
289
|
/** Program calls use the direct-call envelope, ledger, expiry and authority. */
|
|
267
|
-
export
|
|
268
|
-
|
|
269
|
-
const wire = JSON.stringify({ data, format });
|
|
270
|
-
if (enc.encode(wire).length <= wireBytes)
|
|
271
|
-
return { data, format };
|
|
272
|
-
const text = secrets.text(format === "text" ? String(data) : serializeResultText(data));
|
|
273
|
-
const bytes = enc.encode(text);
|
|
274
|
-
const write = resolved.definition.classification !== "read";
|
|
275
|
-
const notice = await stashResult(bytes, {
|
|
276
|
-
binding: {
|
|
277
|
-
identity: registry.resultIdentity(),
|
|
278
|
-
baseUrl,
|
|
279
|
-
connector: resolved.connector.id,
|
|
280
|
-
tool: resolved.definition.name,
|
|
281
|
-
classification: write ? "write" : "read",
|
|
282
|
-
},
|
|
283
|
-
write,
|
|
284
|
-
cap: resolveMaxResultBytes(resolved.connector.maxResultBytes, registry.maxResultBytes),
|
|
285
|
-
set: (id, chunks, ttl) => registry.stashResult(id, chunks, ttl),
|
|
286
|
-
warn: () => logFailure(registry.contextFor(resolved.connector.id, baseUrl, requestScope).logger, "result paging unavailable", failureRecord({ connector: resolved.connector.id, tool: resolved.definition })),
|
|
287
|
-
}, { kind: "none" }, format === "text" ? text : undefined);
|
|
288
|
-
return {
|
|
289
|
-
format: "paged",
|
|
290
|
-
valueFormat: format,
|
|
291
|
-
...(notice.resultId ? { resultId: notice.resultId } : {}),
|
|
292
|
-
totalBytes: bytes.length,
|
|
293
|
-
truncated: true,
|
|
294
|
-
hint: notice.resultId
|
|
295
|
-
? `${write ? WRITE_ALREADY_RAN + " " : ""}Page this result with connecta.result(handle); do not repeat the call for output.`
|
|
296
|
-
: notice.hint,
|
|
297
|
-
};
|
|
290
|
+
export function pageProgramResult(registry, baseUrl, resolved, value, format, secrets, requestScope, wireBytes = PROGRAM_RESULT_WIRE_BYTES) {
|
|
291
|
+
return runEdge(pageProgramResultNative(registry, baseUrl, resolved, value, format, secrets, requestScope, wireBytes));
|
|
298
292
|
}
|
|
293
|
+
const pageProgramResultNative = /* @__PURE__ */ registerAuth(pageProgramResult, function (registry, baseUrl, resolved, value, format, secrets, requestScope, wireBytes = PROGRAM_RESULT_WIRE_BYTES) {
|
|
294
|
+
return authFlow(function* () {
|
|
295
|
+
const data = secrets.redact(value);
|
|
296
|
+
const wire = JSON.stringify({ data, format });
|
|
297
|
+
if (enc.encode(wire).length <= wireBytes)
|
|
298
|
+
return { data, format };
|
|
299
|
+
const text = secrets.text(format === "text" ? String(data) : serializeResultText(data));
|
|
300
|
+
const bytes = enc.encode(text);
|
|
301
|
+
const write = resolved.definition.classification !== "read";
|
|
302
|
+
const notice = yield* upwardStep(stashResult(bytes, {
|
|
303
|
+
binding: {
|
|
304
|
+
identity: registry.resultIdentity(),
|
|
305
|
+
baseUrl,
|
|
306
|
+
connector: resolved.connector.id,
|
|
307
|
+
tool: resolved.definition.name,
|
|
308
|
+
classification: write ? "write" : "read",
|
|
309
|
+
},
|
|
310
|
+
write,
|
|
311
|
+
cap: resolveMaxResultBytes(resolved.connector.maxResultBytes, registry.maxResultBytes),
|
|
312
|
+
set: authMethod((id, chunks, ttl) => registryProgram(registry, "stashResult", id, chunks, ttl)),
|
|
313
|
+
warn: () => logFailure(registry.contextFor(resolved.connector.id, baseUrl, requestScope).logger, "result paging unavailable", failureRecord({ connector: resolved.connector.id, tool: resolved.definition })),
|
|
314
|
+
}, { kind: "none" }, format === "text" ? text : undefined));
|
|
315
|
+
return {
|
|
316
|
+
format: "paged",
|
|
317
|
+
valueFormat: format,
|
|
318
|
+
...(notice.resultId ? { resultId: notice.resultId } : {}),
|
|
319
|
+
totalBytes: bytes.length,
|
|
320
|
+
truncated: true,
|
|
321
|
+
hint: notice.resultId
|
|
322
|
+
? `${write ? WRITE_ALREADY_RAN + " " : ""}Page this result with connecta.result(handle); do not repeat the call for output.`
|
|
323
|
+
: notice.hint,
|
|
324
|
+
};
|
|
325
|
+
});
|
|
326
|
+
});
|
|
299
327
|
/** Program returns share the direct stash, with all host-admitted call dependencies. */
|
|
300
|
-
export
|
|
301
|
-
|
|
302
|
-
if (serialized.length <= maxChars)
|
|
303
|
-
return value;
|
|
304
|
-
const text = secrets.text(serialized);
|
|
305
|
-
const bytes = enc.encode(text);
|
|
306
|
-
const write = dependencies.some((dependency) => dependency.classification === "write");
|
|
307
|
-
const cap = Math.min(registry.maxResultBytes, ...dependencies.map((dependency) => resolveMaxResultBytes(registry.getConnector(dependency.connector)?.maxResultBytes, registry.maxResultBytes)));
|
|
308
|
-
const results = {
|
|
309
|
-
secrets,
|
|
310
|
-
binding: {
|
|
311
|
-
identity: registry.resultIdentity(),
|
|
312
|
-
baseUrl,
|
|
313
|
-
classification: write ? "write" : "read",
|
|
314
|
-
program: dependencies,
|
|
315
|
-
},
|
|
316
|
-
write: completedWrite,
|
|
317
|
-
cap,
|
|
318
|
-
set: (id, chunks, ttl) => registry.stashResult(id, chunks, ttl),
|
|
319
|
-
warn: () => logFailure(logger, "result paging unavailable", failureRecord({})),
|
|
320
|
-
};
|
|
321
|
-
const notice = await stashResult(bytes, results, { kind: "none" }, text);
|
|
322
|
-
// Put the entire recovery route ahead of the preview in both response forms.
|
|
323
|
-
// A client retaining only the first 2,000 characters still sees the action.
|
|
324
|
-
if (notice.resultId) {
|
|
325
|
-
const base = { ...notice, totalChars: text.length, preview: "" };
|
|
326
|
-
const budget = Math.max(0, maxChars - JSON.stringify(base).length);
|
|
327
|
-
return { ...base, preview: escapedHeadOf(bytes, Math.min(512, budget)) };
|
|
328
|
-
}
|
|
329
|
-
const fallback = guardExecuteResultValue(secrets.redact(value), maxChars, notice.hint);
|
|
330
|
-
return { truncated: true, totalChars: fallback.totalChars, hint: fallback.hint, preview: fallback.preview };
|
|
328
|
+
export function guardProgramReturn(value, registry, baseUrl, dependencies, secrets, logger, maxChars, completedWrite) {
|
|
329
|
+
return runEdge(guardProgramReturnNative(value, registry, baseUrl, dependencies, secrets, logger, maxChars, completedWrite));
|
|
331
330
|
}
|
|
331
|
+
const guardProgramReturnNative = /* @__PURE__ */ registerAuth(guardProgramReturn, function (value, registry, baseUrl, dependencies, secrets, logger, maxChars, completedWrite) {
|
|
332
|
+
return authFlow(function* () {
|
|
333
|
+
const serialized = serializeResultText(value);
|
|
334
|
+
if (serialized.length <= maxChars)
|
|
335
|
+
return value;
|
|
336
|
+
const text = secrets.text(serialized);
|
|
337
|
+
const bytes = enc.encode(text);
|
|
338
|
+
const write = dependencies.some((dependency) => dependency.classification === "write");
|
|
339
|
+
const cap = Math.min(registry.maxResultBytes, ...dependencies.map((dependency) => resolveMaxResultBytes(registry.getConnector(dependency.connector)?.maxResultBytes, registry.maxResultBytes)));
|
|
340
|
+
const results = {
|
|
341
|
+
secrets,
|
|
342
|
+
binding: {
|
|
343
|
+
identity: registry.resultIdentity(),
|
|
344
|
+
baseUrl,
|
|
345
|
+
classification: write ? "write" : "read",
|
|
346
|
+
program: dependencies,
|
|
347
|
+
},
|
|
348
|
+
write: completedWrite,
|
|
349
|
+
cap,
|
|
350
|
+
set: authMethod((id, chunks, ttl) => registryProgram(registry, "stashResult", id, chunks, ttl)),
|
|
351
|
+
warn: () => logFailure(logger, "result paging unavailable", failureRecord({})),
|
|
352
|
+
};
|
|
353
|
+
const notice = yield* upwardStep(stashResult(bytes, results, { kind: "none" }, text));
|
|
354
|
+
// Put the entire recovery route ahead of the preview in both response forms.
|
|
355
|
+
// A client retaining only the first 2,000 characters still sees the action.
|
|
356
|
+
if (notice.resultId) {
|
|
357
|
+
const base = { ...notice, totalChars: text.length, preview: "" };
|
|
358
|
+
const budget = Math.max(0, maxChars - JSON.stringify(base).length);
|
|
359
|
+
return { ...base, preview: escapedHeadOf(bytes, Math.min(512, budget)) };
|
|
360
|
+
}
|
|
361
|
+
const fallback = guardExecuteResultValue(secrets.redact(value), maxChars, notice.hint);
|
|
362
|
+
return { truncated: true, totalChars: fallback.totalChars, hint: fallback.hint, preview: fallback.preview };
|
|
363
|
+
});
|
|
364
|
+
});
|
|
332
365
|
/** The first `cap` bytes of `bytes`, ending on a character boundary. */
|
|
333
366
|
function headOf(bytes, cap) {
|
|
334
367
|
return bytes.subarray(0, alignEndToCharBoundary(bytes, 0, cap, bytes.length));
|
|
@@ -347,53 +380,59 @@ function noticeFirst(notice, preview) {
|
|
|
347
380
|
* `text` already encoded, so a caller that had to measure it to make this
|
|
348
381
|
* decision doesn't encode it twice.
|
|
349
382
|
*/
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
383
|
+
const guardEncoded = function (text, bytes, results, cap) {
|
|
384
|
+
return authFlow(function* () {
|
|
385
|
+
if (bytes.length <= cap) {
|
|
386
|
+
return {
|
|
387
|
+
result: { content: [{ type: "text", text }] },
|
|
388
|
+
truncated: false,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
if (!isWellFormedText(text)) {
|
|
392
|
+
const notice = yield* upwardStep(stashResult(bytes, results, { kind: "none" }, text));
|
|
393
|
+
return { result: noticeFirst(notice, ""), truncated: true };
|
|
394
|
+
}
|
|
395
|
+
const head = headOf(bytes, cap);
|
|
396
|
+
const notice = yield* upwardStep(stashResult(bytes, results, {
|
|
397
|
+
kind: "prefix",
|
|
398
|
+
bytes: head.length,
|
|
399
|
+
}));
|
|
400
|
+
return { result: noticeFirst(notice, dec.decode(head)), truncated: true };
|
|
365
401
|
});
|
|
366
|
-
|
|
367
|
-
}
|
|
402
|
+
};
|
|
368
403
|
/** {@link guardEncoded} over a string that has not been measured yet. */
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
404
|
+
const guardText = function (text, results, cap) {
|
|
405
|
+
return authFlow(function* () {
|
|
406
|
+
// `JSON.stringify`'s type says `string` where its behavior says `string |
|
|
407
|
+
// undefined`, so TypeScript alone does not keep a non-string out of here.
|
|
408
|
+
// Normalizing at the door means the size check below always measures exactly
|
|
409
|
+
// the text that is emitted, and no future caller can launder a non-string
|
|
410
|
+
// through it the way issue #42 describes.
|
|
411
|
+
const serialized = typeof text === "string" ? text : serializeResultText(text);
|
|
412
|
+
const body = results.secrets?.text(serialized) ?? serialized;
|
|
413
|
+
return yield* upwardStep(guardEncoded(body, enc.encode(body), results, cap));
|
|
414
|
+
});
|
|
415
|
+
};
|
|
379
416
|
/** Store an oversized JSON value and replace it with a page handle. */
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
417
|
+
const guardValue = function (value, results, cap) {
|
|
418
|
+
return authFlow(function* () {
|
|
419
|
+
const serialized = serializeResultText(value);
|
|
420
|
+
const text = results.secrets?.text(serialized) ?? serialized;
|
|
421
|
+
const bytes = enc.encode(text);
|
|
422
|
+
if (bytes.length <= cap)
|
|
423
|
+
return { result: results.secrets?.redact(value) ?? value, truncated: false };
|
|
424
|
+
const notice = yield* upwardStep(stashResult(bytes, results, { kind: "none" }));
|
|
425
|
+
return {
|
|
426
|
+
result: notice.resultId
|
|
427
|
+
? notice
|
|
428
|
+
: {
|
|
429
|
+
...notice,
|
|
430
|
+
preview: escapedHeadOf(bytes, cap),
|
|
431
|
+
},
|
|
432
|
+
truncated: true,
|
|
433
|
+
};
|
|
434
|
+
});
|
|
435
|
+
};
|
|
397
436
|
/**
|
|
398
437
|
* The longest head of `bytes` whose JSON string form fits `cap` bytes. Value
|
|
399
438
|
* mode carries an unpageable preview inside its JSON envelope, where escaping
|
|
@@ -432,48 +471,50 @@ function escapedHeadOf(bytes, cap) {
|
|
|
432
471
|
* the head of a half-written base64 image is of no use to anyone. Either way
|
|
433
472
|
* the full envelope is stashed and pages through `connecta.result`.
|
|
434
473
|
*/
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
474
|
+
const guardContent = function (content, results, cap) {
|
|
475
|
+
return authFlow(function* () {
|
|
476
|
+
content = results.secrets?.redact({ content }).content ?? content;
|
|
477
|
+
const only = content.length === 1 ? content[0] : undefined;
|
|
478
|
+
if (only?.type === "text" && typeof only.text === "string") {
|
|
479
|
+
const bytes = enc.encode(only.text);
|
|
480
|
+
// Under the cap the block passes through untouched, annotations included.
|
|
481
|
+
if (bytes.length <= cap)
|
|
482
|
+
return { result: { content }, truncated: false };
|
|
483
|
+
return yield* upwardStep(guardEncoded(only.text, bytes, results, cap));
|
|
484
|
+
}
|
|
485
|
+
let text;
|
|
486
|
+
try {
|
|
487
|
+
const serialized = JSON.stringify(content);
|
|
488
|
+
text = results.secrets?.text(serialized) ?? serialized;
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
// A block carrying a BigInt or a cycle cannot be serialized, so it cannot
|
|
492
|
+
// be measured, stashed, or paged either — there is nothing this guard could
|
|
493
|
+
// do with it. Pass it through as the old text-only measure did, rather than
|
|
494
|
+
// turning a call that used to succeed into result_processing_failed.
|
|
442
495
|
return { result: { content }, truncated: false };
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
// included, in their original order.
|
|
460
|
-
if (bytes.length <= cap) {
|
|
461
|
-
return { result: { content }, truncated: false };
|
|
462
|
-
}
|
|
463
|
-
if (content.every((b) => b.type === "text")) {
|
|
464
|
-
const joined = enc.encode(content.map((b) => b.text).join("\n"));
|
|
465
|
-
const notice = await stashResult(bytes, results, { kind: "text-of-envelope" });
|
|
496
|
+
}
|
|
497
|
+
const bytes = enc.encode(text);
|
|
498
|
+
// Under the cap the downstream blocks pass through untouched, non-text ones
|
|
499
|
+
// included, in their original order.
|
|
500
|
+
if (bytes.length <= cap) {
|
|
501
|
+
return { result: { content }, truncated: false };
|
|
502
|
+
}
|
|
503
|
+
if (content.every((b) => b.type === "text")) {
|
|
504
|
+
const joined = enc.encode(content.map((b) => b.text).join("\n"));
|
|
505
|
+
const notice = yield* upwardStep(stashResult(bytes, results, { kind: "text-of-envelope" }));
|
|
506
|
+
return {
|
|
507
|
+
result: noticeFirst(notice, dec.decode(headOf(joined, cap))),
|
|
508
|
+
truncated: true,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
const notice = yield* upwardStep(stashResult(bytes, results, { kind: "none" }));
|
|
466
512
|
return {
|
|
467
|
-
result:
|
|
513
|
+
result: { content: [{ type: "text", text: JSON.stringify(notice) }] },
|
|
468
514
|
truncated: true,
|
|
469
515
|
};
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
return {
|
|
473
|
-
result: { content: [{ type: "text", text: JSON.stringify(notice) }] },
|
|
474
|
-
truncated: true,
|
|
475
|
-
};
|
|
476
|
-
}
|
|
516
|
+
});
|
|
517
|
+
};
|
|
477
518
|
/**
|
|
478
519
|
* Every meta-tool handler over a registry, one per registered tool. Exported for
|
|
479
520
|
* direct testing; registerMetaTools() wires the six explicit tools onto an
|
|
@@ -503,7 +544,13 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
503
544
|
// request without leaking request-bound I/O into the next one.
|
|
504
545
|
const requestScope = opts.requestScope ?? {};
|
|
505
546
|
if (opts.canManageAuth || opts.oauthConnectUrl || opts.credentialHandoffUrl || opts.oauthConnectUnavailable) {
|
|
506
|
-
bindAuthorizationHandoffs(requestScope, (connector) => authorizationHandoff
|
|
547
|
+
bindAuthorizationHandoffs(requestScope, authMethod((connector) => authCall(authorizationHandoff, [
|
|
548
|
+
registry,
|
|
549
|
+
baseUrl,
|
|
550
|
+
requestScope,
|
|
551
|
+
opts,
|
|
552
|
+
{ connector },
|
|
553
|
+
])));
|
|
507
554
|
}
|
|
508
555
|
const sentSecrets = sentSecretsForRequest(requestScope);
|
|
509
556
|
const skills = new SkillsRegistry(registry, baseUrl, {
|
|
@@ -525,144 +572,162 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
525
572
|
});
|
|
526
573
|
const invocation = new InvocationService(registry, catalog, opts.activity);
|
|
527
574
|
/** MCP adapter: shared invocation semantics plus MCP-only result shaping. */
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
},
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const guarded = await guardValue(value, results, cap);
|
|
577
|
-
value = guarded.result;
|
|
578
|
-
return processed(jsonResult({ ok: true, data: value, format }), guarded.truncated, { value });
|
|
579
|
-
}
|
|
580
|
-
if (resolved.connector.kind === "mcp") {
|
|
581
|
-
const mcpResult = result;
|
|
582
|
-
let content = mcpResult?.content ?? [];
|
|
583
|
-
if (!content.some((block) => block.type === "text") && mcpResult?.structuredContent !== undefined) {
|
|
584
|
-
content = [
|
|
585
|
-
...content,
|
|
586
|
-
{
|
|
587
|
-
type: "text",
|
|
588
|
-
text: JSON.stringify(mcpResult.structuredContent),
|
|
575
|
+
function runCall(call, source) {
|
|
576
|
+
return runEdge(runCallNative(call, source));
|
|
577
|
+
}
|
|
578
|
+
const runCallNative = /* @__PURE__ */ registerAuth(runCall, function (call, source) {
|
|
579
|
+
return authFlow(function* () {
|
|
580
|
+
const timeoutMs = normalizeTimeoutMs(call.timeoutMs) ?? defaultToolTimeoutMs;
|
|
581
|
+
const outcome = yield* upwardStep(authInvoke(invocation, "invoke", [
|
|
582
|
+
call.address,
|
|
583
|
+
call.args ?? {},
|
|
584
|
+
{
|
|
585
|
+
source,
|
|
586
|
+
activitySource: opts.activitySource,
|
|
587
|
+
sentSecrets,
|
|
588
|
+
readOnly: opts.readOnly,
|
|
589
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
590
|
+
...(opts.requestSignal !== undefined ? { requestSignal: opts.requestSignal } : {}),
|
|
591
|
+
unwrapResult: call.resultMode === "value",
|
|
592
|
+
...(opts.downstreamInput
|
|
593
|
+
? {
|
|
594
|
+
processInputRequired: authMethod((result, resolved, secrets) => authFlow(function* () {
|
|
595
|
+
yield* upwardStep(authCall(captureDownstreamInput, [
|
|
596
|
+
requestScope,
|
|
597
|
+
resolved.connector.id,
|
|
598
|
+
`${resolved.connector.id}.${resolved.toolName}`,
|
|
599
|
+
result,
|
|
600
|
+
secrets,
|
|
601
|
+
resolved.definition.classification !== "read",
|
|
602
|
+
]));
|
|
603
|
+
return { toolResult: { content: [] } };
|
|
604
|
+
})),
|
|
605
|
+
}
|
|
606
|
+
: {}),
|
|
607
|
+
processResult: authMethod((result, resolved, secrets, format) => authFlow(function* () {
|
|
608
|
+
// Result-size cap for THIS call: the connector's own override wins,
|
|
609
|
+
// then the deployment-wide value, then the built-in default (already
|
|
610
|
+
// folded into `globalCap`). Resolved per call so one request can
|
|
611
|
+
// mix a tight-capped connector with siblings on the global cap. An
|
|
612
|
+
// override the registry already warned about at startup is dropped
|
|
613
|
+
// here, so the connector simply inherits `globalCap`.
|
|
614
|
+
const cap = resolveMaxResultBytes(resolved.connector.maxResultBytes, globalCap);
|
|
615
|
+
const results = {
|
|
616
|
+
secrets,
|
|
617
|
+
binding: {
|
|
618
|
+
identity: registry.resultIdentity(),
|
|
619
|
+
baseUrl,
|
|
620
|
+
connector: resolved.connector.id,
|
|
621
|
+
tool: resolved.definition.name,
|
|
622
|
+
classification: resolved.definition.classification === "read" ? "read" : "write",
|
|
589
623
|
},
|
|
590
|
-
|
|
624
|
+
write: resolved.definition.classification !== "read",
|
|
625
|
+
cap,
|
|
626
|
+
...(opts.pagingTool ? { pagingTool: opts.pagingTool } : {}),
|
|
627
|
+
set: authMethod((id, value, ttlSeconds) => registryProgram(registry, "stashResult", id, value, ttlSeconds)),
|
|
628
|
+
// The catalog entry, never `toolName`: a record names a tool
|
|
629
|
+
// only through the grammar check (src/operator-record.ts).
|
|
630
|
+
warn: () => logFailure(registry.contextFor(resolved.connector.id, baseUrl, requestScope).logger, "result paging unavailable", failureRecord({ connector: resolved.connector.id, tool: resolved.definition })),
|
|
631
|
+
};
|
|
632
|
+
const processed = (toolResult, truncated, value) => ({
|
|
633
|
+
toolResult,
|
|
634
|
+
...value,
|
|
635
|
+
...(truncated ? { friction: "result_too_large" } : {}),
|
|
636
|
+
});
|
|
637
|
+
if (call.resultMode === "value") {
|
|
638
|
+
let value = result;
|
|
639
|
+
const guarded = yield* upwardStep(guardValue(value, results, cap));
|
|
640
|
+
value = guarded.result;
|
|
641
|
+
return processed(jsonResult({ ok: true, data: value, format }), guarded.truncated, { value });
|
|
642
|
+
}
|
|
643
|
+
if (resolved.connector.kind === "mcp") {
|
|
644
|
+
const mcpResult = result;
|
|
645
|
+
let content = mcpResult?.content ?? [];
|
|
646
|
+
if (!content.some((block) => block.type === "text") && mcpResult?.structuredContent !== undefined) {
|
|
647
|
+
content = [
|
|
648
|
+
...content,
|
|
649
|
+
{
|
|
650
|
+
type: "text",
|
|
651
|
+
text: JSON.stringify(mcpResult.structuredContent),
|
|
652
|
+
},
|
|
653
|
+
];
|
|
654
|
+
}
|
|
655
|
+
const guarded = yield* upwardStep(guardContent(content, results, cap));
|
|
656
|
+
// Native blocks and bounded previews are the result. A format-only
|
|
657
|
+
// structuredContent hides them in clients that prefer that field.
|
|
658
|
+
guarded.result._meta = { "dev.connecta/format": format };
|
|
659
|
+
return processed(guarded.result, guarded.truncated);
|
|
660
|
+
}
|
|
661
|
+
const value = result;
|
|
662
|
+
const guarded = yield* upwardStep(guardText(serializeResultText(value), results, cap));
|
|
663
|
+
guarded.result._meta = { "dev.connecta/format": format };
|
|
664
|
+
return processed(guarded.result, guarded.truncated, { value });
|
|
665
|
+
})),
|
|
666
|
+
activityFriction: (processed) => processed.friction,
|
|
667
|
+
},
|
|
668
|
+
]));
|
|
669
|
+
if (!outcome.ok) {
|
|
670
|
+
const structuredRecovery = outcome.error.nextAction !== undefined;
|
|
671
|
+
const recoveryRequired = structuredRecovery ||
|
|
672
|
+
// Sanitized `unavailable` diagnostics ride the structured shape too;
|
|
673
|
+
// the plain-text path would drop them (#539).
|
|
674
|
+
outcome.error.details !== undefined ||
|
|
675
|
+
// So does a conflict's `current`: where things stand is the retry.
|
|
676
|
+
outcome.error.current !== undefined ||
|
|
677
|
+
["auth_required", "invalid_args", "input_required_unsupported"].includes(outcome.error.code);
|
|
678
|
+
// Every mode carries the complete repair envelope in both MCP forms.
|
|
679
|
+
const makeFailedResult = () => jsonResult({
|
|
680
|
+
ok: false,
|
|
681
|
+
error: outcome.error,
|
|
682
|
+
durationMs: outcome.durationMs,
|
|
683
|
+
attempts: outcome.attempts,
|
|
684
|
+
...(call.diagnostics ? { timing: outcome.timing } : {}),
|
|
685
|
+
});
|
|
686
|
+
let failedResult = makeFailedResult();
|
|
687
|
+
// Errors repeat in text and structuredContent. Account for
|
|
688
|
+
// both copies and JSON escaping when the bounded provider reason is large.
|
|
689
|
+
if (!recoveryRequired) {
|
|
690
|
+
const cap = resolveMaxResultBytes(outcome.resolved?.connector.maxResultBytes, globalCap);
|
|
691
|
+
let budget = 512;
|
|
692
|
+
while (enc.encode(JSON.stringify(failedResult)).length > cap && budget > 0) {
|
|
693
|
+
budget = Math.floor(budget / 2);
|
|
694
|
+
outcome.error.message = boundedEchoText(outcome.error.message, budget);
|
|
695
|
+
failedResult = makeFailedResult();
|
|
591
696
|
}
|
|
592
|
-
const guarded = await guardContent(content, results, cap);
|
|
593
|
-
// Native blocks and bounded previews are the result. A format-only
|
|
594
|
-
// structuredContent hides them in clients that prefer that field.
|
|
595
|
-
guarded.result._meta = { "dev.connecta/format": format };
|
|
596
|
-
return processed(guarded.result, guarded.truncated);
|
|
597
|
-
}
|
|
598
|
-
const value = result;
|
|
599
|
-
const guarded = await guardText(serializeResultText(value), results, cap);
|
|
600
|
-
guarded.result._meta = { "dev.connecta/format": format };
|
|
601
|
-
return processed(guarded.result, guarded.truncated, { value });
|
|
602
|
-
},
|
|
603
|
-
activityFriction: (processed) => processed.friction,
|
|
604
|
-
});
|
|
605
|
-
if (!outcome.ok) {
|
|
606
|
-
const structuredRecovery = outcome.error.nextAction !== undefined;
|
|
607
|
-
const recoveryRequired = structuredRecovery ||
|
|
608
|
-
// Sanitized `unavailable` diagnostics ride the structured shape too;
|
|
609
|
-
// the plain-text path would drop them (#539).
|
|
610
|
-
outcome.error.details !== undefined ||
|
|
611
|
-
// So does a conflict's `current`: where things stand is the retry.
|
|
612
|
-
outcome.error.current !== undefined ||
|
|
613
|
-
["auth_required", "invalid_args", "input_required_unsupported"].includes(outcome.error.code);
|
|
614
|
-
// Every mode carries the complete repair envelope in both MCP forms.
|
|
615
|
-
const makeFailedResult = () => jsonResult({
|
|
616
|
-
ok: false,
|
|
617
|
-
error: outcome.error,
|
|
618
|
-
durationMs: outcome.durationMs,
|
|
619
|
-
attempts: outcome.attempts,
|
|
620
|
-
...(call.diagnostics ? { timing: outcome.timing } : {}),
|
|
621
|
-
});
|
|
622
|
-
let failedResult = makeFailedResult();
|
|
623
|
-
// Errors repeat in text and structuredContent. Account for
|
|
624
|
-
// both copies and JSON escaping when the bounded provider reason is large.
|
|
625
|
-
if (!recoveryRequired) {
|
|
626
|
-
const cap = resolveMaxResultBytes(outcome.resolved?.connector.maxResultBytes, globalCap);
|
|
627
|
-
let budget = 512;
|
|
628
|
-
while (enc.encode(JSON.stringify(failedResult)).length > cap && budget > 0) {
|
|
629
|
-
budget = Math.floor(budget / 2);
|
|
630
|
-
outcome.error.message = boundedEchoText(outcome.error.message, budget);
|
|
631
|
-
failedResult = makeFailedResult();
|
|
632
697
|
}
|
|
698
|
+
failedResult.isError = true;
|
|
699
|
+
return {
|
|
700
|
+
toolResult: failedResult,
|
|
701
|
+
durationMs: outcome.durationMs,
|
|
702
|
+
attempts: outcome.attempts,
|
|
703
|
+
timing: outcome.timing,
|
|
704
|
+
error: outcome.error,
|
|
705
|
+
};
|
|
633
706
|
}
|
|
634
|
-
|
|
707
|
+
const valueModeResult = call.resultMode === "value"
|
|
708
|
+
? jsonResult({
|
|
709
|
+
ok: true,
|
|
710
|
+
data: outcome.value.value,
|
|
711
|
+
format: outcome.format,
|
|
712
|
+
durationMs: outcome.durationMs,
|
|
713
|
+
attempts: outcome.attempts,
|
|
714
|
+
...(call.diagnostics ? { timing: outcome.timing } : {}),
|
|
715
|
+
})
|
|
716
|
+
: outcome.value.toolResult;
|
|
635
717
|
return {
|
|
636
|
-
toolResult:
|
|
718
|
+
toolResult: valueModeResult,
|
|
637
719
|
durationMs: outcome.durationMs,
|
|
638
720
|
attempts: outcome.attempts,
|
|
639
721
|
timing: outcome.timing,
|
|
640
|
-
|
|
722
|
+
...(Object.prototype.hasOwnProperty.call(outcome.value, "value") ? { value: outcome.value.value } : {}),
|
|
641
723
|
};
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
? jsonResult({
|
|
645
|
-
ok: true,
|
|
646
|
-
data: outcome.value.value,
|
|
647
|
-
format: outcome.format,
|
|
648
|
-
durationMs: outcome.durationMs,
|
|
649
|
-
attempts: outcome.attempts,
|
|
650
|
-
...(call.diagnostics ? { timing: outcome.timing } : {}),
|
|
651
|
-
})
|
|
652
|
-
: outcome.value.toolResult;
|
|
653
|
-
return {
|
|
654
|
-
toolResult: valueModeResult,
|
|
655
|
-
durationMs: outcome.durationMs,
|
|
656
|
-
attempts: outcome.attempts,
|
|
657
|
-
timing: outcome.timing,
|
|
658
|
-
...(Object.prototype.hasOwnProperty.call(outcome.value, "value") ? { value: outcome.value.value } : {}),
|
|
659
|
-
};
|
|
660
|
-
}
|
|
724
|
+
});
|
|
725
|
+
});
|
|
661
726
|
return {
|
|
662
|
-
|
|
727
|
+
skills: authMethod((args = {}) => authFlow(function* () {
|
|
663
728
|
try {
|
|
664
729
|
if (!args.name) {
|
|
665
|
-
const listing =
|
|
730
|
+
const listing = yield* upwardStep(authInvoke(skills, "summaries", []));
|
|
666
731
|
return {
|
|
667
732
|
structuredContent: { skills: listing },
|
|
668
733
|
content: [
|
|
@@ -674,31 +739,37 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
674
739
|
],
|
|
675
740
|
};
|
|
676
741
|
}
|
|
677
|
-
const text =
|
|
742
|
+
const text = yield* upwardStep(authInvoke(skills, "text", [args.name]));
|
|
678
743
|
return { content: [{ type: "text", text }], structuredContent: { name: args.name, format: "text", text } };
|
|
679
744
|
}
|
|
680
745
|
catch (error) {
|
|
681
746
|
return errorResult(error instanceof ConnectorCallError ? error.message : "Skills are unavailable.");
|
|
682
747
|
}
|
|
683
|
-
},
|
|
684
|
-
|
|
748
|
+
})),
|
|
749
|
+
searchTools: authMethod((args) => authFlow(function* () {
|
|
685
750
|
if (args.connector !== undefined && enc.encode(args.connector).length > 512) {
|
|
686
751
|
return discoveryErrorResult(new DiscoveryPolicyError("invalid_args", "connector must be at most 512 UTF-8 bytes."));
|
|
687
752
|
}
|
|
688
|
-
return discoveryResult(
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
753
|
+
return yield* upwardStep(discoveryResult(authMethod(() => authFlow(function* () {
|
|
754
|
+
return yield* upwardStep(authCall(flatSearchResult, [
|
|
755
|
+
yield* upwardStep(authInvoke(catalog, "search", [
|
|
756
|
+
{
|
|
757
|
+
...args,
|
|
758
|
+
includeSchemaKeys: args.includeSchemas !== undefined,
|
|
759
|
+
},
|
|
760
|
+
])),
|
|
761
|
+
]));
|
|
762
|
+
})), "Request a smaller limit, omit fullDescriptions, or use compact schemas."));
|
|
763
|
+
})),
|
|
764
|
+
callTool: authMethod((args) => authFlow(function* () {
|
|
765
|
+
return (yield* upwardStep(authCall(runCall, [args, "call_tool"]))).toolResult;
|
|
766
|
+
})),
|
|
767
|
+
callDestructiveTool: authMethod((args) => authFlow(function* () {
|
|
697
768
|
// `reason` is read by the host's approval view and stops there — runCall
|
|
698
769
|
// forwards only the call arguments, so it never reaches the connector.
|
|
699
|
-
return (
|
|
700
|
-
},
|
|
701
|
-
|
|
770
|
+
return (yield* upwardStep(authCall(runCall, [args, "call_destructive_tool"]))).toolResult;
|
|
771
|
+
})),
|
|
772
|
+
readResult: authMethod((args, options = {}) => authFlow(function* () {
|
|
702
773
|
// Defense for in-process callers too: guest paging validates these domains
|
|
703
774
|
// before calling here, while internal consumers may call readResult directly.
|
|
704
775
|
if (args.maxBytes !== undefined && !isValidMaxResultBytes(args.maxBytes)) {
|
|
@@ -709,7 +780,8 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
709
780
|
return errorResult(`Invalid offset ${args.offset}: must be a whole number of bytes ` +
|
|
710
781
|
`>= ${MIN_RESULT_OFFSET}. Omit it to start at the beginning.`);
|
|
711
782
|
}
|
|
712
|
-
if (args.page !== undefined &&
|
|
783
|
+
if (args.page !== undefined &&
|
|
784
|
+
(!Number.isSafeInteger(args.page) || args.page < 0 || args.offset !== undefined)) {
|
|
713
785
|
return errorResult("Use a whole page >= 0, without offset.");
|
|
714
786
|
}
|
|
715
787
|
const results = registry.resultsStorage();
|
|
@@ -726,11 +798,12 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
726
798
|
// `false` is a storage failure — retryable, and distinct from an id that
|
|
727
799
|
// is simply gone. Every key a page touches answers the same way, so a
|
|
728
800
|
// backend that dies halfway through a multi-chunk page says so.
|
|
729
|
-
const read =
|
|
801
|
+
const read = authMethod((key) => authFlow(function* () {
|
|
802
|
+
options.onStage?.("result storage");
|
|
730
803
|
options.signal?.throwIfAborted();
|
|
731
804
|
let value;
|
|
732
805
|
try {
|
|
733
|
-
value = (
|
|
806
|
+
value = (yield* upwardStep(authStorage(results, "get", [key]))) ?? null;
|
|
734
807
|
}
|
|
735
808
|
catch {
|
|
736
809
|
value = false;
|
|
@@ -739,8 +812,8 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
739
812
|
// subsequent chunk read or decode may start when that happens (INV-7).
|
|
740
813
|
options.signal?.throwIfAborted();
|
|
741
814
|
return value;
|
|
742
|
-
};
|
|
743
|
-
const stored =
|
|
815
|
+
}));
|
|
816
|
+
const stored = yield* upwardStep(authCall(read, [resultKeys.chunk(args.id, 0)]));
|
|
744
817
|
if (stored === false)
|
|
745
818
|
return unavailableResult();
|
|
746
819
|
if (stored === null) {
|
|
@@ -776,19 +849,19 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
776
849
|
!["read", "write"].includes(dependency.classification))) ||
|
|
777
850
|
!["read", "write"].includes(binding.classification))
|
|
778
851
|
return denied();
|
|
779
|
-
const allowed =
|
|
852
|
+
const allowed = authMethod(() => authFlow(function* () {
|
|
853
|
+
options.onStage?.("access validation");
|
|
780
854
|
options.signal?.throwIfAborted();
|
|
781
855
|
try {
|
|
782
|
-
|
|
783
|
-
|
|
856
|
+
const dependencies = binding.program ?? [binding];
|
|
857
|
+
if (!(yield* upwardStep(registryProgram(registry, "recheckResultAccess", dependencies, binding.classification, options.signal))))
|
|
784
858
|
return false;
|
|
859
|
+
options.signal?.throwIfAborted();
|
|
785
860
|
if (binding.classification === "write" && opts.readOnly === true)
|
|
786
861
|
return false;
|
|
787
|
-
const
|
|
862
|
+
const catalogs = new Map();
|
|
788
863
|
for (const dependency of dependencies) {
|
|
789
|
-
|
|
790
|
-
if (!(await registry.recheckResultAccess(address, dependency.classification, options.signal, dependency.access)))
|
|
791
|
-
return false;
|
|
864
|
+
options.signal?.throwIfAborted();
|
|
792
865
|
if (dependency.tool === undefined) {
|
|
793
866
|
if (dependency.access === "catalog"
|
|
794
867
|
? !registry.getConnector(dependency.connector)
|
|
@@ -796,14 +869,20 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
796
869
|
return false;
|
|
797
870
|
continue;
|
|
798
871
|
}
|
|
799
|
-
|
|
872
|
+
const connector = registry.getConnector(dependency.connector);
|
|
873
|
+
if (!connector)
|
|
800
874
|
return false;
|
|
801
|
-
|
|
875
|
+
let tools = catalogs.get(dependency.connector);
|
|
876
|
+
if (!tools) {
|
|
877
|
+
tools = yield* upwardStep(registryProgram(registry, "getTools", dependency.connector, baseUrl, requestScope, options.signal ? { signal: options.signal } : {}));
|
|
878
|
+
catalogs.set(dependency.connector, tools);
|
|
879
|
+
}
|
|
880
|
+
options.signal?.throwIfAborted();
|
|
802
881
|
const tool = tools.find((tool) => tool.name === dependency.tool);
|
|
803
882
|
if (!tool ||
|
|
804
883
|
!(dependency.access === "catalog" ||
|
|
805
884
|
(dependency.classification === "read" && tool.classification === "read") ||
|
|
806
|
-
opts.readOnly !== true))
|
|
885
|
+
(opts.readOnly !== true && connector.readOnly !== true)))
|
|
807
886
|
return false;
|
|
808
887
|
}
|
|
809
888
|
return true;
|
|
@@ -812,9 +891,10 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
812
891
|
options.signal?.throwIfAborted();
|
|
813
892
|
return false;
|
|
814
893
|
}
|
|
815
|
-
};
|
|
816
|
-
if (!(
|
|
894
|
+
}));
|
|
895
|
+
if (!(yield* upwardStep(authCall(allowed, []))))
|
|
817
896
|
return denied();
|
|
897
|
+
options.onStage?.("result storage");
|
|
818
898
|
opts.onResultRead?.(binding.program ?? [binding]);
|
|
819
899
|
let requestedOffset = args.offset ?? 0;
|
|
820
900
|
const cap = Number(header[4]);
|
|
@@ -844,7 +924,9 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
844
924
|
bytes = new Uint8Array(Math.max(0, end - start));
|
|
845
925
|
const lastChunk = Math.floor(Math.max(end - 1, start) / chunkBytes);
|
|
846
926
|
for (let index = Math.floor(start / chunkBytes); index <= lastChunk; index++) {
|
|
847
|
-
const encoded = index === 0
|
|
927
|
+
const encoded = index === 0
|
|
928
|
+
? stored.slice(header[0].length)
|
|
929
|
+
: yield* upwardStep(authCall(read, [resultKeys.chunk(args.id, index)]));
|
|
848
930
|
if (encoded === false)
|
|
849
931
|
return unavailableResult();
|
|
850
932
|
if (encoded === null) {
|
|
@@ -893,28 +975,28 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
893
975
|
hasMore,
|
|
894
976
|
...(hasMore ? { nextOffset: end } : {}),
|
|
895
977
|
};
|
|
896
|
-
if (!(
|
|
978
|
+
if (!(yield* upwardStep(authCall(allowed, []))))
|
|
897
979
|
return denied();
|
|
898
980
|
const page = noticeFirst(pageHeader, slice);
|
|
899
981
|
page.structuredContent = { ...pageHeader, text: slice, format: "text" };
|
|
900
982
|
return page;
|
|
901
|
-
},
|
|
902
|
-
|
|
983
|
+
})),
|
|
984
|
+
authorizeConnector: authMethod((args) => authFlow(function* () {
|
|
903
985
|
try {
|
|
904
|
-
return jsonResult(
|
|
986
|
+
return jsonResult(yield* upwardStep(authCall(authorizeConnector, [registry, baseUrl, requestScope, opts, args])));
|
|
905
987
|
}
|
|
906
988
|
catch (err) {
|
|
907
989
|
return errorResult(msg(err));
|
|
908
990
|
}
|
|
909
|
-
},
|
|
991
|
+
})),
|
|
910
992
|
};
|
|
911
993
|
}
|
|
912
|
-
const SEARCH_DESC = (readOnly) => `${readOnly ? "Use top-level search for catalog inspection; this read-only endpoint omits write tools." : "Use top-level search for catalog inspection or
|
|
994
|
+
const SEARCH_DESC = (readOnly) => `${readOnly ? "Use top-level search for catalog inspection; this read-only endpoint omits write tools." : "Use top-level search for catalog inspection or write work before call_destructive_tool."} Unknown-address read-only work belongs in one execute_code program that searches, calls, and returns the answer. Use 2–4 action/object terms and includeSchemas="compact"; the default limit is ${DEFAULT_SEARCH_LIMIT}. Set connector when known. safety="readOnly" selects reads; "approvalRequired" selects the fail-closed write complement. The host owns confirmation; these filters grant no authority. Empty query browses. Returns { catalogErrors, tools, total, offset, limit, hasMore }; read catalogErrors and absence before selecting a tool. Compact schemas have schemaFormat="text".`;
|
|
913
995
|
const CALL_DESC = (readOnly) => `Call one known-address tool explicitly annotated readOnlyHint: true. Use execute_code for unknown-address, multiple, dependent, or reduced read-only work. ${readOnly ? "This endpoint is read-only and refuses every write." : "Unannotated or write-capable tools fail closed to call_destructive_tool."} A truncated result carries a connecta.result action.`;
|
|
914
996
|
const CALL_DESTRUCTIVE_DESC = "Call any tool not explicitly annotated readOnlyHint: true. Include a short reason for the human reviewer after checking the schema and consequences. The reason grants no authority and is not sent downstream.";
|
|
915
997
|
/** A read-only endpoint keeps the six-tool surface, so this tool says why it never writes here. */
|
|
916
998
|
const CALL_DESTRUCTIVE_READ_ONLY_DESC = "This endpoint is read-only: search and describe omit write tools, and every write call here is refused. Reads still run; prefer call_tool or execute_code for them.";
|
|
917
|
-
const AUTHORIZE_DESC = "Use after auth_required. Returns an OAuth or operator-credential handoff, or
|
|
999
|
+
const AUTHORIZE_DESC = "Use after auth_required. Returns an OAuth or operator-credential handoff, or required deployment configuration. A configured credential reports presence only: if a call just failed with an authentication or operator-configuration error, give the retained handoff to the operator; otherwise proceed with the call. force=true requests an OAuth restart when the verified user opens the /connect URL; this tool never accepts credentials.";
|
|
918
1000
|
const SKILLS_DESC = "List or fetch on-demand guidance. Fetch usage only when the always-loaded instructions are insufficient or a program needs repair.";
|
|
919
1001
|
/**
|
|
920
1002
|
* Sentences appended to a meta-tool description only when this connection
|
|
@@ -981,6 +1063,13 @@ const CALL_DESTRUCTIVE_INPUT = advertisedSchema(z.strictObject({
|
|
|
981
1063
|
// trade. It is normalized to absent in the handler instead.
|
|
982
1064
|
reason: z.string().max(500).optional(),
|
|
983
1065
|
}));
|
|
1066
|
+
const READ_RESULT_DESC = "Page by resultId and nextAction from the truncation notice. UTF-8 byte ranges: continue from nextOffset while hasMore. Never repeat a write for output.";
|
|
1067
|
+
const READ_RESULT_INPUT = advertisedSchema(z.strictObject({
|
|
1068
|
+
id: z.string(),
|
|
1069
|
+
offset: z.number().int().nonnegative().optional(),
|
|
1070
|
+
page: z.number().int().nonnegative().optional(),
|
|
1071
|
+
maxBytes: z.number().int().positive().optional(),
|
|
1072
|
+
}));
|
|
984
1073
|
const AUTHORIZE_INPUT = advertisedSchema(z.object({
|
|
985
1074
|
connector: z.string(),
|
|
986
1075
|
force: z.boolean().optional(),
|
|
@@ -995,6 +1084,8 @@ const AUTHORIZE_INPUT = advertisedSchema(z.object({
|
|
|
995
1084
|
*/
|
|
996
1085
|
export function registerMetaTools(server, registry, ctx) {
|
|
997
1086
|
const mt = createMetaTools(registry, ctx.baseUrl, {
|
|
1087
|
+
...(ctx.native && !ctx.native.executeCode ? { pagingTool: "read_result" } : {}),
|
|
1088
|
+
...(ctx.native ? { activitySource: "native" } : {}),
|
|
998
1089
|
downstreamInput: Boolean(ctx.authElicitation),
|
|
999
1090
|
requestScope: ctx.requestScope,
|
|
1000
1091
|
readOnly: ctx.readOnly,
|
|
@@ -1016,22 +1107,40 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
1016
1107
|
};
|
|
1017
1108
|
server.registerTool("skills", {
|
|
1018
1109
|
description: describedFor(registry, SKILLS_DESC, "skills"),
|
|
1110
|
+
...(ctx.native ? { _meta: { "dev.connecta/surface": { surface: "native", ...ctx.native } } } : {}),
|
|
1019
1111
|
inputSchema: SKILLS_INPUT,
|
|
1020
1112
|
outputSchema: SKILLS_OUTPUT,
|
|
1021
1113
|
annotations: READ_ONLY_LOCAL,
|
|
1022
|
-
},
|
|
1114
|
+
}, authMethod((args, request) => authFlow(function* () {
|
|
1023
1115
|
bindRequest(request);
|
|
1024
|
-
return mt
|
|
1025
|
-
});
|
|
1116
|
+
return yield* upwardStep(authInvoke(mt, "skills", [args]));
|
|
1117
|
+
})));
|
|
1118
|
+
if (ctx.native) {
|
|
1119
|
+
if (!ctx.native.executeCode) {
|
|
1120
|
+
server.registerTool("read_result", { description: READ_RESULT_DESC, inputSchema: READ_RESULT_INPUT, annotations: READ_ONLY_LOCAL }, authMethod((args, request) => authFlow(function* () {
|
|
1121
|
+
bindRequest(request);
|
|
1122
|
+
return yield* upwardStep(authInvoke(mt, "readResult", [args, { signal: request.mcpReq.signal }]));
|
|
1123
|
+
})));
|
|
1124
|
+
}
|
|
1125
|
+
// A native call is the call_tool or call_destructive_tool call it names,
|
|
1126
|
+
// run exactly as those two tools run it below.
|
|
1127
|
+
return authMethod((route, call, request) => {
|
|
1128
|
+
bindRequest(request);
|
|
1129
|
+
const run = authMethod(() => authInvoke(mt, route === "call_tool" ? "callTool" : "callDestructiveTool", [call]));
|
|
1130
|
+
return ctx.authElicitation
|
|
1131
|
+
? authInvoke(ctx.authElicitation, "run", [route, call, request, run])
|
|
1132
|
+
: authCall(run, []);
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1026
1135
|
server.registerTool("search_tools", {
|
|
1027
1136
|
description: describedFor(registry, SEARCH_DESC(ctx.readOnly === true), "search"),
|
|
1028
1137
|
inputSchema: SEARCH_INPUT,
|
|
1029
1138
|
outputSchema: SEARCH_OUTPUT,
|
|
1030
1139
|
annotations: READ_ONLY_REMOTE,
|
|
1031
|
-
},
|
|
1140
|
+
}, authMethod((args, request) => authFlow(function* () {
|
|
1032
1141
|
bindRequest(request);
|
|
1033
|
-
return mt
|
|
1034
|
-
});
|
|
1142
|
+
return yield* upwardStep(authInvoke(mt, "searchTools", [args]));
|
|
1143
|
+
})));
|
|
1035
1144
|
server.registerTool("call_tool", {
|
|
1036
1145
|
description: CALL_DESC(ctx.readOnly === true),
|
|
1037
1146
|
inputSchema: CALL_INPUT,
|
|
@@ -1039,12 +1148,17 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
1039
1148
|
// call_tool admits only tools that are themselves explicitly read-only;
|
|
1040
1149
|
// anything else is refused and routed to call_destructive_tool.
|
|
1041
1150
|
annotations: READ_ONLY_REMOTE,
|
|
1042
|
-
},
|
|
1151
|
+
}, authMethod((args, request) => authFlow(function* () {
|
|
1043
1152
|
bindRequest(request);
|
|
1044
|
-
return ctx.authElicitation
|
|
1045
|
-
? ctx.authElicitation
|
|
1046
|
-
|
|
1047
|
-
|
|
1153
|
+
return yield* upwardStep(ctx.authElicitation
|
|
1154
|
+
? authInvoke(ctx.authElicitation, "run", [
|
|
1155
|
+
"call_tool",
|
|
1156
|
+
args,
|
|
1157
|
+
request,
|
|
1158
|
+
authMethod(() => authInvoke(mt, "callTool", [args])),
|
|
1159
|
+
])
|
|
1160
|
+
: authInvoke(mt, "callTool", [args]));
|
|
1161
|
+
})));
|
|
1048
1162
|
server.registerTool("call_destructive_tool", {
|
|
1049
1163
|
description: ctx.readOnly === true
|
|
1050
1164
|
? CALL_DESTRUCTIVE_READ_ONLY_DESC
|
|
@@ -1055,7 +1169,7 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
1055
1169
|
readOnlyHint: false,
|
|
1056
1170
|
openWorldHint: true,
|
|
1057
1171
|
},
|
|
1058
|
-
},
|
|
1172
|
+
}, authMethod((args, request) => authFlow(function* () {
|
|
1059
1173
|
bindRequest(request);
|
|
1060
1174
|
// `reason` is the host's to display and connecta's to keep out of the
|
|
1061
1175
|
// downstream call, so this destructuring is the whole of its handling:
|
|
@@ -1063,10 +1177,15 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
1063
1177
|
// whitespace-only one "absent" rather than a validation failure — there
|
|
1064
1178
|
// is no field left for it to be absent from.
|
|
1065
1179
|
const { reason: _hostContext, ...call } = args;
|
|
1066
|
-
return ctx.authElicitation
|
|
1067
|
-
? ctx.authElicitation
|
|
1068
|
-
|
|
1069
|
-
|
|
1180
|
+
return yield* upwardStep(ctx.authElicitation
|
|
1181
|
+
? authInvoke(ctx.authElicitation, "run", [
|
|
1182
|
+
"call_destructive_tool",
|
|
1183
|
+
call,
|
|
1184
|
+
request,
|
|
1185
|
+
authMethod(() => authInvoke(mt, "callDestructiveTool", [call])),
|
|
1186
|
+
])
|
|
1187
|
+
: authInvoke(mt, "callDestructiveTool", [call]));
|
|
1188
|
+
})));
|
|
1070
1189
|
server.registerTool("authorize_connector", {
|
|
1071
1190
|
description: AUTHORIZE_DESC,
|
|
1072
1191
|
inputSchema: AUTHORIZE_INPUT,
|
|
@@ -1078,10 +1197,17 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
1078
1197
|
destructiveHint: false,
|
|
1079
1198
|
openWorldHint: true,
|
|
1080
1199
|
},
|
|
1081
|
-
},
|
|
1200
|
+
}, authMethod((args, request) => authFlow(function* () {
|
|
1082
1201
|
bindRequest(request);
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1202
|
+
const run = authMethod(() => authInvoke(mt, "authorizeConnector", [args]));
|
|
1203
|
+
return yield* upwardStep(ctx.authElicitation
|
|
1204
|
+
? authInvoke(ctx.authElicitation, "run", [
|
|
1205
|
+
"authorize_connector",
|
|
1206
|
+
args,
|
|
1207
|
+
request,
|
|
1208
|
+
run,
|
|
1209
|
+
])
|
|
1210
|
+
: authCall(run, []));
|
|
1211
|
+
})));
|
|
1212
|
+
return undefined;
|
|
1087
1213
|
}
|