@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/catalog-service.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { registerAuthMethod } from "./auth/native.js";
|
|
2
|
+
import { withAuthorizationHandoffEffect } from "./authorization-handoff.js";
|
|
2
3
|
import { carryCatalogFreshness, catalogIsFresh } from "./catalog-freshness.js";
|
|
4
|
+
import { cloneTool } from "./tool-safety.js";
|
|
3
5
|
import { hasControlCharacters } from "./tool-name.js";
|
|
4
6
|
import { Effect, Result } from "effect";
|
|
5
7
|
import { compactDiscoverySchema, compactDescriptionSchema, lexicalCorpusStatistics, lexicalQueryTerms, lexicalSearchQuery, matchesLexicalTerm, rankTools, schemaObjectKeys, summarizeDiscoveryDescription, summarizeDescription, typescriptSignature, } from "./catalog.js";
|
|
@@ -7,8 +9,12 @@ import { resolveDiscoveryConcurrency } from "./concurrency.js";
|
|
|
7
9
|
import { boundedEchoText, classifyCallError, ConnectorCallError, framingError } from "./errors.js";
|
|
8
10
|
import { connectorGuide, connectorGuideRequired, connectorGuideSummary, connectorSkillName } from "./skills.js";
|
|
9
11
|
import { runEdge, withDeadlineEffect } from "./runtime/run.js";
|
|
12
|
+
import { registryProgram } from "./runtime/registry.js";
|
|
10
13
|
import { SharedRead } from "./runtime/shared-read.js";
|
|
14
|
+
import { duringViewEffect } from "./runtime/call.js";
|
|
11
15
|
import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs } from "./timeout.js";
|
|
16
|
+
import { keepErrorName } from "./redaction-identity.js";
|
|
17
|
+
import { operationVocabularyMatches } from "./operation-vocabulary.js";
|
|
12
18
|
export const DEFAULT_SEARCH_LIMIT = 8;
|
|
13
19
|
export const MAX_SEARCH_LIMIT = 100;
|
|
14
20
|
export const MAX_DESCRIBE_ADDRESSES = 100;
|
|
@@ -22,7 +28,22 @@ const MAX_QUERY_TERM_LENGTH = 64;
|
|
|
22
28
|
*/
|
|
23
29
|
const MAX_IDENTITY_CONNECTORS = 3;
|
|
24
30
|
const MAX_DESCRIBE_SUGGESTIONS = 3;
|
|
31
|
+
const MAX_API_SEARCH_POINTERS = 3;
|
|
32
|
+
const MAX_API_SEARCH_ADDRESS_BYTES = 256;
|
|
33
|
+
/** Recognize the visible standard family without importing provider implementations or indexes. */
|
|
34
|
+
function apiSearchTools(tools) {
|
|
35
|
+
const names = new Set(tools.map((tool) => tool.name));
|
|
36
|
+
return tools.filter((tool) => {
|
|
37
|
+
if (!tool.name.endsWith("_api_search") || tool.classification !== "read")
|
|
38
|
+
return false;
|
|
39
|
+
const prefix = tool.name.slice(0, -"search".length);
|
|
40
|
+
return (names.has(`${prefix}details`) &&
|
|
41
|
+
names.has(`${prefix}read`) &&
|
|
42
|
+
schemaObjectKeys(tool.inputSchema)?.properties.includes("query") === true);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
25
45
|
// Recognized service names are query vocabulary, never a deployment inventory.
|
|
46
|
+
// This includes generic hosted connections such as Notion and Mixpanel.
|
|
26
47
|
// Unknown/custom services use the explicit connector scope. Keep this independent
|
|
27
48
|
// of provider implementations so discovery cannot pull them into root imports.
|
|
28
49
|
const SERVICE_NAMES = [
|
|
@@ -88,6 +109,7 @@ export class DiscoveryPolicyError extends Error {
|
|
|
88
109
|
this.name = "DiscoveryPolicyError";
|
|
89
110
|
}
|
|
90
111
|
}
|
|
112
|
+
keepErrorName(DiscoveryPolicyError, "DiscoveryPolicyError");
|
|
91
113
|
/** Validate before ranking so a huge page request does no proportional work. */
|
|
92
114
|
function discoverySearchLimit(value) {
|
|
93
115
|
if (value === undefined)
|
|
@@ -396,6 +418,7 @@ export class CatalogService {
|
|
|
396
418
|
constructor(registry, baseUrl, options = {}) {
|
|
397
419
|
this.registry = registry;
|
|
398
420
|
this.baseUrl = baseUrl;
|
|
421
|
+
void CatalogService.nativePrograms;
|
|
399
422
|
this.requestScope = options.requestScope ?? {};
|
|
400
423
|
this.probeTimeoutMs = normalizeTimeoutMs(options.probeTimeoutMs) ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
401
424
|
this.concurrency = resolveDiscoveryConcurrency(options.concurrency);
|
|
@@ -426,7 +449,10 @@ export class CatalogService {
|
|
|
426
449
|
: { tool: "search_tools", arguments: searchArgs, purpose };
|
|
427
450
|
}
|
|
428
451
|
loadConnector(id, callOptions = {}) {
|
|
429
|
-
return runEdge(this.
|
|
452
|
+
return runEdge(this.loadConnectorEffect(id, callOptions));
|
|
453
|
+
}
|
|
454
|
+
loadConnectorEffect(id, callOptions = {}) {
|
|
455
|
+
return Effect.suspend(() => duringViewEffect({ requestScope: this.requestScope, signal: callOptions.signal ?? this.requestSignal }, (signal) => this.catalog(id, { ...callOptions, signal }))).pipe(Effect.catchDefect((error) => Effect.fail(error)));
|
|
430
456
|
}
|
|
431
457
|
// One connector's catalog from the request-scoped cache, reading it through
|
|
432
458
|
// the registry when this request has not yet. No asker's options govern the
|
|
@@ -439,23 +465,21 @@ export class CatalogService {
|
|
|
439
465
|
return Effect.suspend(() => {
|
|
440
466
|
let read = this.catalogs.get(id);
|
|
441
467
|
if (!read) {
|
|
442
|
-
const started = new SharedRead((signal) => this.registry
|
|
443
|
-
.getTools(id, this.baseUrl, this.requestScope, {
|
|
468
|
+
const started = new SharedRead((signal) => registryProgram(this.registry, "getTools", id, this.baseUrl, this.requestScope, {
|
|
444
469
|
signal,
|
|
445
470
|
timeoutMs: this.probeTimeoutMs,
|
|
446
471
|
...(this.defer ? { defer: this.defer } : {}),
|
|
447
|
-
})
|
|
448
|
-
.then((tools) => carryCatalogFreshness(tools, structuredClone(tools))), (succeeded) => {
|
|
472
|
+
}).pipe(Effect.map((tools) => carryCatalogFreshness(tools, tools.map(cloneTool)))), (succeeded) => {
|
|
449
473
|
// Evicted before the read resumes anyone, so an asker that
|
|
450
474
|
// retries on hearing of the failure starts a fresh read.
|
|
451
475
|
if (!succeeded && this.catalogs.get(id) === started) {
|
|
452
476
|
this.catalogs.delete(id);
|
|
453
477
|
}
|
|
454
|
-
});
|
|
478
|
+
}, this.requestScope);
|
|
455
479
|
this.catalogs.set(id, started);
|
|
456
480
|
read = started;
|
|
457
481
|
}
|
|
458
|
-
return Effect.map(read.join(callOptions.signal), (tools) => carryCatalogFreshness(tools,
|
|
482
|
+
return Effect.map(read.join(callOptions.signal), (tools) => carryCatalogFreshness(tools, tools.map(cloneTool)));
|
|
459
483
|
});
|
|
460
484
|
}
|
|
461
485
|
// Every named catalog under its own probe deadline, `concurrency` at a time,
|
|
@@ -464,8 +488,8 @@ export class CatalogService {
|
|
|
464
488
|
// A probe also ends with its request, so no listing outlives the caller
|
|
465
489
|
// that asked for it. On a read-only endpoint a write is absent from search
|
|
466
490
|
// and describe, as if the connector never had it. Guides are not filtered.
|
|
467
|
-
discoveryCatalogs(ids, route) {
|
|
468
|
-
return Effect.forEach(ids, (id) => Effect.result(withDeadlineEffect((signal) => this.readOnly || this.registry.getConnector(id)?.readOnly === true
|
|
491
|
+
discoveryCatalogs(ids, route, includeWrites = false) {
|
|
492
|
+
return Effect.forEach(ids, (id) => Effect.result(withDeadlineEffect((signal) => !includeWrites && (this.readOnly || this.registry.getConnector(id)?.readOnly === true)
|
|
469
493
|
? Effect.map(this.catalog(id, { signal }), (tools) => tools.filter((tool) => tool.classification === "read"))
|
|
470
494
|
: this.catalog(id, { signal }), {
|
|
471
495
|
...(this.requestSignal ? { signal: this.requestSignal } : {}),
|
|
@@ -473,6 +497,26 @@ export class CatalogService {
|
|
|
473
497
|
timeoutError: new ConnectorCallError("timeout", `${route} probe of "${id}" timed out after ${this.probeTimeoutMs}ms`),
|
|
474
498
|
})), { concurrency: this.concurrency });
|
|
475
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* Every visible catalog as discovery shows it, for a native `tools/list`:
|
|
502
|
+
* a read-only endpoint or connector omits its writes, and each catalog
|
|
503
|
+
* settles on its own, so one unavailable connector is an `error` entry
|
|
504
|
+
* beside the others rather than a failed listing.
|
|
505
|
+
*/
|
|
506
|
+
listCatalogs(includeWrites = false, connectors = this.registry.listConnectors()) {
|
|
507
|
+
return runEdge(this.listCatalogsEffect(includeWrites, connectors));
|
|
508
|
+
}
|
|
509
|
+
listCatalogsEffect(includeWrites = false, connectors) {
|
|
510
|
+
return duringViewEffect({ requestScope: this.requestScope, signal: this.requestSignal }, () => Effect.suspend(() => {
|
|
511
|
+
const visible = connectors ?? this.registry.listConnectors();
|
|
512
|
+
return Effect.map(this.discoveryCatalogs(visible.map((connector) => connector.id), "tools/list", includeWrites), (results) => results.map((result, index) => {
|
|
513
|
+
const connector = visible[index];
|
|
514
|
+
return Result.isSuccess(result)
|
|
515
|
+
? { connector, tools: result.success }
|
|
516
|
+
: { connector, error: result.failure };
|
|
517
|
+
}));
|
|
518
|
+
})).pipe(Effect.catchDefect((error) => Effect.fail(error)));
|
|
519
|
+
}
|
|
476
520
|
outputSchema(connectorId, tool) {
|
|
477
521
|
if (tool.outputSchema)
|
|
478
522
|
return { schema: tool.outputSchema };
|
|
@@ -515,7 +559,7 @@ export class CatalogService {
|
|
|
515
559
|
};
|
|
516
560
|
}
|
|
517
561
|
resolveTool(address, callOptions = {}) {
|
|
518
|
-
return runEdge(this.resolve(address, callOptions));
|
|
562
|
+
return runEdge(duringViewEffect({ requestScope: this.requestScope, signal: callOptions.signal ?? this.requestSignal }, (signal) => this.resolve(address, { ...callOptions, signal })));
|
|
519
563
|
}
|
|
520
564
|
// resolveTool without the Promise, for InvocationService's pipeline, which
|
|
521
565
|
// yields it inside the call's deadline rather than crossing an edge. Never
|
|
@@ -544,553 +588,638 @@ export class CatalogService {
|
|
|
544
588
|
});
|
|
545
589
|
});
|
|
546
590
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if (error.code === "auth_required" || error.code === "downstream_oauth_required") {
|
|
565
|
-
error.recovery = connector.startAuth
|
|
566
|
-
? "oauth"
|
|
567
|
-
: this.registry.credentialUiAvailable() &&
|
|
568
|
-
connector.credential &&
|
|
569
|
-
this.registry.contextFor(connector.id, this.baseUrl, this.requestScope).credential
|
|
570
|
-
? "operator_config"
|
|
571
|
-
: "unavailable";
|
|
572
|
-
error.nextAction = {
|
|
573
|
-
tool: "authorize_connector",
|
|
574
|
-
arguments: { connector: connector.id },
|
|
575
|
-
operatorHandoff: "Give the URL and instructions it returns to the operator.",
|
|
591
|
+
catalogFailure(connector, failure) {
|
|
592
|
+
return Effect.gen({ self: this }, function* () {
|
|
593
|
+
const classified = classifyCallError(failure, "catalog_lookup_failed");
|
|
594
|
+
const error = {
|
|
595
|
+
connector: connector.id,
|
|
596
|
+
code: classified.code,
|
|
597
|
+
// Catalog discovery reports recovery facts, never connector-authored
|
|
598
|
+
// error text. Clipping a downstream message does not make it safe.
|
|
599
|
+
message: classified.code === "downstream_oauth_required"
|
|
600
|
+
? `Connector "${connector.id}" requires downstream OAuth authorization. When an auth error includes a handoff, give it to the user or operator.`
|
|
601
|
+
: classified.code === "auth_required"
|
|
602
|
+
? `Connector "${connector.id}" requires operator-managed credentials or configuration. When an auth error includes a handoff, give it to the user or operator.`
|
|
603
|
+
: classified.code === "provider_permission_denied"
|
|
604
|
+
? `Connector "${connector.id}" requires permission from the provider's resource owner or administrator. Reconnecting alone will not grant access.`
|
|
605
|
+
: `Connector "${connector.id}" catalog lookup failed (${classified.code}).`,
|
|
606
|
+
retryable: classified.retryable,
|
|
607
|
+
...(classified.retryAfterMs === undefined ? {} : { retryAfterMs: classified.retryAfterMs }),
|
|
576
608
|
};
|
|
577
|
-
error.
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
609
|
+
if (error.code === "auth_required" || error.code === "downstream_oauth_required") {
|
|
610
|
+
error.recovery = connector.startAuth
|
|
611
|
+
? "oauth"
|
|
612
|
+
: this.registry.credentialUiAvailable() &&
|
|
613
|
+
connector.credential &&
|
|
614
|
+
this.registry.contextFor(connector.id, this.baseUrl, this.requestScope).credential
|
|
615
|
+
? "operator_config"
|
|
616
|
+
: "unavailable";
|
|
617
|
+
error.nextAction = {
|
|
618
|
+
tool: "authorize_connector",
|
|
619
|
+
arguments: { connector: connector.id },
|
|
620
|
+
operatorHandoff: "Give the URL and instructions it returns to the operator.",
|
|
621
|
+
};
|
|
622
|
+
error.retry = `Retry discovery after the operator completes recovery for "${connector.id}".`;
|
|
623
|
+
}
|
|
624
|
+
else if (error.code === "provider_permission_denied") {
|
|
625
|
+
error.retry =
|
|
626
|
+
"Ask the provider's resource owner or administrator to grant the required permission or scope, then retry. Reconnecting alone will not grant access.";
|
|
627
|
+
}
|
|
628
|
+
return yield* withAuthorizationHandoffEffect(this.requestScope, error);
|
|
629
|
+
});
|
|
584
630
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
const retrievalQuery = lexicalSearchQuery(query);
|
|
596
|
-
const safety = discoverySafety(args.safety);
|
|
597
|
-
const limit = discoverySearchLimit(args.limit);
|
|
598
|
-
if (args.offset !== undefined &&
|
|
599
|
-
(typeof args.offset !== "number" || !Number.isInteger(args.offset) || args.offset < 0)) {
|
|
600
|
-
throw new DiscoveryPolicyError("invalid_args", "offset must be a non-negative whole number. Start at 0 or use the previous page's nextOffset.");
|
|
601
|
-
}
|
|
602
|
-
const offset = args.offset ?? 0;
|
|
603
|
-
const scopedConnector = args.connector ? this.registry.getConnector(args.connector) : undefined;
|
|
604
|
-
const visibleConnectors = this.registry.listConnectors();
|
|
605
|
-
const namedService = !args.connector ? SERVICE_NAMES.find((name) => containsPhrase(query, name)) : undefined;
|
|
606
|
-
const absentService = args.connector && !scopedConnector
|
|
607
|
-
? args.connector
|
|
608
|
-
: namedService &&
|
|
609
|
-
!visibleConnectors.some((connector) => connectorNames(connector).some((name) => containsPhrase(name, namedService)))
|
|
610
|
-
? namedService
|
|
611
|
-
: undefined;
|
|
612
|
-
const absence = absentService
|
|
613
|
-
? {
|
|
614
|
-
service: absentService,
|
|
615
|
-
message: `No connector for "${absentService}" is configured for this endpoint.`,
|
|
616
|
-
configuredConnectors: visibleConnectors.map((connector) => connector.id),
|
|
631
|
+
search(args, onCatalogReference) {
|
|
632
|
+
return runEdge(this.searchEffect(args, onCatalogReference));
|
|
633
|
+
}
|
|
634
|
+
searchEffect(args, onCatalogReference) {
|
|
635
|
+
return duringViewEffect({ requestScope: this.requestScope, signal: this.requestSignal }, () => this.searchPage(args, onCatalogReference)).pipe(Effect.catchDefect((error) => Effect.fail(error)));
|
|
636
|
+
}
|
|
637
|
+
searchPage(args, onCatalogReference) {
|
|
638
|
+
return Effect.gen({ self: this }, function* () {
|
|
639
|
+
if (args.query !== undefined && typeof args.query !== "string") {
|
|
640
|
+
throw new DiscoveryPolicyError("invalid_args", "query must be a string. Omit it or use an empty string to browse the catalog.");
|
|
617
641
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
const exactConnector = connectorRank > 0;
|
|
647
|
-
const connectorTerms = new Set([
|
|
648
|
-
...connectorNames(connector).flatMap(lexicalQueryTerms),
|
|
649
|
-
...(namedService && connectorNames(connector).some((name) => containsPhrase(name, namedService))
|
|
650
|
-
? lexicalQueryTerms(namedService)
|
|
651
|
-
: []),
|
|
652
|
-
]);
|
|
653
|
-
const localStatistics = exactConnector
|
|
654
|
-
? lexicalCorpusStatistics([catalog.success], retrievalQuery)
|
|
655
|
-
: undefined;
|
|
656
|
-
// An exact identity is a browse even when one tool repeats that
|
|
657
|
-
// identity in its name or description. Action queries still keep
|
|
658
|
-
// identity terms that also name an actual capability.
|
|
659
|
-
const identityBrowse = connectorNames(connector).some((name) => normalizedPhrase(query) === normalizedPhrase(name));
|
|
660
|
-
const toolQuery = identityBrowse
|
|
661
|
-
? ""
|
|
662
|
-
: exactConnector
|
|
663
|
-
? queryTerms
|
|
664
|
-
.filter((term) => !connectorTerms.has(term) ||
|
|
665
|
-
(localStatistics?.nameMatches.get(term)?.size ?? 0) > 0 ||
|
|
666
|
-
(localStatistics?.descriptionMatches.get(term)?.size ?? 0) > 0)
|
|
667
|
-
.join(" ")
|
|
668
|
-
: retrievalQuery;
|
|
669
|
-
const toolTermCount = lexicalQueryTerms(toolQuery).length;
|
|
670
|
-
for (const ranked of rankTools(catalog.success, toolQuery, mode, statistics, query)) {
|
|
671
|
-
collected.push({
|
|
672
|
-
connector,
|
|
673
|
-
tool: ranked.tool,
|
|
674
|
-
score: ranked.score,
|
|
675
|
-
order: orderBase + ranked.order,
|
|
676
|
-
exactName: ranked.exactName,
|
|
677
|
-
exactLookup: rawPhrase === lexicalQueryTerms(ranked.tool.name).join(" ") ||
|
|
678
|
-
rawPhrase === lexicalQueryTerms(`${connector.id}.${ranked.tool.name}`).join(" "),
|
|
679
|
-
connectorRank,
|
|
680
|
-
matchedTermCount: ranked.matchedTermCount,
|
|
681
|
-
complete: isBrowse || ranked.matchedTermCount === toolTermCount,
|
|
682
|
-
});
|
|
683
|
-
}
|
|
642
|
+
if (args.connector !== undefined && boundedEchoText(args.connector) !== args.connector) {
|
|
643
|
+
// The scope is echoed back as `queryAnalysis.connectorScope`; a clipped
|
|
644
|
+
// copy could name a different connector, so refuse instead of clamping.
|
|
645
|
+
throw new DiscoveryPolicyError("invalid_args", "connector must be at most 512 UTF-8 bytes.");
|
|
646
|
+
}
|
|
647
|
+
const query = args.query ?? "";
|
|
648
|
+
const retrievalQuery = lexicalSearchQuery(query);
|
|
649
|
+
const safety = discoverySafety(args.safety);
|
|
650
|
+
const limit = discoverySearchLimit(args.limit);
|
|
651
|
+
if (args.offset !== undefined &&
|
|
652
|
+
(typeof args.offset !== "number" || !Number.isInteger(args.offset) || args.offset < 0)) {
|
|
653
|
+
throw new DiscoveryPolicyError("invalid_args", "offset must be a non-negative whole number. Start at 0 or use the previous page's nextOffset.");
|
|
654
|
+
}
|
|
655
|
+
const offset = args.offset ?? 0;
|
|
656
|
+
const scopedConnector = args.connector ? this.registry.getConnector(args.connector) : undefined;
|
|
657
|
+
const visibleConnectors = this.registry.listConnectors();
|
|
658
|
+
const namedService = !args.connector ? SERVICE_NAMES.find((name) => containsPhrase(query, name)) : undefined;
|
|
659
|
+
const absentService = args.connector && !scopedConnector
|
|
660
|
+
? args.connector
|
|
661
|
+
: namedService &&
|
|
662
|
+
!visibleConnectors.some((connector) => connectorNames(connector).some((name) => containsPhrase(name, namedService)))
|
|
663
|
+
? namedService
|
|
664
|
+
: undefined;
|
|
665
|
+
const absence = absentService
|
|
666
|
+
? {
|
|
667
|
+
service: absentService,
|
|
668
|
+
message: `No connector for "${absentService}" is configured for this endpoint.`,
|
|
669
|
+
configuredConnectors: visibleConnectors.map((connector) => connector.id),
|
|
684
670
|
}
|
|
685
|
-
orderBase += Result.isSuccess(catalog) ? catalog.success.length : 1;
|
|
686
|
-
});
|
|
687
|
-
return collected;
|
|
688
|
-
};
|
|
689
|
-
// A non-empty query that normalizes to no lexical terms is not a browse.
|
|
690
|
-
// Ranking an empty phrase would otherwise return every tool as an
|
|
691
|
-
// unrelated zero-score match, with no coverage to explain the result.
|
|
692
|
-
const rankedMatches = unsearchableQuery ? [] : collectMatches(isBrowse ? "all" : "partial");
|
|
693
|
-
const completeMatchCount = rankedMatches.filter((match) => match.complete).length;
|
|
694
|
-
matches.push(...rankedMatches.filter((match) => match.complete ||
|
|
695
|
-
completeMatchCount === 0 ||
|
|
696
|
-
match.exactName ||
|
|
697
|
-
match.connectorRank > 0 ||
|
|
698
|
-
match.matchedTermCount >= 2));
|
|
699
|
-
if (!isBrowse && !unsearchableQuery && completeMatchCount === 0) {
|
|
700
|
-
matchMode = "partial";
|
|
701
|
-
}
|
|
702
|
-
// Exact tool names precede connector identity and lexical scoring. A
|
|
703
|
-
// longer name such as get_issue_status cannot beat an exact get_issue
|
|
704
|
-
// merely because its description repeats rare query terms.
|
|
705
|
-
matches.sort((a, b) => {
|
|
706
|
-
const aFirstTier = a.complete || a.exactName;
|
|
707
|
-
const bFirstTier = b.complete || b.exactName;
|
|
708
|
-
return (Number(b.exactLookup) - Number(a.exactLookup) ||
|
|
709
|
-
Number(b.exactName) - Number(a.exactName) ||
|
|
710
|
-
b.connectorRank - a.connectorRank ||
|
|
711
|
-
Number(bFirstTier) - Number(aFirstTier) ||
|
|
712
|
-
b.score - a.score ||
|
|
713
|
-
a.order - b.order);
|
|
714
|
-
});
|
|
715
|
-
const pageMatches = matches.slice(offset, offset + limit);
|
|
716
|
-
const withSchemas = args.includeSchemas;
|
|
717
|
-
const entries = pageMatches.map((match) => {
|
|
718
|
-
const output = withSchemas ? this.outputSchema(match.connector.id, match.tool) : {};
|
|
719
|
-
const input = match.tool.inputSchema ?? { type: "object" };
|
|
720
|
-
const signature = withSchemas === "typescript"
|
|
721
|
-
? typescriptSignature(input, output.schema, {
|
|
722
|
-
observed: output.source === "observed",
|
|
723
|
-
description: false,
|
|
724
|
-
})
|
|
725
671
|
: undefined;
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
const
|
|
738
|
-
const
|
|
739
|
-
const
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
672
|
+
const connectors = absence ? [] : args.connector ? (scopedConnector ? [scopedConnector] : []) : visibleConnectors;
|
|
673
|
+
// Timeout messages name the route the caller used.
|
|
674
|
+
const catalogs = yield* this.discoveryCatalogs(connectors.map((connector) => connector.id), this.searchRoute);
|
|
675
|
+
const searchableCatalogs = catalogs.map((catalog) => Result.map(catalog, (tools) => toolsForSafety(tools, safety)));
|
|
676
|
+
const matches = [];
|
|
677
|
+
let matchMode = "all";
|
|
678
|
+
const statistics = lexicalCorpusStatistics(searchableCatalogs.flatMap((catalog) => (Result.isSuccess(catalog) ? [catalog.success] : [])), retrievalQuery);
|
|
679
|
+
const trimmedQuery = query.trim();
|
|
680
|
+
const isBrowse = trimmedQuery.length === 0;
|
|
681
|
+
const queryTerms = lexicalQueryTerms(retrievalQuery);
|
|
682
|
+
const rawPhrase = lexicalQueryTerms(query).join(" ");
|
|
683
|
+
const queryTermCount = queryTerms.length;
|
|
684
|
+
const unsearchableQuery = !isBrowse && queryTermCount === 0;
|
|
685
|
+
const analysisTerms = unsearchableQuery ? [trimmedQuery] : queryTerms;
|
|
686
|
+
const analyzedTerms = analysisTerms.slice(0, MAX_QUERY_TERMS);
|
|
687
|
+
const displayTerm = (term) => boundedQueryTerm(term).text;
|
|
688
|
+
const queryMetadataTruncated = analysisTerms.length > analyzedTerms.length || analyzedTerms.some((term) => boundedQueryTerm(term).truncated);
|
|
689
|
+
const vocabularyTools = new Set();
|
|
690
|
+
const collectMatches = (mode) => {
|
|
691
|
+
const collected = [];
|
|
692
|
+
let orderBase = 0;
|
|
693
|
+
searchableCatalogs.forEach((catalog, connectorIndex) => {
|
|
694
|
+
const connector = connectors[connectorIndex];
|
|
695
|
+
if (!connector) {
|
|
696
|
+
throw new Error("Catalog result has no corresponding connector");
|
|
747
697
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
698
|
+
if (Result.isSuccess(catalog)) {
|
|
699
|
+
const connectorRank = args.connector ? 0 : connectorMatchRank(query, connector);
|
|
700
|
+
const exactConnector = connectorRank > 0;
|
|
701
|
+
const connectorTerms = new Set([
|
|
702
|
+
...connectorNames(connector).flatMap(lexicalQueryTerms),
|
|
703
|
+
...(namedService && connectorNames(connector).some((name) => containsPhrase(name, namedService))
|
|
704
|
+
? lexicalQueryTerms(namedService)
|
|
705
|
+
: []),
|
|
706
|
+
]);
|
|
707
|
+
const localStatistics = exactConnector
|
|
708
|
+
? lexicalCorpusStatistics([catalog.success], retrievalQuery)
|
|
709
|
+
: undefined;
|
|
710
|
+
// An exact identity is a browse even when one tool repeats that
|
|
711
|
+
// identity in its name or description. Action queries still keep
|
|
712
|
+
// identity terms that also name an actual capability.
|
|
713
|
+
const identityBrowse = connectorNames(connector).some((name) => normalizedPhrase(query) === normalizedPhrase(name));
|
|
714
|
+
const toolQuery = identityBrowse
|
|
715
|
+
? ""
|
|
716
|
+
: exactConnector
|
|
717
|
+
? queryTerms
|
|
718
|
+
.filter((term) => !connectorTerms.has(term) ||
|
|
719
|
+
(localStatistics?.nameMatches.get(term)?.size ?? 0) > 0 ||
|
|
720
|
+
(localStatistics?.descriptionMatches.get(term)?.size ?? 0) > 0)
|
|
721
|
+
.join(" ")
|
|
722
|
+
: retrievalQuery;
|
|
723
|
+
const toolTerms = lexicalQueryTerms(toolQuery);
|
|
724
|
+
const toolTermCount = toolTerms.length;
|
|
725
|
+
const rankedTools = rankTools(catalog.success, toolQuery, mode, statistics, query);
|
|
726
|
+
const original = catalogs[connectorIndex];
|
|
727
|
+
if (!isBrowse && query.length <= 200 && queryTerms.length <= 8 && Result.isSuccess(original)) {
|
|
728
|
+
for (const tool of operationVocabularyMatches(connector, original.success, toolTerms.filter((term) => !connectorTerms.has(term)).join(" "), safety, this.readOnly)) {
|
|
729
|
+
vocabularyTools.add(tool);
|
|
730
|
+
if (!rankedTools.some((match) => match.tool === tool))
|
|
731
|
+
rankedTools.push({
|
|
732
|
+
tool,
|
|
733
|
+
score: 0,
|
|
734
|
+
order: original.success.indexOf(tool),
|
|
735
|
+
exactName: false,
|
|
736
|
+
matchedTermCount: toolTermCount,
|
|
737
|
+
});
|
|
738
|
+
}
|
|
764
739
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
740
|
+
for (const ranked of rankedTools) {
|
|
741
|
+
collected.push({
|
|
742
|
+
connector,
|
|
743
|
+
tool: ranked.tool,
|
|
744
|
+
score: vocabularyTools.has(ranked.tool) && !ranked.exactName ? 0 : ranked.score,
|
|
745
|
+
order: orderBase + ranked.order,
|
|
746
|
+
exactName: ranked.exactName,
|
|
747
|
+
exactLookup: rawPhrase === lexicalQueryTerms(ranked.tool.name).join(" ") ||
|
|
748
|
+
rawPhrase === lexicalQueryTerms(`${connector.id}.${ranked.tool.name}`).join(" "),
|
|
749
|
+
connectorRank,
|
|
750
|
+
matchedTermCount: vocabularyTools.has(ranked.tool) ? toolTermCount : ranked.matchedTermCount,
|
|
751
|
+
complete: isBrowse || vocabularyTools.has(ranked.tool) || ranked.matchedTermCount === toolTermCount,
|
|
752
|
+
});
|
|
772
753
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
754
|
+
}
|
|
755
|
+
orderBase += Result.isSuccess(catalog) ? catalog.success.length : 1;
|
|
756
|
+
});
|
|
757
|
+
return collected;
|
|
758
|
+
};
|
|
759
|
+
// A non-empty query that normalizes to no lexical terms is not a browse.
|
|
760
|
+
// Ranking an empty phrase would otherwise return every tool as an
|
|
761
|
+
// unrelated zero-score match, with no coverage to explain the result.
|
|
762
|
+
const rankedMatches = unsearchableQuery ? [] : collectMatches(isBrowse ? "all" : "partial");
|
|
763
|
+
const completeMatchCount = rankedMatches.filter((match) => match.complete).length;
|
|
764
|
+
matches.push(...rankedMatches.filter((match) => match.complete ||
|
|
765
|
+
completeMatchCount === 0 ||
|
|
766
|
+
match.exactName ||
|
|
767
|
+
match.connectorRank > 0 ||
|
|
768
|
+
match.matchedTermCount >= 2));
|
|
769
|
+
if (!isBrowse && !unsearchableQuery && completeMatchCount === 0) {
|
|
770
|
+
matchMode = "partial";
|
|
771
|
+
}
|
|
772
|
+
// Exact tool names precede connector identity and lexical scoring. A
|
|
773
|
+
// longer name such as get_issue_status cannot beat an exact get_issue
|
|
774
|
+
// merely because its description repeats rare query terms.
|
|
775
|
+
matches.sort((a, b) => {
|
|
776
|
+
const aFirstTier = a.complete || a.exactName;
|
|
777
|
+
const bFirstTier = b.complete || b.exactName;
|
|
778
|
+
return (Number(b.exactLookup) - Number(a.exactLookup) ||
|
|
779
|
+
Number(b.exactName) - Number(a.exactName) ||
|
|
780
|
+
b.connectorRank - a.connectorRank ||
|
|
781
|
+
Number(bFirstTier) - Number(aFirstTier) ||
|
|
782
|
+
b.score - a.score ||
|
|
783
|
+
a.order - b.order);
|
|
784
|
+
});
|
|
785
|
+
const pageMatches = matches.slice(offset, offset + limit);
|
|
786
|
+
const withSchemas = args.includeSchemas;
|
|
787
|
+
const entries = pageMatches.map((match) => {
|
|
788
|
+
const output = withSchemas ? this.outputSchema(match.connector.id, match.tool) : {};
|
|
789
|
+
const input = match.tool.inputSchema ?? { type: "object" };
|
|
790
|
+
const signature = withSchemas === "typescript"
|
|
791
|
+
? typescriptSignature(input, output.schema, {
|
|
792
|
+
observed: output.source === "observed",
|
|
793
|
+
description: false,
|
|
794
|
+
})
|
|
795
|
+
: undefined;
|
|
796
|
+
const schemaFormat = withSchemas === "typescript" ? undefined : withSchemas;
|
|
797
|
+
const renderedInput = signature
|
|
798
|
+
? { schema: undefined, truncated: signature.inputTruncated }
|
|
799
|
+
: schemaFormat
|
|
800
|
+
? renderSearchSchema(input, schemaFormat)
|
|
801
|
+
: undefined;
|
|
802
|
+
const renderedOutput = signature
|
|
803
|
+
? { schema: undefined, truncated: signature.outputTruncated }
|
|
804
|
+
: schemaFormat && output.schema
|
|
805
|
+
? renderSearchSchema(output.schema, schemaFormat)
|
|
806
|
+
: undefined;
|
|
807
|
+
const schemaKeys = withSchemas && args.includeSchemaKeys ? schemaKeyMetadata(input, output.schema) : undefined;
|
|
808
|
+
const description = summarizeDiscoveryDescription(match.tool.description, args.fullDescriptions === true);
|
|
809
|
+
const requiredReasons = guideRequiredReasons(match.connector, match.tool, renderedInput?.truncated === true || renderedOutput?.truncated === true);
|
|
810
|
+
const guideSummary = connectorGuideSummary(match.connector);
|
|
811
|
+
return {
|
|
812
|
+
connector: match.connector,
|
|
813
|
+
...(connectorGuide(match.connector)
|
|
778
814
|
? {
|
|
779
|
-
|
|
780
|
-
|
|
815
|
+
guide: connectorSkillName(match.connector.id),
|
|
816
|
+
...(guideSummary ? { guideSummary } : {}),
|
|
781
817
|
}
|
|
782
818
|
: {}),
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
const matchingTools = (term) => new Set([...(statistics.nameMatches.get(term) ?? []), ...(statistics.descriptionMatches.get(term) ?? [])]);
|
|
789
|
-
const representedTerms = [];
|
|
790
|
-
const otherResultTerms = [];
|
|
791
|
-
const unmatchedTerms = [];
|
|
792
|
-
for (const term of analyzedTerms) {
|
|
793
|
-
const termTools = matchingTools(term);
|
|
794
|
-
if ([...termTools].some((tool) => pageTools.has(tool))) {
|
|
795
|
-
representedTerms.push(displayTerm(term));
|
|
796
|
-
}
|
|
797
|
-
else if (termTools.size > 0) {
|
|
798
|
-
otherResultTerms.push(displayTerm(term));
|
|
799
|
-
}
|
|
800
|
-
else {
|
|
801
|
-
unmatchedTerms.push(displayTerm(term));
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
const unavailableCatalogs = catalogs.filter(Result.isFailure).length;
|
|
805
|
-
const catalogErrors = (await Promise.all(catalogs.map(async (catalog, index) => {
|
|
806
|
-
if (Result.isSuccess(catalog))
|
|
807
|
-
return [];
|
|
808
|
-
return [await this.catalogFailure(connectors[index], catalog.failure)];
|
|
809
|
-
}))).flat();
|
|
810
|
-
const needsAuth = (error) => error.code === "auth_required" ||
|
|
811
|
-
error.code === "downstream_oauth_required" ||
|
|
812
|
-
error.code === "provider_permission_denied";
|
|
813
|
-
catalogErrors.sort((a, b) => Number(needsAuth(b)) - Number(needsAuth(a)));
|
|
814
|
-
const scopedFailure = args.connector ? catalogErrors[0] : undefined;
|
|
815
|
-
const scopedCatalogError = scopedFailure
|
|
816
|
-
? scopedFailure.code === "auth_required" || scopedFailure.code === "downstream_oauth_required"
|
|
817
|
-
? scopedFailure
|
|
818
|
-
: {
|
|
819
|
-
code: scopedFailure.code,
|
|
820
|
-
message: scopedFailure.message,
|
|
821
|
-
retryable: scopedFailure.retryable,
|
|
822
|
-
...(scopedFailure.retryAfterMs === undefined ? {} : { retryAfterMs: scopedFailure.retryAfterMs }),
|
|
823
|
-
}
|
|
824
|
-
: undefined;
|
|
825
|
-
const safetyLabel = safety === "readOnly" ? "read-only " : safety === "approvalRequired" ? "approval-required " : "";
|
|
826
|
-
const filterRecovery = safety === "all" ? "" : " Change safety to inspect the other tools.";
|
|
827
|
-
const scopedGuide = matches.length === 0 && scopedConnector && connectorGuide(scopedConnector)
|
|
828
|
-
? {
|
|
829
|
-
guide: connectorSkillName(scopedConnector.id),
|
|
830
|
-
guideSummary: connectorGuideSummary(scopedConnector),
|
|
831
|
-
required: connectorGuideRequired(scopedConnector),
|
|
832
|
-
}
|
|
833
|
-
: undefined;
|
|
834
|
-
// Partial identity words can still explain a miss. Exact identities are
|
|
835
|
-
// already ranking inputs above; this guidance handles a title word such
|
|
836
|
-
// as "warehouse" when the full title is "Warehouse stock".
|
|
837
|
-
const identityConnectorIds = matches.length === 0 && !isBrowse && !unsearchableQuery && !scopedConnector
|
|
838
|
-
? connectors
|
|
839
|
-
// The full query, not `analyzedTerms`: that cap exists to bound
|
|
840
|
-
// the serialized term fields, and ranking already reads every
|
|
841
|
-
// term. A ninth term is a real search term, and a search that
|
|
842
|
-
// ranked against it must not deny the connector it names.
|
|
843
|
-
.filter((connector) => queryTerms.some((term) => matchesLexicalTerm(connector.id, term) ||
|
|
844
|
-
(connector.title !== undefined && matchesLexicalTerm(connector.title, term))))
|
|
845
|
-
.map((connector) => connector.id)
|
|
846
|
-
: [];
|
|
847
|
-
const namedIdentityConnectors = identityConnectorIds
|
|
848
|
-
.slice(0, MAX_IDENTITY_CONNECTORS)
|
|
849
|
-
.map((id) => `"${id}"`)
|
|
850
|
-
.join(", ");
|
|
851
|
-
const unnamedIdentityConnectors = identityConnectorIds.length - MAX_IDENTITY_CONNECTORS;
|
|
852
|
-
const identityGuidance = identityConnectorIds.length === 0
|
|
853
|
-
? undefined
|
|
854
|
-
: `No matching ${safetyLabel}capability was found${unavailableCatalogs === 0 ? "" : " in the catalogs that answered"}, but the query names configured connector${identityConnectorIds.length === 1 ? "" : "s"} ${namedIdentityConnectors}${unnamedIdentityConnectors > 0 ? ` and ${unnamedIdentityConnectors} more` : ""}. Scope by connector and browse with an empty query to list the tools there.${filterRecovery}`;
|
|
855
|
-
// A scope that resolved to nothing is the same silence one step earlier in
|
|
856
|
-
// the lookup: no connector resolved, so no catalog was even attempted, so
|
|
857
|
-
// no catalog failed and the unavailable path below never fires. Name
|
|
858
|
-
// only configured connectors in the current identity/pool registry view.
|
|
859
|
-
const unknownConnectorGuidance = args.connector && !scopedConnector
|
|
860
|
-
? `Connector "${args.connector}" is not configured in this deployment. Omit connector to search all configured tools. Configured connectors for this endpoint: ${this.registry
|
|
861
|
-
.listConnectors()
|
|
862
|
-
.map((connector) => connector.id)
|
|
863
|
-
.join(", ") || "none"}.`
|
|
864
|
-
: undefined;
|
|
865
|
-
// Searchable queries report analysis when the scorer had to degrade. A
|
|
866
|
-
// non-empty query with no searchable terms reports the bounded raw input
|
|
867
|
-
// as unmatched instead of silently becoming a browse. A real browse has
|
|
868
|
-
// no terms to analyse and normally reports none at all. Scope failures are
|
|
869
|
-
// the exception, because an empty result alone looks like a connector that
|
|
870
|
-
// correctly exposes no tools.
|
|
871
|
-
const reportsQueryAnalysis = absence !== undefined ||
|
|
872
|
-
unsearchableQuery ||
|
|
873
|
-
(queryTerms.length > 0
|
|
874
|
-
? matchMode === "partial"
|
|
875
|
-
: unknownConnectorGuidance !== undefined || unavailableCatalogs > 0);
|
|
876
|
-
const searchGuidance = () => {
|
|
877
|
-
if (unknownConnectorGuidance)
|
|
878
|
-
return unknownConnectorGuidance;
|
|
879
|
-
if (absence)
|
|
880
|
-
return absence.message;
|
|
881
|
-
if (unsearchableQuery) {
|
|
882
|
-
return scopedConnector && unavailableCatalogs > 0
|
|
883
|
-
? `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
884
|
-
: "The query contained no searchable lexical terms. Use 2–4 ASCII action/object terms, or browse with an empty query.";
|
|
885
|
-
}
|
|
886
|
-
if (queryTerms.length === 0) {
|
|
887
|
-
if (unavailableCatalogs === 0)
|
|
888
|
-
return undefined;
|
|
889
|
-
return scopedConnector
|
|
890
|
-
? `Connector "${scopedConnector.id}" could not be browsed because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
891
|
-
: `${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable, so this browse is incomplete. Inspect catalogErrors for the typed reasons and recovery actions.`;
|
|
892
|
-
}
|
|
893
|
-
if (matches.length === 0) {
|
|
894
|
-
if (scopedConnector) {
|
|
895
|
-
if (unavailableCatalogs > 0) {
|
|
896
|
-
return `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`;
|
|
897
|
-
}
|
|
898
|
-
return scopedGuide?.required
|
|
899
|
-
? `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Fetch queryAnalysis.guide before calling, then refine terms or browse with an empty query.${filterRecovery}`
|
|
900
|
-
: `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Refine terms or browse it with an empty query.${filterRecovery}`;
|
|
901
|
-
}
|
|
902
|
-
if (identityGuidance)
|
|
903
|
-
return identityGuidance;
|
|
904
|
-
return unavailableCatalogs === 0
|
|
905
|
-
? `No matching ${safetyLabel}capability is configured in this deployment. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`
|
|
906
|
-
: `No matching ${safetyLabel}capability was found in the catalogs that answered; ${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`;
|
|
907
|
-
}
|
|
908
|
-
if (matchMode !== "partial")
|
|
909
|
-
return undefined;
|
|
910
|
-
if (scopedConnector) {
|
|
911
|
-
return `No single tool on connector "${scopedConnector.id}" matched every term. Split distinct intents into separate searches.`;
|
|
912
|
-
}
|
|
913
|
-
return unavailableCatalogs === 0
|
|
914
|
-
? "No single tool matched every term. Split distinct intents into separate searches."
|
|
915
|
-
: "No single tool matched every term in the catalogs that answered. Split distinct intents into separate searches.";
|
|
916
|
-
};
|
|
917
|
-
const guidance = searchGuidance();
|
|
918
|
-
// Counts, ranking, and term analysis use the whole searched corpus, not
|
|
919
|
-
// just this page's tools. Bind every searched connector, including failed
|
|
920
|
-
// catalogs and off-page matches, so paging cannot reveal a hidden
|
|
921
|
-
// connector's contribution. Keep provenance host-only.
|
|
922
|
-
for (const connector of connectors)
|
|
923
|
-
onCatalogReference?.(connector.id);
|
|
924
|
-
for (const entry of entries)
|
|
925
|
-
onCatalogReference?.(entry.connector.id, entry.tool.name);
|
|
926
|
-
for (const connector of absence?.configuredConnectors ?? [])
|
|
927
|
-
onCatalogReference?.(connector);
|
|
928
|
-
// An unknown scope's guidance lists the visible roster without searching it.
|
|
929
|
-
if (reportsQueryAnalysis && args.connector && !scopedConnector)
|
|
930
|
-
for (const connector of visibleConnectors)
|
|
931
|
-
onCatalogReference?.(connector.id);
|
|
932
|
-
return {
|
|
933
|
-
catalogErrors,
|
|
934
|
-
...(absence ? { absence } : {}),
|
|
935
|
-
entries,
|
|
936
|
-
total: matches.length,
|
|
937
|
-
offset,
|
|
938
|
-
limit,
|
|
939
|
-
hasMore: nextOffset !== undefined,
|
|
940
|
-
...(nextOffset !== undefined ? { nextOffset } : {}),
|
|
941
|
-
...(matchMode === "partial" && matches.length > 0 ? { matchMode } : {}),
|
|
942
|
-
...(reportsQueryAnalysis
|
|
943
|
-
? {
|
|
944
|
-
queryAnalysis: {
|
|
945
|
-
representedTerms,
|
|
946
|
-
otherResultTerms,
|
|
947
|
-
unmatchedTerms,
|
|
948
|
-
...(queryMetadataTruncated ? { truncated: true } : {}),
|
|
949
|
-
...(args.connector ? { connectorScope: args.connector } : {}),
|
|
950
|
-
...(args.connector && !scopedConnector
|
|
819
|
+
tool: {
|
|
820
|
+
name: match.tool.name,
|
|
821
|
+
address: `${match.connector.id}.${match.tool.name}`,
|
|
822
|
+
...(description !== undefined ? { description } : {}),
|
|
823
|
+
...(schemaFormat
|
|
951
824
|
? {
|
|
952
|
-
|
|
953
|
-
configuredConnectors: this.registry.listConnectors().map((connector) => connector.id),
|
|
825
|
+
inputSchema: renderedInput?.schema,
|
|
954
826
|
}
|
|
955
827
|
: {}),
|
|
956
|
-
...(
|
|
957
|
-
...(
|
|
958
|
-
...(
|
|
828
|
+
...(withSchemas ? { schemaFormat: withSchemas === "json" ? "json" : "text" } : {}),
|
|
829
|
+
...(signature ? { signature: signature.text } : {}),
|
|
830
|
+
...(renderedInput?.truncated ? { inputSchemaTruncated: true } : {}),
|
|
831
|
+
...(schemaFormat && output.schema
|
|
959
832
|
? {
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
833
|
+
outputSchema: renderedOutput?.schema,
|
|
834
|
+
}
|
|
835
|
+
: {}),
|
|
836
|
+
...(withSchemas && output.source ? { outputSchemaSource: output.source } : {}),
|
|
837
|
+
...(renderedOutput?.truncated ? { outputSchemaTruncated: true } : {}),
|
|
838
|
+
...(schemaKeys && !renderedInput?.truncated
|
|
839
|
+
? {
|
|
840
|
+
...(schemaKeys.inputKeys ? { inputKeys: schemaKeys.inputKeys } : {}),
|
|
841
|
+
...(schemaKeys.requiredInputKeys ? { requiredInputKeys: schemaKeys.requiredInputKeys } : {}),
|
|
842
|
+
}
|
|
843
|
+
: {}),
|
|
844
|
+
...(schemaKeys?.outputKeys && !renderedOutput?.truncated ? { outputKeys: schemaKeys.outputKeys } : {}),
|
|
845
|
+
...(match.tool.annotations ? { annotations: match.tool.annotations } : {}),
|
|
846
|
+
classification: match.tool.classification,
|
|
847
|
+
...(requiredReasons
|
|
848
|
+
? {
|
|
849
|
+
guideRequired: true,
|
|
850
|
+
guideRequiredReasons: requiredReasons,
|
|
968
851
|
}
|
|
969
852
|
: {}),
|
|
970
|
-
...(guidance ? { guidance } : {}),
|
|
971
|
-
},
|
|
972
|
-
}
|
|
973
|
-
: {}),
|
|
974
|
-
};
|
|
975
|
-
}
|
|
976
|
-
async describe(args) {
|
|
977
|
-
const addresses = discoveryAddresses(args);
|
|
978
|
-
const format = args.format ?? "compact";
|
|
979
|
-
const resolved = addresses.map((rawAddress) => {
|
|
980
|
-
const address = String(rawAddress);
|
|
981
|
-
return { address, resolved: this.registry.resolveAddress(address) };
|
|
982
|
-
});
|
|
983
|
-
const connectorIds = [
|
|
984
|
-
...new Set(resolved.map((entry) => entry.resolved?.connector.id).filter((id) => Boolean(id))),
|
|
985
|
-
];
|
|
986
|
-
const loaded = await runEdge(this.discoveryCatalogs(connectorIds, "connecta.describe"));
|
|
987
|
-
const catalogs = new Map(connectorIds.map((id, index) => [id, loaded[index]]));
|
|
988
|
-
const authFailures = new Map(await Promise.all(connectorIds.map(async (id) => {
|
|
989
|
-
const catalog = catalogs.get(id);
|
|
990
|
-
if (!catalog || !Result.isFailure(catalog))
|
|
991
|
-
return [id, undefined];
|
|
992
|
-
const classified = classifyCallError(catalog.failure, "catalog_lookup_failed");
|
|
993
|
-
const connector = this.registry.getConnector(id);
|
|
994
|
-
const error = classified.code === "auth_required" || classified.code === "downstream_oauth_required"
|
|
995
|
-
? await this.catalogFailure(connector, catalog.failure)
|
|
996
|
-
: undefined;
|
|
997
|
-
return [id, error];
|
|
998
|
-
})));
|
|
999
|
-
return resolved.map(({ address, resolved: addressResolution }) => {
|
|
1000
|
-
if (!addressResolution) {
|
|
1001
|
-
const message = `Unknown address "${boundedEchoText(address)}"`;
|
|
1002
|
-
return {
|
|
1003
|
-
address: boundedEchoText(address),
|
|
1004
|
-
error: message,
|
|
1005
|
-
errorDetails: {
|
|
1006
|
-
...framingError("unknown_address", message),
|
|
1007
|
-
configuredConnectors: this.registry.listConnectors().map((connector) => connector.id),
|
|
1008
|
-
nextAction: this.searchRecovery({ query: recoveryQuery(address) }, "Find the configured canonical address before retrying."),
|
|
1009
|
-
},
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
const catalog = catalogs.get(addressResolution.connector.id);
|
|
1013
|
-
if (catalog && Result.isFailure(catalog)) {
|
|
1014
|
-
const authFailure = authFailures.get(addressResolution.connector.id);
|
|
1015
|
-
if (authFailure)
|
|
1016
|
-
return { address: boundedEchoText(address), error: authFailure.message, errorDetails: authFailure };
|
|
1017
|
-
const classified = classifyCallError(catalog.failure, "catalog_lookup_failed");
|
|
1018
|
-
const message = boundedEchoText(classified.message);
|
|
1019
|
-
return {
|
|
1020
|
-
address: boundedEchoText(address),
|
|
1021
|
-
error: message,
|
|
1022
|
-
errorDetails: {
|
|
1023
|
-
code: classified.code,
|
|
1024
|
-
message,
|
|
1025
|
-
retryable: classified.retryable,
|
|
1026
|
-
...(classified.retryAfterMs === undefined ? {} : { retryAfterMs: classified.retryAfterMs }),
|
|
1027
|
-
},
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
const tools = catalog?.success ?? [];
|
|
1031
|
-
const tool = tools.find((item) => item.name === addressResolution.toolName);
|
|
1032
|
-
if (!tool) {
|
|
1033
|
-
const message = `Unknown tool "${boundedEchoText(addressResolution.toolName)}" on connector "${addressResolution.connector.id}"`;
|
|
1034
|
-
const suggestions = describeSuggestions(addressResolution.connector.id, addressResolution.toolName, tools);
|
|
1035
|
-
return {
|
|
1036
|
-
address: boundedEchoText(address),
|
|
1037
|
-
error: message,
|
|
1038
|
-
errorDetails: {
|
|
1039
|
-
...framingError("unknown_tool", message),
|
|
1040
|
-
nextAction: this.searchRecovery({
|
|
1041
|
-
query: recoveryQuery(addressResolution.toolName),
|
|
1042
|
-
connector: addressResolution.connector.id,
|
|
1043
|
-
}, "Find the connector's current canonical tool address."),
|
|
1044
|
-
...(suggestions.length > 0 ? { suggestions } : {}),
|
|
1045
853
|
},
|
|
1046
854
|
};
|
|
855
|
+
});
|
|
856
|
+
const nextOffset = offset + entries.length < matches.length ? offset + entries.length : undefined;
|
|
857
|
+
const pageTools = new Set(pageMatches.map((match) => match.tool));
|
|
858
|
+
const matchingTools = (term) => new Set([
|
|
859
|
+
...(statistics.nameMatches.get(term) ?? []),
|
|
860
|
+
...(statistics.descriptionMatches.get(term) ?? []),
|
|
861
|
+
...vocabularyTools,
|
|
862
|
+
]);
|
|
863
|
+
const representedTerms = [];
|
|
864
|
+
const otherResultTerms = [];
|
|
865
|
+
const unmatchedTerms = [];
|
|
866
|
+
for (const term of analyzedTerms) {
|
|
867
|
+
const termTools = matchingTools(term);
|
|
868
|
+
if ([...termTools].some((tool) => pageTools.has(tool))) {
|
|
869
|
+
representedTerms.push(displayTerm(term));
|
|
870
|
+
}
|
|
871
|
+
else if (termTools.size > 0) {
|
|
872
|
+
otherResultTerms.push(displayTerm(term));
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
unmatchedTerms.push(displayTerm(term));
|
|
876
|
+
}
|
|
1047
877
|
}
|
|
1048
|
-
const
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
const
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
schemaFormat: format === "json" ? "json" : "text",
|
|
1066
|
-
name: tool.name,
|
|
1067
|
-
...(description !== undefined ? { description } : {}),
|
|
1068
|
-
...(connectorGuide(addressResolution.connector)
|
|
1069
|
-
? {
|
|
1070
|
-
guide: connectorSkillName(addressResolution.connector.id),
|
|
1071
|
-
...(guideSummary ? { guideSummary } : {}),
|
|
878
|
+
const unavailableCatalogs = catalogs.filter(Result.isFailure).length;
|
|
879
|
+
const catalogErrors = (yield* Effect.forEach(catalogs, (catalog, index) => Result.isSuccess(catalog)
|
|
880
|
+
? Effect.succeed([])
|
|
881
|
+
: this.catalogFailure(connectors[index], catalog.failure).pipe(Effect.map((error) => [error])), { concurrency: "unbounded" })).flat();
|
|
882
|
+
const needsAuth = (error) => error.code === "auth_required" ||
|
|
883
|
+
error.code === "downstream_oauth_required" ||
|
|
884
|
+
error.code === "provider_permission_denied";
|
|
885
|
+
catalogErrors.sort((a, b) => Number(needsAuth(b)) - Number(needsAuth(a)));
|
|
886
|
+
const scopedFailure = args.connector ? catalogErrors[0] : undefined;
|
|
887
|
+
const scopedCatalogError = scopedFailure
|
|
888
|
+
? scopedFailure.code === "auth_required" || scopedFailure.code === "downstream_oauth_required"
|
|
889
|
+
? scopedFailure
|
|
890
|
+
: {
|
|
891
|
+
code: scopedFailure.code,
|
|
892
|
+
message: scopedFailure.message,
|
|
893
|
+
retryable: scopedFailure.retryable,
|
|
894
|
+
...(scopedFailure.retryAfterMs === undefined ? {} : { retryAfterMs: scopedFailure.retryAfterMs }),
|
|
1072
895
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
896
|
+
: undefined;
|
|
897
|
+
const safetyLabel = safety === "readOnly" ? "read-only " : safety === "approvalRequired" ? "approval-required " : "";
|
|
898
|
+
const filterRecovery = safety === "all" ? "" : " Change safety to inspect the other tools.";
|
|
899
|
+
const scopedGuide = matches.length === 0 && scopedConnector && connectorGuide(scopedConnector)
|
|
900
|
+
? {
|
|
901
|
+
guide: connectorSkillName(scopedConnector.id),
|
|
902
|
+
guideSummary: connectorGuideSummary(scopedConnector),
|
|
903
|
+
required: connectorGuideRequired(scopedConnector),
|
|
904
|
+
}
|
|
905
|
+
: undefined;
|
|
906
|
+
// Partial identity words can still explain a miss. Exact identities are
|
|
907
|
+
// already ranking inputs above; this guidance handles a title word such
|
|
908
|
+
// as "warehouse" when the full title is "Warehouse stock".
|
|
909
|
+
const identityConnectorIds = matches.length === 0 && !isBrowse && !unsearchableQuery && !scopedConnector
|
|
910
|
+
? connectors
|
|
911
|
+
// The full query, not `analyzedTerms`: that cap exists to bound
|
|
912
|
+
// the serialized term fields, and ranking already reads every
|
|
913
|
+
// term. A ninth term is a real search term, and a search that
|
|
914
|
+
// ranked against it must not deny the connector it names.
|
|
915
|
+
.filter((connector) => queryTerms.some((term) => matchesLexicalTerm(connector.id, term) ||
|
|
916
|
+
(connector.title !== undefined && matchesLexicalTerm(connector.title, term))))
|
|
917
|
+
.map((connector) => connector.id)
|
|
918
|
+
: [];
|
|
919
|
+
const namedIdentityConnectors = identityConnectorIds
|
|
920
|
+
.slice(0, MAX_IDENTITY_CONNECTORS)
|
|
921
|
+
.map((id) => `"${id}"`)
|
|
922
|
+
.join(", ");
|
|
923
|
+
const unnamedIdentityConnectors = identityConnectorIds.length - MAX_IDENTITY_CONNECTORS;
|
|
924
|
+
const identityGuidance = identityConnectorIds.length === 0
|
|
925
|
+
? undefined
|
|
926
|
+
: `No matching ${safetyLabel}capability was found${unavailableCatalogs === 0 ? "" : " in the catalogs that answered"}, but the query names configured connector${identityConnectorIds.length === 1 ? "" : "s"} ${namedIdentityConnectors}${unnamedIdentityConnectors > 0 ? ` and ${unnamedIdentityConnectors} more` : ""}. Scope by connector and browse with an empty query to list the tools there.`;
|
|
927
|
+
// Bounded vocabulary is not an exhaustive operation search. Keep the
|
|
928
|
+
// fallback to already-loaded visible tools, in stable connector order.
|
|
929
|
+
// The caller's safety filter is not a visibility boundary: a read search
|
|
930
|
+
// tool is also how one discovers indexed writes for approvalRequired.
|
|
931
|
+
const apiSearchPointers = matches.length === 0 && !isBrowse && !unsearchableQuery && !(this.readOnly && safety === "approvalRequired")
|
|
932
|
+
? catalogs.flatMap((catalog, index) => Result.isSuccess(catalog) && !(safety === "approvalRequired" && connectors[index].readOnly === true)
|
|
933
|
+
? apiSearchTools(catalog.success).map((tool) => ({ connector: connectors[index].id, tool: tool.name }))
|
|
934
|
+
: [])
|
|
935
|
+
: [];
|
|
936
|
+
const namedApiSearchPointers = apiSearchPointers
|
|
937
|
+
.filter(({ connector, tool }) => encoder.encode(`${connector}.${tool}`).length <= MAX_API_SEARCH_ADDRESS_BYTES)
|
|
938
|
+
.slice(0, MAX_API_SEARCH_POINTERS);
|
|
939
|
+
const omittedApiSearchPointers = apiSearchPointers.length - namedApiSearchPointers.length;
|
|
940
|
+
const apiSearchGuidance = apiSearchPointers.length === 0
|
|
941
|
+
? undefined
|
|
942
|
+
: "No catalog tool matched this query. Bounded API operation vocabulary was consulted; full API operation indexes have not been searched." +
|
|
943
|
+
(namedApiSearchPointers.length
|
|
944
|
+
? ` Search operations with ${namedApiSearchPointers.map(({ connector, tool }) => `\`${connector}.${tool}\``).join(", ")}.`
|
|
945
|
+
: "") +
|
|
946
|
+
(omittedApiSearchPointers
|
|
947
|
+
? ` ${omittedApiSearchPointers} API search tool${omittedApiSearchPointers === 1 ? " was" : "s were"} omitted.`
|
|
948
|
+
: "") +
|
|
949
|
+
' To list every API search tool, browse with an empty query and follow all pages, or search for "_api_search".' +
|
|
950
|
+
(scopedGuide?.required
|
|
951
|
+
? " Fetch queryAnalysis.guide before calling."
|
|
952
|
+
: " Read the connector guide before calling when discovery requires it.") +
|
|
953
|
+
(unavailableCatalogs
|
|
954
|
+
? ` ${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable; inspect catalogErrors.`
|
|
955
|
+
: "") +
|
|
956
|
+
filterRecovery;
|
|
957
|
+
// A scope that resolved to nothing is the same silence one step earlier in
|
|
958
|
+
// the lookup: no connector resolved, so no catalog was even attempted, so
|
|
959
|
+
// no catalog failed and the unavailable path below never fires. Name
|
|
960
|
+
// only configured connectors in the current identity/pool registry view.
|
|
961
|
+
const unknownConnectorGuidance = args.connector && !scopedConnector
|
|
962
|
+
? `Connector "${args.connector}" is not configured in this deployment. Omit connector to search all configured tools. Configured connectors for this endpoint: ${this.registry
|
|
963
|
+
.listConnectors()
|
|
964
|
+
.map((connector) => connector.id)
|
|
965
|
+
.join(", ") || "none"}.`
|
|
966
|
+
: undefined;
|
|
967
|
+
// Searchable queries report analysis when the scorer had to degrade. A
|
|
968
|
+
// non-empty query with no searchable terms reports the bounded raw input
|
|
969
|
+
// as unmatched instead of silently becoming a browse. A real browse has
|
|
970
|
+
// no terms to analyse and normally reports none at all. Scope failures are
|
|
971
|
+
// the exception, because an empty result alone looks like a connector that
|
|
972
|
+
// correctly exposes no tools.
|
|
973
|
+
const reportsQueryAnalysis = absence !== undefined ||
|
|
974
|
+
unsearchableQuery ||
|
|
975
|
+
(queryTerms.length > 0
|
|
976
|
+
? matchMode === "partial"
|
|
977
|
+
: unknownConnectorGuidance !== undefined || unavailableCatalogs > 0);
|
|
978
|
+
const searchGuidance = () => {
|
|
979
|
+
if (unknownConnectorGuidance)
|
|
980
|
+
return unknownConnectorGuidance;
|
|
981
|
+
if (absence)
|
|
982
|
+
return absence.message;
|
|
983
|
+
if (unsearchableQuery) {
|
|
984
|
+
return scopedConnector && unavailableCatalogs > 0
|
|
985
|
+
? `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
986
|
+
: "The query contained no searchable lexical terms. Use 2–4 ASCII action/object terms, or browse with an empty query.";
|
|
987
|
+
}
|
|
988
|
+
if (queryTerms.length === 0) {
|
|
989
|
+
if (unavailableCatalogs === 0)
|
|
990
|
+
return undefined;
|
|
991
|
+
return scopedConnector
|
|
992
|
+
? `Connector "${scopedConnector.id}" could not be browsed because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
|
|
993
|
+
: `${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable, so this browse is incomplete. Inspect catalogErrors for the typed reasons and recovery actions.`;
|
|
994
|
+
}
|
|
995
|
+
if (matches.length === 0) {
|
|
996
|
+
if (scopedConnector) {
|
|
997
|
+
if (unavailableCatalogs > 0) {
|
|
998
|
+
return `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`;
|
|
999
|
+
}
|
|
1000
|
+
if (apiSearchGuidance)
|
|
1001
|
+
return apiSearchGuidance;
|
|
1002
|
+
return scopedGuide?.required
|
|
1003
|
+
? `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Fetch queryAnalysis.guide before calling, then refine terms or browse with an empty query.${filterRecovery}`
|
|
1004
|
+
: `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Refine terms or browse it with an empty query.${filterRecovery}`;
|
|
1078
1005
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1006
|
+
if (identityGuidance || apiSearchGuidance)
|
|
1007
|
+
return ([identityGuidance, apiSearchGuidance].filter(Boolean).join(" ") +
|
|
1008
|
+
(apiSearchGuidance ? "" : filterRecovery));
|
|
1009
|
+
return unavailableCatalogs === 0
|
|
1010
|
+
? `No matching ${safetyLabel}capability is configured in this deployment. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`
|
|
1011
|
+
: `No matching ${safetyLabel}capability was found in the catalogs that answered; ${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`;
|
|
1012
|
+
}
|
|
1013
|
+
if (matchMode !== "partial")
|
|
1014
|
+
return undefined;
|
|
1015
|
+
if (scopedConnector) {
|
|
1016
|
+
return `No single tool on connector "${scopedConnector.id}" matched every term. Split distinct intents into separate searches.`;
|
|
1017
|
+
}
|
|
1018
|
+
return unavailableCatalogs === 0
|
|
1019
|
+
? "No single tool matched every term. Split distinct intents into separate searches."
|
|
1020
|
+
: "No single tool matched every term in the catalogs that answered. Split distinct intents into separate searches.";
|
|
1021
|
+
};
|
|
1022
|
+
const guidance = searchGuidance();
|
|
1023
|
+
// Counts, ranking, and term analysis use the whole searched corpus, not
|
|
1024
|
+
// just this page's tools. Bind every searched connector, including failed
|
|
1025
|
+
// catalogs and off-page matches, so paging cannot reveal a hidden
|
|
1026
|
+
// connector's contribution. Keep provenance host-only.
|
|
1027
|
+
for (const connector of connectors)
|
|
1028
|
+
onCatalogReference?.(connector.id);
|
|
1029
|
+
for (const entry of entries)
|
|
1030
|
+
onCatalogReference?.(entry.connector.id, entry.tool.name);
|
|
1031
|
+
for (const pointer of apiSearchPointers)
|
|
1032
|
+
onCatalogReference?.(pointer.connector, pointer.tool);
|
|
1033
|
+
for (const connector of absence?.configuredConnectors ?? [])
|
|
1034
|
+
onCatalogReference?.(connector);
|
|
1035
|
+
// An unknown scope's guidance lists the visible roster without searching it.
|
|
1036
|
+
if (reportsQueryAnalysis && args.connector && !scopedConnector)
|
|
1037
|
+
for (const connector of visibleConnectors)
|
|
1038
|
+
onCatalogReference?.(connector.id);
|
|
1039
|
+
return {
|
|
1040
|
+
catalogErrors,
|
|
1041
|
+
...(absence ? { absence } : {}),
|
|
1042
|
+
entries,
|
|
1043
|
+
total: matches.length,
|
|
1044
|
+
offset,
|
|
1045
|
+
limit,
|
|
1046
|
+
hasMore: nextOffset !== undefined,
|
|
1047
|
+
...(nextOffset !== undefined ? { nextOffset } : {}),
|
|
1048
|
+
...(matchMode === "partial" && matches.length > 0 ? { matchMode } : {}),
|
|
1049
|
+
...(reportsQueryAnalysis
|
|
1084
1050
|
? {
|
|
1085
|
-
|
|
1051
|
+
queryAnalysis: {
|
|
1052
|
+
representedTerms,
|
|
1053
|
+
otherResultTerms,
|
|
1054
|
+
unmatchedTerms,
|
|
1055
|
+
...(queryMetadataTruncated ? { truncated: true } : {}),
|
|
1056
|
+
...(args.connector ? { connectorScope: args.connector } : {}),
|
|
1057
|
+
...(args.connector && !scopedConnector
|
|
1058
|
+
? {
|
|
1059
|
+
unknownConnector: true,
|
|
1060
|
+
configuredConnectors: this.registry.listConnectors().map((connector) => connector.id),
|
|
1061
|
+
}
|
|
1062
|
+
: {}),
|
|
1063
|
+
...(unavailableCatalogs > 0 ? { unavailableConnectorCount: unavailableCatalogs } : {}),
|
|
1064
|
+
...(scopedCatalogError ? { catalogError: scopedCatalogError } : {}),
|
|
1065
|
+
...(scopedGuide
|
|
1066
|
+
? {
|
|
1067
|
+
guide: scopedGuide.guide,
|
|
1068
|
+
...(scopedGuide.guideSummary ? { guideSummary: scopedGuide.guideSummary } : {}),
|
|
1069
|
+
...(scopedGuide.required
|
|
1070
|
+
? {
|
|
1071
|
+
guideRequired: true,
|
|
1072
|
+
guideRequiredReasons: ["connector_required"],
|
|
1073
|
+
}
|
|
1074
|
+
: {}),
|
|
1075
|
+
}
|
|
1076
|
+
: {}),
|
|
1077
|
+
...(guidance ? { guidance } : {}),
|
|
1078
|
+
},
|
|
1086
1079
|
}
|
|
1087
1080
|
: {}),
|
|
1088
|
-
...(output.source ? { outputSchemaSource: output.source } : {}),
|
|
1089
|
-
...(tool.annotations ? { annotations: tool.annotations } : {}),
|
|
1090
|
-
classification: tool.classification,
|
|
1091
1081
|
};
|
|
1092
1082
|
});
|
|
1093
1083
|
}
|
|
1084
|
+
describe(args) {
|
|
1085
|
+
return runEdge(this.describeEffect(args));
|
|
1086
|
+
}
|
|
1087
|
+
describeEffect(args) {
|
|
1088
|
+
return duringViewEffect({ requestScope: this.requestScope, signal: this.requestSignal }, () => this.describePage(args)).pipe(Effect.catchDefect((error) => Effect.fail(error)));
|
|
1089
|
+
}
|
|
1090
|
+
describePage(args) {
|
|
1091
|
+
return Effect.gen({ self: this }, function* () {
|
|
1092
|
+
const addresses = discoveryAddresses(args);
|
|
1093
|
+
const format = args.format ?? "compact";
|
|
1094
|
+
const resolved = addresses.map((rawAddress) => {
|
|
1095
|
+
const address = String(rawAddress);
|
|
1096
|
+
return { address, resolved: this.registry.resolveAddress(address) };
|
|
1097
|
+
});
|
|
1098
|
+
const connectorIds = [
|
|
1099
|
+
...new Set(resolved.map((entry) => entry.resolved?.connector.id).filter((id) => Boolean(id))),
|
|
1100
|
+
];
|
|
1101
|
+
const loaded = yield* this.discoveryCatalogs(connectorIds, "connecta.describe");
|
|
1102
|
+
const catalogs = new Map(connectorIds.map((id, index) => [id, loaded[index]]));
|
|
1103
|
+
const authFailures = new Map(yield* Effect.forEach(connectorIds, (id) => Effect.gen({ self: this }, function* () {
|
|
1104
|
+
const catalog = catalogs.get(id);
|
|
1105
|
+
if (!catalog || !Result.isFailure(catalog))
|
|
1106
|
+
return [id, undefined];
|
|
1107
|
+
const classified = classifyCallError(catalog.failure, "catalog_lookup_failed");
|
|
1108
|
+
const connector = this.registry.getConnector(id);
|
|
1109
|
+
const error = classified.code === "auth_required" || classified.code === "downstream_oauth_required"
|
|
1110
|
+
? yield* this.catalogFailure(connector, catalog.failure)
|
|
1111
|
+
: undefined;
|
|
1112
|
+
return [id, error];
|
|
1113
|
+
}), { concurrency: "unbounded" }));
|
|
1114
|
+
return resolved.map(({ address, resolved: addressResolution }) => {
|
|
1115
|
+
if (!addressResolution) {
|
|
1116
|
+
const message = `Unknown address "${boundedEchoText(address)}"`;
|
|
1117
|
+
return {
|
|
1118
|
+
address: boundedEchoText(address),
|
|
1119
|
+
error: message,
|
|
1120
|
+
errorDetails: {
|
|
1121
|
+
...framingError("unknown_address", message),
|
|
1122
|
+
configuredConnectors: this.registry.listConnectors().map((connector) => connector.id),
|
|
1123
|
+
nextAction: this.searchRecovery({ query: recoveryQuery(address) }, "Find the configured canonical address before retrying."),
|
|
1124
|
+
},
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
const catalog = catalogs.get(addressResolution.connector.id);
|
|
1128
|
+
if (catalog && Result.isFailure(catalog)) {
|
|
1129
|
+
const authFailure = authFailures.get(addressResolution.connector.id);
|
|
1130
|
+
if (authFailure)
|
|
1131
|
+
return { address: boundedEchoText(address), error: authFailure.message, errorDetails: authFailure };
|
|
1132
|
+
const classified = classifyCallError(catalog.failure, "catalog_lookup_failed");
|
|
1133
|
+
const message = boundedEchoText(classified.message);
|
|
1134
|
+
return {
|
|
1135
|
+
address: boundedEchoText(address),
|
|
1136
|
+
error: message,
|
|
1137
|
+
errorDetails: {
|
|
1138
|
+
code: classified.code,
|
|
1139
|
+
message,
|
|
1140
|
+
retryable: classified.retryable,
|
|
1141
|
+
...(classified.retryAfterMs === undefined ? {} : { retryAfterMs: classified.retryAfterMs }),
|
|
1142
|
+
},
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
const tools = catalog?.success ?? [];
|
|
1146
|
+
const tool = tools.find((item) => item.name === addressResolution.toolName);
|
|
1147
|
+
if (!tool) {
|
|
1148
|
+
const message = `Unknown tool "${boundedEchoText(addressResolution.toolName)}" on connector "${addressResolution.connector.id}"`;
|
|
1149
|
+
const suggestions = describeSuggestions(addressResolution.connector.id, addressResolution.toolName, tools);
|
|
1150
|
+
return {
|
|
1151
|
+
address: boundedEchoText(address),
|
|
1152
|
+
error: message,
|
|
1153
|
+
errorDetails: {
|
|
1154
|
+
...framingError("unknown_tool", message),
|
|
1155
|
+
nextAction: this.searchRecovery({
|
|
1156
|
+
query: recoveryQuery(addressResolution.toolName),
|
|
1157
|
+
connector: addressResolution.connector.id,
|
|
1158
|
+
}, "Find the connector's current canonical tool address."),
|
|
1159
|
+
...(suggestions.length > 0 ? { suggestions } : {}),
|
|
1160
|
+
},
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
const input = tool.inputSchema ?? { type: "object" };
|
|
1164
|
+
const output = this.outputSchema(addressResolution.connector.id, tool);
|
|
1165
|
+
const description = summarizeDescription(tool.description, args.fullDescriptions === true);
|
|
1166
|
+
const signature = format === "typescript"
|
|
1167
|
+
? typescriptSignature(input, output.schema, {
|
|
1168
|
+
observed: output.source === "observed",
|
|
1169
|
+
description: true,
|
|
1170
|
+
})
|
|
1171
|
+
: undefined;
|
|
1172
|
+
const compactInput = format === "compact" ? compactDescriptionSchema(input) : undefined;
|
|
1173
|
+
const compactOutput = format === "compact" && output.schema ? compactDescriptionSchema(output.schema) : undefined;
|
|
1174
|
+
const inputTruncated = compactInput?.truncated === true || signature?.inputTruncated === true;
|
|
1175
|
+
const outputTruncated = compactOutput?.truncated === true || signature?.outputTruncated === true;
|
|
1176
|
+
const requiredReasons = guideRequiredReasons(addressResolution.connector, tool, inputTruncated || outputTruncated);
|
|
1177
|
+
const guideSummary = connectorGuideSummary(addressResolution.connector);
|
|
1178
|
+
return {
|
|
1179
|
+
address,
|
|
1180
|
+
schemaFormat: format === "json" ? "json" : "text",
|
|
1181
|
+
name: tool.name,
|
|
1182
|
+
...(description !== undefined ? { description } : {}),
|
|
1183
|
+
...(connectorGuide(addressResolution.connector)
|
|
1184
|
+
? {
|
|
1185
|
+
guide: connectorSkillName(addressResolution.connector.id),
|
|
1186
|
+
...(guideSummary ? { guideSummary } : {}),
|
|
1187
|
+
}
|
|
1188
|
+
: {}),
|
|
1189
|
+
...(requiredReasons
|
|
1190
|
+
? {
|
|
1191
|
+
guideRequired: true,
|
|
1192
|
+
guideRequiredReasons: requiredReasons,
|
|
1193
|
+
}
|
|
1194
|
+
: {}),
|
|
1195
|
+
...(signature ? { signature: signature.text } : { inputSchema: compactInput?.text ?? input }),
|
|
1196
|
+
...(inputTruncated ? { inputSchemaTruncated: true } : {}),
|
|
1197
|
+
...(outputTruncated ? { outputSchemaTruncated: true } : {}),
|
|
1198
|
+
...(output.schema && !signature
|
|
1199
|
+
? {
|
|
1200
|
+
outputSchema: compactOutput?.text ?? output.schema,
|
|
1201
|
+
}
|
|
1202
|
+
: {}),
|
|
1203
|
+
...(output.source ? { outputSchemaSource: output.source } : {}),
|
|
1204
|
+
...(tool.annotations ? { annotations: tool.annotations } : {}),
|
|
1205
|
+
classification: tool.classification,
|
|
1206
|
+
};
|
|
1207
|
+
});
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
static nativePrograms = /* @__PURE__ */ (() => {
|
|
1211
|
+
for (const [key, native] of [
|
|
1212
|
+
["search", "searchEffect"],
|
|
1213
|
+
["describe", "describeEffect"],
|
|
1214
|
+
["listCatalogs", "listCatalogsEffect"],
|
|
1215
|
+
["loadConnector", "loadConnectorEffect"],
|
|
1216
|
+
["resolveTool", "resolve"],
|
|
1217
|
+
]) {
|
|
1218
|
+
registerAuthMethod(CatalogService.prototype, key, function (...args) {
|
|
1219
|
+
return Reflect.apply(this[native], this, args);
|
|
1220
|
+
}, CatalogService.prototype[key]);
|
|
1221
|
+
}
|
|
1222
|
+
})();
|
|
1094
1223
|
}
|
|
1095
1224
|
function pageTail(page) {
|
|
1096
1225
|
return {
|