@zackbart/connecta 0.34.1 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +363 -0
- package/README.md +62 -106
- package/SECURITY.md +3 -2
- package/bin/connecta.mjs +41 -16
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +2 -1
- package/dist/access-tokens.js +66 -30
- package/dist/activity.d.ts +1 -1
- package/dist/activity.js +2 -0
- package/dist/agent-config.d.ts +23 -0
- package/dist/agent-config.js +59 -0
- package/dist/agent-description.js +16 -0
- package/dist/agent-identity.js +4 -4
- package/dist/agent-observation.js +39 -0
- package/dist/auth/clerk-sdk/client.d.ts +2 -0
- package/dist/auth/clerk-sdk/generated.js +554 -563
- package/dist/auth/clerk-transport.js +70 -3
- package/dist/auth/clerk.d.ts +13 -0
- package/dist/auth/clerk.js +653 -246
- package/dist/auth/cloudflare-access.js +46 -42
- package/dist/auth/downstream-client-metadata.js +2 -2
- package/dist/auth/downstream-oauth.js +2051 -1160
- package/dist/auth/downstream-revocation.js +88 -88
- package/dist/auth/native-call.js +20 -0
- package/dist/auth/native.js +184 -0
- package/dist/auth/oauth-start-reset.js +11 -5
- package/dist/auth/oauth-token-response.js +121 -102
- package/dist/auth/oauth-v2-migration.js +97 -78
- package/dist/auth/renewable-grant.js +10 -0
- package/dist/auth/static-oauth.js +663 -271
- package/dist/auth/vercel-agent.js +266 -0
- package/dist/auth-admission.js +204 -0
- package/dist/auth-elicitation.js +350 -241
- package/dist/authorization-handoff.js +164 -90
- package/dist/call-admission-context.js +3 -10
- package/dist/call-admission.js +2 -0
- package/dist/call-classification.js +9 -0
- package/dist/call-recovery.js +32 -13
- package/dist/canonical-json.js +8 -0
- package/dist/catalog-cache.js +369 -213
- package/dist/catalog-service.js +659 -530
- package/dist/catalog.js +5 -1
- package/dist/concurrency.js +0 -25
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +3 -1
- package/dist/config-schema.js +13 -3
- package/dist/config.d.ts +40 -6
- package/dist/config.js +40 -26
- package/dist/connector-caller.d.ts +4 -0
- package/dist/connector-caller.js +17 -4
- package/dist/connectors/api-connector.d.ts +12 -7
- package/dist/connectors/api-connector.js +142 -56
- package/dist/connectors/guarded-fetch.d.ts +51 -17
- package/dist/connectors/guarded-fetch.js +215 -121
- package/dist/connectors/negotiation-cache.js +20 -10
- package/dist/connectors/remote-mcp.d.ts +10 -30
- package/dist/connectors/remote-mcp.js +955 -563
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +3 -1
- package/dist/describe-config.js +2 -0
- package/dist/downstream-input.js +347 -260
- package/dist/errors.d.ts +87 -6
- package/dist/errors.js +114 -4
- package/dist/execute.js +87 -50
- package/dist/executor-admission.js +3 -0
- package/dist/executors/quickjs.js +163 -112
- package/dist/guarded-transport.js +50 -0
- package/dist/guest-runtime.js +1 -1
- package/dist/http/client.js +439 -0
- package/dist/http/errors.js +558 -0
- package/dist/http/fields.js +49 -0
- package/dist/http/hatch-paging.js +318 -0
- package/dist/http/hatch.js +1279 -0
- package/dist/http/json-schema.js +267 -0
- package/dist/http/node-transport.js +241 -0
- package/dist/http/oauth.js +162 -0
- package/dist/http/paging.js +228 -0
- package/dist/http/provider.js +221 -0
- package/dist/http/safety.js +82 -0
- package/dist/http/services.js +79 -0
- package/dist/http/tools.js +383 -0
- package/dist/http/vendor-index.js +138 -0
- package/dist/http/vendor.js +448 -0
- package/dist/http/write-request.js +66 -0
- package/dist/http-policy.d.ts +11 -0
- package/dist/http-policy.js +45 -0
- package/dist/inbound-credential.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/invocation-auth.js +30 -19
- package/dist/invocation.js +218 -85
- package/dist/mcp-result.js +0 -4
- package/dist/membership.d.ts +4 -0
- package/dist/meta-output.js +1 -1
- package/dist/meta-tools.js +531 -406
- package/dist/native-recovery.js +52 -0
- package/dist/native-tools.js +382 -0
- package/dist/node.d.ts +4 -3
- package/dist/node.js +45 -8
- package/dist/oauth-handoff.js +177 -104
- package/dist/oauth-partition.js +6 -20
- package/dist/oauth-sealing.js +6 -7
- package/dist/operation-vocabulary.js +101 -0
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +17 -4
- package/dist/operator-ui/agents.d.ts +5 -4
- package/dist/operator-ui/agents.js +17 -7
- package/dist/operator-ui/config-summary.js +5 -4
- package/dist/operator-ui/connection-map.js +7 -5
- package/dist/operator-ui/contract.d.ts +7 -3
- package/dist/operator-ui/endpoints.js +4 -13
- package/dist/operator-ui/fix-prompts.d.ts +1 -1
- package/dist/operator-ui/fix-prompts.js +0 -8
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/operator-ui/overview.js +5 -2
- package/dist/operator-ui/setup-commands.js +0 -4
- package/dist/operator-ui/view.d.ts +2 -25
- package/dist/operator-ui/view.js +2 -76
- package/dist/program-writes.js +47 -26
- package/dist/provider-call-facts.d.ts +115 -0
- package/dist/provider-call-facts.js +182 -0
- package/dist/provider-call.d.ts +112 -0
- package/dist/provider-call.js +271 -0
- package/dist/provider.d.ts +65 -31
- package/dist/provider.js +116 -39
- package/dist/providers/_shared/google/hatch.js +91 -0
- package/dist/providers/_shared/google/kit.js +537 -0
- package/dist/providers/_shared/google/result-size.js +15 -2
- package/dist/providers/_shared/google/service-account.d.ts +2 -1
- package/dist/providers/_shared/google/service-account.js +8 -4
- package/dist/providers/_shared/google/workspace.d.ts +6 -210
- package/dist/providers/_shared/google/workspace.js +6 -425
- package/dist/providers/_shared/rest/compact-index.js +86 -0
- package/dist/providers/_shared/rest/dispatch.js +3 -23
- package/dist/providers/_shared/rest/kit.js +546 -0
- package/dist/providers/_shared/rest/operation-index.js +230 -14
- package/dist/providers/_shared/rest/response-media.js +11 -0
- package/dist/providers/_shared/rest/tools.js +101 -78
- package/dist/providers/_shared/rest/value-safety.js +353 -195
- package/dist/providers/_shared/sensitive-fields.js +27 -0
- package/dist/providers/breeze/api.js +584 -0
- package/dist/providers/breeze/index.d.ts +1 -3
- package/dist/providers/breeze/index.js +67 -1420
- package/dist/providers/breeze/openapi.generated.js +60 -0
- package/dist/providers/breeze/skill.generated.js +2 -6
- package/dist/providers/breeze/tools.js +696 -0
- package/dist/providers/ccb/api.js +496 -0
- package/dist/providers/ccb/index.d.ts +15 -37
- package/dist/providers/ccb/index.js +77 -1702
- package/dist/providers/ccb/lib/constants.d.ts +34 -0
- package/dist/providers/ccb/lib/constants.js +74 -0
- package/dist/providers/ccb/lib/guide.js +9 -0
- package/dist/providers/ccb/lib/verdicts.js +387 -0
- package/dist/providers/ccb/openapi.generated.js +397 -0
- package/dist/providers/ccb/safety.js +295 -0
- package/dist/providers/ccb/skill.generated.js +2 -6
- package/dist/providers/ccb/tools.js +853 -0
- package/dist/providers/cloudflare/api.js +1066 -0
- package/dist/providers/cloudflare/index.d.ts +5 -22
- package/dist/providers/cloudflare/index.js +79 -197
- package/dist/providers/cloudflare/lib/config.d.ts +16 -0
- package/dist/providers/cloudflare/lib/config.js +2 -0
- package/dist/providers/cloudflare/lib/discovery.js +29 -0
- package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
- package/dist/providers/cloudflare/lib/hatch.js +103 -0
- package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
- package/dist/providers/cloudflare/lib/response-media.js +75 -0
- package/dist/providers/cloudflare/openapi.generated.js +4 -3
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
- package/dist/providers/cloudflare/skill.generated.js +2 -10
- package/dist/providers/cloudflare/tools.js +360 -0
- package/dist/providers/docs/api.js +51 -0
- package/dist/providers/docs/index.d.ts +1 -12
- package/dist/providers/docs/index.js +53 -1165
- package/dist/providers/docs/lib/constants.d.ts +18 -0
- package/dist/providers/docs/lib/constants.js +18 -0
- package/dist/providers/docs/lib/discovery.js +26 -0
- package/dist/providers/docs/lib/requests.js +48 -0
- package/dist/providers/docs/lib/schemas.js +168 -0
- package/dist/providers/docs/openapi.generated.js +16 -0
- package/dist/providers/docs/safety.js +54 -0
- package/dist/providers/docs/skill.generated.js +2 -6
- package/dist/providers/docs/tools.js +802 -0
- package/dist/providers/drive/api.js +177 -0
- package/dist/providers/drive/index.d.ts +1 -12
- package/dist/providers/drive/index.js +38 -1448
- package/dist/providers/drive/lib/constants.d.ts +6 -0
- package/dist/providers/drive/lib/constants.js +6 -0
- package/dist/providers/drive/lib/contracts.js +532 -0
- package/dist/providers/drive/lib/hatch.js +197 -0
- package/dist/providers/drive/openapi.generated.js +77 -0
- package/dist/providers/drive/safety.js +90 -0
- package/dist/providers/drive/skill.generated.js +2 -6
- package/dist/providers/drive/tools.js +1290 -0
- package/dist/providers/forms/api.js +71 -0
- package/dist/providers/forms/index.d.ts +1 -12
- package/dist/providers/forms/index.js +49 -1145
- package/dist/providers/forms/lib/constants.d.ts +14 -0
- package/dist/providers/forms/lib/constants.js +19 -0
- package/dist/providers/forms/lib/hatch.js +72 -0
- package/dist/providers/forms/openapi.generated.js +23 -0
- package/dist/providers/forms/safety.js +34 -0
- package/dist/providers/forms/skill.generated.js +2 -8
- package/dist/providers/forms/tools.js +1024 -0
- package/dist/providers/github/api.js +150 -0
- package/dist/providers/github/index.d.ts +4 -5
- package/dist/providers/github/index.js +30 -87
- package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
- package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
- package/dist/providers/github/lib/discovery.js +132 -0
- package/dist/providers/github/lib/operations.js +58 -0
- package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
- package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
- package/dist/providers/github/openapi.generated.js +1216 -0
- package/dist/providers/github/safety.js +216 -0
- package/dist/providers/github/skill.generated.js +2 -5
- package/dist/providers/github/tools.js +506 -0
- package/dist/providers/gmail/api.js +114 -0
- package/dist/providers/gmail/index.d.ts +1 -12
- package/dist/providers/gmail/index.js +73 -1621
- package/dist/providers/gmail/lib/constants.d.ts +13 -0
- package/dist/providers/gmail/lib/constants.js +16 -0
- package/dist/providers/gmail/lib/index.js +173 -0
- package/dist/providers/gmail/lib/schemas.js +176 -0
- package/dist/providers/gmail/lib/threading.js +120 -0
- package/dist/providers/gmail/openapi.generated.js +92 -0
- package/dist/providers/gmail/safety.js +21 -0
- package/dist/providers/gmail/skill.generated.js +2 -6
- package/dist/providers/gmail/tools.js +1435 -0
- package/dist/providers/overflow/api.js +1008 -0
- package/dist/providers/overflow/index.d.ts +1 -17
- package/dist/providers/overflow/index.js +60 -1488
- package/dist/providers/overflow/skill.generated.js +2 -5
- package/dist/providers/overflow/tools.js +873 -0
- package/dist/providers/planning-center/api.js +467 -0
- package/dist/providers/planning-center/index.d.ts +4 -35
- package/dist/providers/planning-center/index.js +69 -2445
- package/dist/providers/planning-center/lib/constants.d.ts +58 -0
- package/dist/providers/planning-center/lib/constants.js +83 -0
- package/dist/providers/planning-center/lib/discovery.js +40 -0
- package/dist/providers/planning-center/lib/guide.js +39 -0
- package/dist/providers/planning-center/lib/json-api.js +83 -0
- package/dist/providers/planning-center/lib/verdicts.js +1758 -0
- package/dist/providers/planning-center/openapi.generated.js +1764 -0
- package/dist/providers/planning-center/safety.js +188 -0
- package/dist/providers/planning-center/skill.generated.js +2 -27
- package/dist/providers/planning-center/tools.js +1010 -0
- package/dist/providers/sheets/api.js +133 -0
- package/dist/providers/sheets/index.d.ts +4 -16
- package/dist/providers/sheets/index.js +61 -1172
- package/dist/providers/sheets/lib/constants.d.ts +14 -0
- package/dist/providers/sheets/lib/constants.js +14 -0
- package/dist/providers/sheets/lib/hatch.js +163 -0
- package/dist/providers/sheets/lib/requests.js +105 -0
- package/dist/providers/sheets/lib/schemas.js +138 -0
- package/dist/providers/sheets/openapi.generated.js +30 -0
- package/dist/providers/sheets/safety.js +27 -0
- package/dist/providers/sheets/skill.generated.js +2 -7
- package/dist/providers/sheets/tools.js +734 -0
- package/dist/providers/slides/api.js +147 -0
- package/dist/providers/slides/index.d.ts +2 -10
- package/dist/providers/slides/index.js +48 -2555
- package/dist/providers/slides/lib/constants.d.ts +11 -0
- package/dist/providers/slides/lib/constants.js +11 -0
- package/dist/providers/slides/lib/requests.js +60 -0
- package/dist/providers/slides/lib/schemas.js +565 -0
- package/dist/providers/slides/openapi.generated.js +18 -0
- package/dist/providers/slides/skill.generated.js +2 -6
- package/dist/providers/slides/tools.js +1968 -0
- package/dist/providers/stripe/api.js +401 -0
- package/dist/providers/stripe/index.d.ts +5 -27
- package/dist/providers/stripe/index.js +74 -210
- package/dist/providers/stripe/lib/config.d.ts +4 -0
- package/dist/providers/stripe/lib/config.js +2 -0
- package/dist/providers/stripe/openapi.generated.js +4 -3
- package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
- package/dist/providers/stripe/skill.generated.js +2 -11
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +17 -0
- package/dist/providers/tithely/index.js +86 -965
- package/dist/providers/tithely/skill.generated.js +2 -5
- package/dist/providers/tithely/tools.js +426 -0
- package/dist/providers/vercel/api.js +551 -0
- package/dist/providers/vercel/index.d.ts +9 -25
- package/dist/providers/vercel/index.js +66 -260
- package/dist/providers/vercel/lib/config.d.ts +2 -0
- package/dist/providers/vercel/lib/config.js +2 -0
- package/dist/providers/vercel/lib/index.js +117 -0
- package/dist/providers/vercel/lib/logs.js +100 -0
- package/dist/providers/vercel/lib/pin.js +270 -0
- package/dist/providers/vercel/openapi.generated.js +10 -5
- package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
- package/dist/providers/vercel/skill.generated.js +2 -7
- package/dist/providers/vercel/tools.js +642 -0
- package/dist/redaction-identity.js +47 -0
- package/dist/redaction-marker.js +125 -0
- package/dist/registry.d.ts +21 -15
- package/dist/registry.js +409 -376
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/actor-labels.js +1 -1
- package/dist/routes/connect.js +1 -1
- package/dist/routes/dispatch.js +103 -0
- package/dist/routes/mcp.js +170 -79
- package/dist/routes/oauth.js +3 -1
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +5 -4
- package/dist/routes/shared.js +244 -177
- package/dist/routes/skills.js +10 -8
- package/dist/routes/ui-config.js +27 -3
- package/dist/routes/ui-data.js +21 -20
- package/dist/routes/ui.js +1 -1
- package/dist/runtime/call-admission.js +0 -26
- package/dist/runtime/call-entry.js +30 -0
- package/dist/runtime/call.js +556 -0
- package/dist/runtime/connector-scope.js +7 -0
- package/dist/runtime/credential-probe.js +34 -0
- package/dist/runtime/executor.js +40 -0
- package/dist/runtime/native-storage.js +63 -0
- package/dist/runtime/registry.js +27 -0
- package/dist/runtime/request-scope.js +72 -0
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/shared-read.js +34 -5
- package/dist/runtime/storage.js +34 -21
- package/dist/sent-secrets.d.ts +194 -37
- package/dist/sent-secrets.js +1743 -340
- package/dist/server.js +5 -56
- package/dist/skills.js +322 -222
- package/dist/sqlite.js +15 -14
- package/dist/storage/d1-copy.js +255 -0
- package/dist/storage/d1-driver.js +15 -0
- package/dist/storage/keys.js +28 -0
- package/dist/storage/sql-driver.js +88 -0
- package/dist/storage/sql-native.js +544 -0
- package/dist/storage/sql-schema.d.ts +6 -0
- package/dist/storage/sql-schema.js +15 -0
- package/dist/storage/sql.d.ts +14 -20
- package/dist/storage/sql.js +29 -535
- package/dist/timeout.js +0 -26
- package/dist/tool-safety.js +20 -10
- package/dist/types.d.ts +7 -109
- package/dist/upward.js +3 -0
- package/dist/usage-guide.js +3 -1
- package/dist/validate.d.ts +4 -2
- package/dist/validate.js +86 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +312 -292
- package/dist/workspace-subject.js +5 -1
- package/dist/write-outcome.js +454 -34
- package/documentation/README.md +27 -7
- package/documentation/architecture.md +2547 -226
- package/documentation/auth.md +821 -98
- package/documentation/code-mode.md +49 -16
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +129 -144
- package/documentation/meta-tools.md +409 -32
- package/documentation/operating.md +27 -3
- package/documentation/operator-tests.md +3 -0
- package/documentation/operator-ui.md +68 -45
- package/documentation/provider-auth-migration.md +16 -63
- package/documentation/provider-migration-0.29.md +10 -5
- package/documentation/provider-migration-0.35.md +975 -0
- package/documentation/providers.md +965 -0
- package/examples/worker/AGENTS.md +23 -9
- package/examples/worker/README.md +254 -135
- package/examples/worker/alchemy.run.ts +116 -0
- package/examples/worker/gitignore +10 -0
- package/examples/worker/src/connecta.config.ts +23 -21
- package/examples/worker/wrangler.jsonc +14 -5
- package/guides/connecting-agents.md +157 -0
- package/guides/how-it-works.md +156 -0
- package/guides/safety-and-trust.md +153 -0
- package/package.json +27 -46
- package/templates/node/.env.example +3 -0
- package/templates/node/README.md +22 -2
- package/templates/node/docker-compose.yml +1 -0
- package/templates/node/package.json +3 -2
- package/templates/node/src/connecta.config.ts +8 -0
- package/dist/catalog-drift.d.ts +0 -138
- package/dist/catalog-drift.js +0 -578
- package/dist/providers/_shared/request-token.js +0 -83
- package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
- package/dist/providers/_shared/rest/tools.d.ts +0 -141
- package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
- package/dist/providers/basecamp/index.d.ts +0 -45
- package/dist/providers/basecamp/index.js +0 -997
- package/dist/providers/basecamp/skill.generated.js +0 -9
- package/dist/providers/cloudflare/rest.d.ts +0 -37
- package/dist/providers/cloudflare/rest.js +0 -1097
- package/dist/providers/github/hosted.js +0 -218
- package/dist/providers/github/rest.js +0 -321
- package/dist/providers/infisical/index.d.ts +0 -7
- package/dist/providers/infisical/index.js +0 -713
- package/dist/providers/infisical/skill.generated.js +0 -8
- package/dist/providers/intercom/index.d.ts +0 -24
- package/dist/providers/intercom/index.js +0 -121
- package/dist/providers/intercom/skill.generated.js +0 -8
- package/dist/providers/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -537
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -59
- package/dist/providers/mixpanel/index.js +0 -472
- package/dist/providers/mixpanel/operations.js +0 -20
- package/dist/providers/mixpanel/rest.js +0 -603
- package/dist/providers/mixpanel/skill.generated.js +0 -10
- package/dist/providers/mixpanel/value-safety.js +0 -16
- package/dist/providers/notion/index.d.ts +0 -67
- package/dist/providers/notion/index.js +0 -368
- package/dist/providers/notion/openapi.generated.js +0 -75
- package/dist/providers/notion/rest.d.ts +0 -24
- package/dist/providers/notion/rest.js +0 -1226
- package/dist/providers/notion/skill.generated.js +0 -11
- package/dist/providers/notion/value-safety.js +0 -56
- package/dist/providers/revenuecat/index.d.ts +0 -51
- package/dist/providers/revenuecat/index.js +0 -697
- package/dist/providers/revenuecat/skill.generated.js +0 -11
- package/dist/providers/stripe/rest.d.ts +0 -14
- package/dist/providers/stripe/rest.js +0 -402
- package/dist/providers/vercel/rest.d.ts +0 -20
- package/dist/providers/vercel/rest.js +0 -1020
- package/dist/providers/wispr-flow/index.d.ts +0 -14
- package/dist/providers/wispr-flow/index.js +0 -117
- package/dist/providers/wispr-flow/skill.generated.js +0 -8
- /package/dist/providers/github/{key.js → lib/key.js} +0 -0
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)
|
|
@@ -799,7 +872,12 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
799
872
|
const connector = registry.getConnector(dependency.connector);
|
|
800
873
|
if (!connector)
|
|
801
874
|
return false;
|
|
802
|
-
|
|
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();
|
|
803
881
|
const tool = tools.find((tool) => tool.name === dependency.tool);
|
|
804
882
|
if (!tool ||
|
|
805
883
|
!(dependency.access === "catalog" ||
|
|
@@ -813,9 +891,10 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
813
891
|
options.signal?.throwIfAborted();
|
|
814
892
|
return false;
|
|
815
893
|
}
|
|
816
|
-
};
|
|
817
|
-
if (!(
|
|
894
|
+
}));
|
|
895
|
+
if (!(yield* upwardStep(authCall(allowed, []))))
|
|
818
896
|
return denied();
|
|
897
|
+
options.onStage?.("result storage");
|
|
819
898
|
opts.onResultRead?.(binding.program ?? [binding]);
|
|
820
899
|
let requestedOffset = args.offset ?? 0;
|
|
821
900
|
const cap = Number(header[4]);
|
|
@@ -845,7 +924,9 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
845
924
|
bytes = new Uint8Array(Math.max(0, end - start));
|
|
846
925
|
const lastChunk = Math.floor(Math.max(end - 1, start) / chunkBytes);
|
|
847
926
|
for (let index = Math.floor(start / chunkBytes); index <= lastChunk; index++) {
|
|
848
|
-
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)]));
|
|
849
930
|
if (encoded === false)
|
|
850
931
|
return unavailableResult();
|
|
851
932
|
if (encoded === null) {
|
|
@@ -894,28 +975,28 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
894
975
|
hasMore,
|
|
895
976
|
...(hasMore ? { nextOffset: end } : {}),
|
|
896
977
|
};
|
|
897
|
-
if (!(
|
|
978
|
+
if (!(yield* upwardStep(authCall(allowed, []))))
|
|
898
979
|
return denied();
|
|
899
980
|
const page = noticeFirst(pageHeader, slice);
|
|
900
981
|
page.structuredContent = { ...pageHeader, text: slice, format: "text" };
|
|
901
982
|
return page;
|
|
902
|
-
},
|
|
903
|
-
|
|
983
|
+
})),
|
|
984
|
+
authorizeConnector: authMethod((args) => authFlow(function* () {
|
|
904
985
|
try {
|
|
905
|
-
return jsonResult(
|
|
986
|
+
return jsonResult(yield* upwardStep(authCall(authorizeConnector, [registry, baseUrl, requestScope, opts, args])));
|
|
906
987
|
}
|
|
907
988
|
catch (err) {
|
|
908
989
|
return errorResult(msg(err));
|
|
909
990
|
}
|
|
910
|
-
},
|
|
991
|
+
})),
|
|
911
992
|
};
|
|
912
993
|
}
|
|
913
|
-
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".`;
|
|
914
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.`;
|
|
915
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.";
|
|
916
997
|
/** A read-only endpoint keeps the six-tool surface, so this tool says why it never writes here. */
|
|
917
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.";
|
|
918
|
-
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.";
|
|
919
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.";
|
|
920
1001
|
/**
|
|
921
1002
|
* Sentences appended to a meta-tool description only when this connection
|
|
@@ -982,6 +1063,13 @@ const CALL_DESTRUCTIVE_INPUT = advertisedSchema(z.strictObject({
|
|
|
982
1063
|
// trade. It is normalized to absent in the handler instead.
|
|
983
1064
|
reason: z.string().max(500).optional(),
|
|
984
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
|
+
}));
|
|
985
1073
|
const AUTHORIZE_INPUT = advertisedSchema(z.object({
|
|
986
1074
|
connector: z.string(),
|
|
987
1075
|
force: z.boolean().optional(),
|
|
@@ -996,6 +1084,8 @@ const AUTHORIZE_INPUT = advertisedSchema(z.object({
|
|
|
996
1084
|
*/
|
|
997
1085
|
export function registerMetaTools(server, registry, ctx) {
|
|
998
1086
|
const mt = createMetaTools(registry, ctx.baseUrl, {
|
|
1087
|
+
...(ctx.native && !ctx.native.executeCode ? { pagingTool: "read_result" } : {}),
|
|
1088
|
+
...(ctx.native ? { activitySource: "native" } : {}),
|
|
999
1089
|
downstreamInput: Boolean(ctx.authElicitation),
|
|
1000
1090
|
requestScope: ctx.requestScope,
|
|
1001
1091
|
readOnly: ctx.readOnly,
|
|
@@ -1017,22 +1107,40 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
1017
1107
|
};
|
|
1018
1108
|
server.registerTool("skills", {
|
|
1019
1109
|
description: describedFor(registry, SKILLS_DESC, "skills"),
|
|
1110
|
+
...(ctx.native ? { _meta: { "dev.connecta/surface": { surface: "native", ...ctx.native } } } : {}),
|
|
1020
1111
|
inputSchema: SKILLS_INPUT,
|
|
1021
1112
|
outputSchema: SKILLS_OUTPUT,
|
|
1022
1113
|
annotations: READ_ONLY_LOCAL,
|
|
1023
|
-
},
|
|
1114
|
+
}, authMethod((args, request) => authFlow(function* () {
|
|
1024
1115
|
bindRequest(request);
|
|
1025
|
-
return mt
|
|
1026
|
-
});
|
|
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
|
+
}
|
|
1027
1135
|
server.registerTool("search_tools", {
|
|
1028
1136
|
description: describedFor(registry, SEARCH_DESC(ctx.readOnly === true), "search"),
|
|
1029
1137
|
inputSchema: SEARCH_INPUT,
|
|
1030
1138
|
outputSchema: SEARCH_OUTPUT,
|
|
1031
1139
|
annotations: READ_ONLY_REMOTE,
|
|
1032
|
-
},
|
|
1140
|
+
}, authMethod((args, request) => authFlow(function* () {
|
|
1033
1141
|
bindRequest(request);
|
|
1034
|
-
return mt
|
|
1035
|
-
});
|
|
1142
|
+
return yield* upwardStep(authInvoke(mt, "searchTools", [args]));
|
|
1143
|
+
})));
|
|
1036
1144
|
server.registerTool("call_tool", {
|
|
1037
1145
|
description: CALL_DESC(ctx.readOnly === true),
|
|
1038
1146
|
inputSchema: CALL_INPUT,
|
|
@@ -1040,12 +1148,17 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
1040
1148
|
// call_tool admits only tools that are themselves explicitly read-only;
|
|
1041
1149
|
// anything else is refused and routed to call_destructive_tool.
|
|
1042
1150
|
annotations: READ_ONLY_REMOTE,
|
|
1043
|
-
},
|
|
1151
|
+
}, authMethod((args, request) => authFlow(function* () {
|
|
1044
1152
|
bindRequest(request);
|
|
1045
|
-
return ctx.authElicitation
|
|
1046
|
-
? ctx.authElicitation
|
|
1047
|
-
|
|
1048
|
-
|
|
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
|
+
})));
|
|
1049
1162
|
server.registerTool("call_destructive_tool", {
|
|
1050
1163
|
description: ctx.readOnly === true
|
|
1051
1164
|
? CALL_DESTRUCTIVE_READ_ONLY_DESC
|
|
@@ -1056,7 +1169,7 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
1056
1169
|
readOnlyHint: false,
|
|
1057
1170
|
openWorldHint: true,
|
|
1058
1171
|
},
|
|
1059
|
-
},
|
|
1172
|
+
}, authMethod((args, request) => authFlow(function* () {
|
|
1060
1173
|
bindRequest(request);
|
|
1061
1174
|
// `reason` is the host's to display and connecta's to keep out of the
|
|
1062
1175
|
// downstream call, so this destructuring is the whole of its handling:
|
|
@@ -1064,10 +1177,15 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
1064
1177
|
// whitespace-only one "absent" rather than a validation failure — there
|
|
1065
1178
|
// is no field left for it to be absent from.
|
|
1066
1179
|
const { reason: _hostContext, ...call } = args;
|
|
1067
|
-
return ctx.authElicitation
|
|
1068
|
-
? ctx.authElicitation
|
|
1069
|
-
|
|
1070
|
-
|
|
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
|
+
})));
|
|
1071
1189
|
server.registerTool("authorize_connector", {
|
|
1072
1190
|
description: AUTHORIZE_DESC,
|
|
1073
1191
|
inputSchema: AUTHORIZE_INPUT,
|
|
@@ -1079,10 +1197,17 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
1079
1197
|
destructiveHint: false,
|
|
1080
1198
|
openWorldHint: true,
|
|
1081
1199
|
},
|
|
1082
|
-
},
|
|
1200
|
+
}, authMethod((args, request) => authFlow(function* () {
|
|
1083
1201
|
bindRequest(request);
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
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;
|
|
1088
1213
|
}
|