@zackbart/connecta 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (428) hide show
  1. package/CHANGELOG.md +393 -0
  2. package/README.md +62 -105
  3. package/SECURITY.md +3 -2
  4. package/bin/connecta.mjs +41 -16
  5. package/bin/version.mjs +1 -1
  6. package/dist/access-tokens.d.ts +2 -1
  7. package/dist/access-tokens.js +66 -30
  8. package/dist/activity.d.ts +1 -1
  9. package/dist/activity.js +2 -0
  10. package/dist/agent-config.d.ts +23 -0
  11. package/dist/agent-config.js +59 -0
  12. package/dist/agent-description.js +16 -0
  13. package/dist/agent-identity.js +4 -4
  14. package/dist/agent-observation.js +39 -0
  15. package/dist/auth/clerk-sdk/client.d.ts +2 -0
  16. package/dist/auth/clerk-sdk/generated.js +554 -563
  17. package/dist/auth/clerk-transport.js +70 -3
  18. package/dist/auth/clerk.d.ts +13 -0
  19. package/dist/auth/clerk.js +653 -246
  20. package/dist/auth/cloudflare-access.js +46 -42
  21. package/dist/auth/downstream-client-metadata.js +2 -2
  22. package/dist/auth/downstream-oauth.js +2051 -1160
  23. package/dist/auth/downstream-revocation.js +88 -88
  24. package/dist/auth/native-call.js +20 -0
  25. package/dist/auth/native.js +184 -0
  26. package/dist/auth/oauth-start-reset.js +11 -5
  27. package/dist/auth/oauth-token-response.js +121 -102
  28. package/dist/auth/oauth-v2-migration.js +97 -78
  29. package/dist/auth/renewable-grant.js +10 -0
  30. package/dist/auth/static-oauth.js +663 -271
  31. package/dist/auth/vercel-agent.js +266 -0
  32. package/dist/auth-admission.js +204 -0
  33. package/dist/auth-elicitation.js +350 -241
  34. package/dist/authorization-handoff.js +164 -90
  35. package/dist/call-admission-context.js +3 -10
  36. package/dist/call-admission.js +2 -0
  37. package/dist/call-classification.js +9 -0
  38. package/dist/call-recovery.js +32 -13
  39. package/dist/canonical-json.js +8 -0
  40. package/dist/catalog-cache.js +369 -213
  41. package/dist/catalog-service.js +659 -530
  42. package/dist/catalog.js +5 -1
  43. package/dist/concurrency.js +0 -25
  44. package/dist/config-defaults.d.ts +1 -0
  45. package/dist/config-defaults.js +1 -0
  46. package/dist/config-schema.d.ts +3 -1
  47. package/dist/config-schema.js +13 -3
  48. package/dist/config.d.ts +40 -6
  49. package/dist/config.js +40 -26
  50. package/dist/connector-caller.d.ts +4 -0
  51. package/dist/connector-caller.js +17 -4
  52. package/dist/connectors/api-connector.d.ts +15 -7
  53. package/dist/connectors/api-connector.js +147 -57
  54. package/dist/connectors/guarded-fetch.d.ts +51 -17
  55. package/dist/connectors/guarded-fetch.js +215 -121
  56. package/dist/connectors/negotiation-cache.js +20 -10
  57. package/dist/connectors/option-shapes.js +1 -1
  58. package/dist/connectors/remote-mcp.d.ts +12 -30
  59. package/dist/connectors/remote-mcp.js +960 -564
  60. package/dist/d1.js +10 -243
  61. package/dist/derived-context.js +5 -0
  62. package/dist/describe-config.d.ts +5 -1
  63. package/dist/describe-config.js +3 -0
  64. package/dist/downstream-input.js +347 -260
  65. package/dist/errors.d.ts +87 -6
  66. package/dist/errors.js +114 -4
  67. package/dist/execute.js +87 -50
  68. package/dist/executor-admission.js +3 -0
  69. package/dist/executors/quickjs.js +163 -112
  70. package/dist/guarded-transport.js +50 -0
  71. package/dist/guest-runtime.js +1 -1
  72. package/dist/http/client.js +439 -0
  73. package/dist/http/errors.js +558 -0
  74. package/dist/http/fields.js +49 -0
  75. package/dist/http/hatch-paging.js +318 -0
  76. package/dist/http/hatch.js +1279 -0
  77. package/dist/http/json-schema.js +267 -0
  78. package/dist/http/node-transport.js +241 -0
  79. package/dist/http/oauth.js +162 -0
  80. package/dist/http/paging.js +228 -0
  81. package/dist/http/provider.js +221 -0
  82. package/dist/http/safety.js +82 -0
  83. package/dist/http/services.js +79 -0
  84. package/dist/http/tools.js +383 -0
  85. package/dist/http/vendor-index.js +138 -0
  86. package/dist/http/vendor.js +448 -0
  87. package/dist/http/write-request.js +66 -0
  88. package/dist/http-policy.d.ts +11 -0
  89. package/dist/http-policy.js +45 -0
  90. package/dist/inbound-credential.js +1 -1
  91. package/dist/index.d.ts +2 -2
  92. package/dist/index.js +4 -0
  93. package/dist/invocation-auth.js +30 -19
  94. package/dist/invocation.js +221 -87
  95. package/dist/mcp-result.js +0 -4
  96. package/dist/membership.d.ts +4 -0
  97. package/dist/meta-output.js +1 -1
  98. package/dist/meta-tools.js +534 -408
  99. package/dist/native-recovery.js +52 -0
  100. package/dist/native-tools.js +382 -0
  101. package/dist/node.d.ts +4 -3
  102. package/dist/node.js +45 -8
  103. package/dist/oauth-handoff.js +177 -104
  104. package/dist/oauth-partition.js +6 -20
  105. package/dist/oauth-sealing.js +6 -7
  106. package/dist/operation-vocabulary.js +101 -0
  107. package/dist/operator-record.d.ts +2 -2
  108. package/dist/operator-record.js +17 -4
  109. package/dist/operator-ui/agents.d.ts +5 -4
  110. package/dist/operator-ui/agents.js +17 -7
  111. package/dist/operator-ui/config-summary.js +8 -4
  112. package/dist/operator-ui/connection-map.js +9 -6
  113. package/dist/operator-ui/contract.d.ts +7 -3
  114. package/dist/operator-ui/endpoints.js +5 -14
  115. package/dist/operator-ui/fix-prompts.d.ts +1 -1
  116. package/dist/operator-ui/fix-prompts.js +0 -8
  117. package/dist/operator-ui/generated.js +3 -3
  118. package/dist/operator-ui/health.js +52 -0
  119. package/dist/operator-ui/model.d.ts +2 -3
  120. package/dist/operator-ui/overview.js +11 -19
  121. package/dist/operator-ui/setup-commands.js +0 -4
  122. package/dist/operator-ui/view.d.ts +5 -29
  123. package/dist/operator-ui/view.js +3 -59
  124. package/dist/program-writes.js +47 -26
  125. package/dist/provider-call-facts.d.ts +115 -0
  126. package/dist/provider-call-facts.js +182 -0
  127. package/dist/provider-call.d.ts +112 -0
  128. package/dist/provider-call.js +271 -0
  129. package/dist/provider.d.ts +71 -32
  130. package/dist/provider.js +134 -40
  131. package/dist/providers/_shared/google/hatch.js +91 -0
  132. package/dist/providers/_shared/google/kit.js +537 -0
  133. package/dist/providers/_shared/google/result-size.js +15 -2
  134. package/dist/providers/_shared/google/service-account.d.ts +2 -1
  135. package/dist/providers/_shared/google/service-account.js +8 -4
  136. package/dist/providers/_shared/google/workspace.d.ts +8 -209
  137. package/dist/providers/_shared/google/workspace.js +7 -426
  138. package/dist/providers/_shared/rest/compact-index.js +86 -0
  139. package/dist/providers/_shared/rest/dispatch.js +3 -23
  140. package/dist/providers/_shared/rest/kit.js +546 -0
  141. package/dist/providers/_shared/rest/operation-index.js +230 -14
  142. package/dist/providers/_shared/rest/response-media.js +11 -0
  143. package/dist/providers/_shared/rest/tools.js +101 -78
  144. package/dist/providers/_shared/rest/value-safety.js +353 -195
  145. package/dist/providers/_shared/sensitive-fields.js +27 -0
  146. package/dist/providers/breeze/api.js +584 -0
  147. package/dist/providers/breeze/index.d.ts +3 -3
  148. package/dist/providers/breeze/index.js +67 -1420
  149. package/dist/providers/breeze/openapi.generated.js +60 -0
  150. package/dist/providers/breeze/skill.generated.js +2 -6
  151. package/dist/providers/breeze/tools.js +696 -0
  152. package/dist/providers/ccb/api.js +496 -0
  153. package/dist/providers/ccb/index.d.ts +17 -37
  154. package/dist/providers/ccb/index.js +77 -1702
  155. package/dist/providers/ccb/lib/constants.d.ts +34 -0
  156. package/dist/providers/ccb/lib/constants.js +74 -0
  157. package/dist/providers/ccb/lib/guide.js +9 -0
  158. package/dist/providers/ccb/lib/verdicts.js +387 -0
  159. package/dist/providers/ccb/openapi.generated.js +397 -0
  160. package/dist/providers/ccb/safety.js +295 -0
  161. package/dist/providers/ccb/skill.generated.js +2 -6
  162. package/dist/providers/ccb/tools.js +853 -0
  163. package/dist/providers/cloudflare/api.js +1066 -0
  164. package/dist/providers/cloudflare/index.d.ts +7 -22
  165. package/dist/providers/cloudflare/index.js +80 -194
  166. package/dist/providers/cloudflare/lib/config.d.ts +16 -0
  167. package/dist/providers/cloudflare/lib/config.js +2 -0
  168. package/dist/providers/cloudflare/lib/discovery.js +29 -0
  169. package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
  170. package/dist/providers/cloudflare/lib/hatch.js +103 -0
  171. package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
  172. package/dist/providers/cloudflare/lib/response-media.js +75 -0
  173. package/dist/providers/cloudflare/openapi.generated.js +3299 -3208
  174. package/dist/providers/cloudflare/{value-safety.js → safety.js} +538 -18
  175. package/dist/providers/cloudflare/skill.generated.js +2 -10
  176. package/dist/providers/cloudflare/tools.js +360 -0
  177. package/dist/providers/docs/api.js +51 -0
  178. package/dist/providers/docs/index.d.ts +1 -12
  179. package/dist/providers/docs/index.js +53 -1165
  180. package/dist/providers/docs/lib/constants.d.ts +18 -0
  181. package/dist/providers/docs/lib/constants.js +18 -0
  182. package/dist/providers/docs/lib/discovery.js +26 -0
  183. package/dist/providers/docs/lib/requests.js +48 -0
  184. package/dist/providers/docs/lib/schemas.js +168 -0
  185. package/dist/providers/docs/openapi.generated.js +16 -0
  186. package/dist/providers/docs/safety.js +54 -0
  187. package/dist/providers/docs/skill.generated.js +2 -6
  188. package/dist/providers/docs/tools.js +802 -0
  189. package/dist/providers/drive/api.js +177 -0
  190. package/dist/providers/drive/index.d.ts +1 -12
  191. package/dist/providers/drive/index.js +38 -1448
  192. package/dist/providers/drive/lib/constants.d.ts +6 -0
  193. package/dist/providers/drive/lib/constants.js +6 -0
  194. package/dist/providers/drive/lib/contracts.js +532 -0
  195. package/dist/providers/drive/lib/hatch.js +197 -0
  196. package/dist/providers/drive/openapi.generated.js +77 -0
  197. package/dist/providers/drive/safety.js +90 -0
  198. package/dist/providers/drive/skill.generated.js +2 -6
  199. package/dist/providers/drive/tools.js +1290 -0
  200. package/dist/providers/forms/api.js +71 -0
  201. package/dist/providers/forms/index.d.ts +1 -12
  202. package/dist/providers/forms/index.js +49 -1145
  203. package/dist/providers/forms/lib/constants.d.ts +14 -0
  204. package/dist/providers/forms/lib/constants.js +19 -0
  205. package/dist/providers/forms/lib/hatch.js +72 -0
  206. package/dist/providers/forms/openapi.generated.js +23 -0
  207. package/dist/providers/forms/safety.js +34 -0
  208. package/dist/providers/forms/skill.generated.js +2 -8
  209. package/dist/providers/forms/tools.js +1024 -0
  210. package/dist/providers/github/api.js +150 -0
  211. package/dist/providers/github/index.d.ts +4 -5
  212. package/dist/providers/github/index.js +30 -87
  213. package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
  214. package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
  215. package/dist/providers/github/lib/discovery.js +132 -0
  216. package/dist/providers/github/lib/operations.js +58 -0
  217. package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
  218. package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
  219. package/dist/providers/github/openapi.generated.js +1216 -0
  220. package/dist/providers/github/safety.js +216 -0
  221. package/dist/providers/github/skill.generated.js +2 -5
  222. package/dist/providers/github/tools.js +506 -0
  223. package/dist/providers/gmail/api.js +114 -0
  224. package/dist/providers/gmail/index.d.ts +1 -12
  225. package/dist/providers/gmail/index.js +73 -1621
  226. package/dist/providers/gmail/lib/constants.d.ts +13 -0
  227. package/dist/providers/gmail/lib/constants.js +16 -0
  228. package/dist/providers/gmail/lib/index.js +173 -0
  229. package/dist/providers/gmail/lib/schemas.js +176 -0
  230. package/dist/providers/gmail/lib/threading.js +120 -0
  231. package/dist/providers/gmail/openapi.generated.js +92 -0
  232. package/dist/providers/gmail/safety.js +21 -0
  233. package/dist/providers/gmail/skill.generated.js +2 -6
  234. package/dist/providers/gmail/tools.js +1435 -0
  235. package/dist/providers/overflow/api.js +1008 -0
  236. package/dist/providers/overflow/index.d.ts +3 -17
  237. package/dist/providers/overflow/index.js +60 -1488
  238. package/dist/providers/overflow/skill.generated.js +2 -5
  239. package/dist/providers/overflow/tools.js +873 -0
  240. package/dist/providers/planning-center/api.js +467 -0
  241. package/dist/providers/planning-center/index.d.ts +6 -35
  242. package/dist/providers/planning-center/index.js +69 -2445
  243. package/dist/providers/planning-center/lib/constants.d.ts +58 -0
  244. package/dist/providers/planning-center/lib/constants.js +83 -0
  245. package/dist/providers/planning-center/lib/discovery.js +40 -0
  246. package/dist/providers/planning-center/lib/guide.js +39 -0
  247. package/dist/providers/planning-center/lib/json-api.js +83 -0
  248. package/dist/providers/planning-center/lib/verdicts.js +1758 -0
  249. package/dist/providers/planning-center/openapi.generated.js +1764 -0
  250. package/dist/providers/planning-center/safety.js +188 -0
  251. package/dist/providers/planning-center/skill.generated.js +2 -27
  252. package/dist/providers/planning-center/tools.js +1010 -0
  253. package/dist/providers/sheets/api.js +133 -0
  254. package/dist/providers/sheets/index.d.ts +4 -16
  255. package/dist/providers/sheets/index.js +61 -1172
  256. package/dist/providers/sheets/lib/constants.d.ts +14 -0
  257. package/dist/providers/sheets/lib/constants.js +14 -0
  258. package/dist/providers/sheets/lib/hatch.js +163 -0
  259. package/dist/providers/sheets/lib/requests.js +105 -0
  260. package/dist/providers/sheets/lib/schemas.js +138 -0
  261. package/dist/providers/sheets/openapi.generated.js +30 -0
  262. package/dist/providers/sheets/safety.js +27 -0
  263. package/dist/providers/sheets/skill.generated.js +2 -7
  264. package/dist/providers/sheets/tools.js +734 -0
  265. package/dist/providers/slides/api.js +147 -0
  266. package/dist/providers/slides/index.d.ts +2 -10
  267. package/dist/providers/slides/index.js +48 -2555
  268. package/dist/providers/slides/lib/constants.d.ts +11 -0
  269. package/dist/providers/slides/lib/constants.js +11 -0
  270. package/dist/providers/slides/lib/requests.js +60 -0
  271. package/dist/providers/slides/lib/schemas.js +565 -0
  272. package/dist/providers/slides/openapi.generated.js +18 -0
  273. package/dist/providers/slides/skill.generated.js +2 -6
  274. package/dist/providers/slides/tools.js +1968 -0
  275. package/dist/providers/stripe/api.js +401 -0
  276. package/dist/providers/stripe/index.d.ts +7 -27
  277. package/dist/providers/stripe/index.js +75 -191
  278. package/dist/providers/stripe/lib/config.d.ts +4 -0
  279. package/dist/providers/stripe/lib/config.js +2 -0
  280. package/dist/providers/stripe/openapi.generated.js +4 -3
  281. package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
  282. package/dist/providers/stripe/skill.generated.js +2 -10
  283. package/dist/providers/stripe/tools.js +77 -0
  284. package/dist/providers/tithely/api.js +531 -0
  285. package/dist/providers/tithely/index.d.ts +19 -0
  286. package/dist/providers/tithely/index.js +86 -965
  287. package/dist/providers/tithely/skill.generated.js +2 -5
  288. package/dist/providers/tithely/tools.js +426 -0
  289. package/dist/providers/vercel/api.js +551 -0
  290. package/dist/providers/vercel/index.d.ts +11 -25
  291. package/dist/providers/vercel/index.js +67 -261
  292. package/dist/providers/vercel/lib/config.d.ts +2 -0
  293. package/dist/providers/vercel/lib/config.js +2 -0
  294. package/dist/providers/vercel/lib/index.js +117 -0
  295. package/dist/providers/vercel/lib/logs.js +100 -0
  296. package/dist/providers/vercel/lib/pin.js +270 -0
  297. package/dist/providers/vercel/openapi.generated.js +398 -376
  298. package/dist/providers/vercel/{value-safety.js → safety.js} +261 -42
  299. package/dist/providers/vercel/skill.generated.js +2 -7
  300. package/dist/providers/vercel/tools.js +642 -0
  301. package/dist/redaction-identity.js +47 -0
  302. package/dist/redaction-marker.js +125 -0
  303. package/dist/registry.d.ts +30 -28
  304. package/dist/registry.js +446 -413
  305. package/dist/routes/access-tokens.js +1 -1
  306. package/dist/routes/actor-labels.js +1 -1
  307. package/dist/routes/connect.js +1 -1
  308. package/dist/routes/dispatch.js +103 -0
  309. package/dist/routes/mcp.js +170 -79
  310. package/dist/routes/oauth.js +3 -1
  311. package/dist/routes/operator.js +2 -2
  312. package/dist/routes/shared.d.ts +5 -4
  313. package/dist/routes/shared.js +244 -177
  314. package/dist/routes/skills.js +10 -8
  315. package/dist/routes/ui-config.js +27 -3
  316. package/dist/routes/ui-data.js +29 -22
  317. package/dist/routes/ui.js +1 -1
  318. package/dist/runtime/call-admission.js +0 -26
  319. package/dist/runtime/call-entry.js +30 -0
  320. package/dist/runtime/call.js +556 -0
  321. package/dist/runtime/connector-scope.js +7 -0
  322. package/dist/runtime/credential-probe.js +34 -0
  323. package/dist/runtime/executor.js +40 -0
  324. package/dist/runtime/native-storage.js +63 -0
  325. package/dist/runtime/registry.js +27 -0
  326. package/dist/runtime/request-scope.js +72 -0
  327. package/dist/runtime/run.js +15 -7
  328. package/dist/runtime/shared-read.js +34 -5
  329. package/dist/runtime/storage.js +34 -21
  330. package/dist/sent-secrets.d.ts +194 -37
  331. package/dist/sent-secrets.js +1743 -340
  332. package/dist/server.js +5 -56
  333. package/dist/skills.js +330 -224
  334. package/dist/sqlite.js +15 -14
  335. package/dist/storage/d1-copy.js +255 -0
  336. package/dist/storage/d1-driver.js +15 -0
  337. package/dist/storage/keys.js +28 -0
  338. package/dist/storage/sql-driver.js +88 -0
  339. package/dist/storage/sql-native.js +544 -0
  340. package/dist/storage/sql-schema.d.ts +6 -0
  341. package/dist/storage/sql-schema.js +15 -0
  342. package/dist/storage/sql.d.ts +14 -20
  343. package/dist/storage/sql.js +29 -535
  344. package/dist/timeout.js +0 -26
  345. package/dist/tool-safety.js +20 -10
  346. package/dist/types.d.ts +11 -99
  347. package/dist/upward.js +3 -0
  348. package/dist/usage-guide.js +3 -1
  349. package/dist/validate.d.ts +4 -2
  350. package/dist/validate.js +86 -9
  351. package/dist/version.d.ts +1 -1
  352. package/dist/version.js +1 -1
  353. package/dist/worker.js +312 -292
  354. package/dist/workspace-subject.js +5 -1
  355. package/dist/write-outcome.js +454 -34
  356. package/documentation/README.md +27 -7
  357. package/documentation/architecture.md +2550 -186
  358. package/documentation/auth.md +821 -98
  359. package/documentation/code-mode.md +56 -21
  360. package/documentation/deploying.md +164 -20
  361. package/documentation/hosted-mcp-migration.md +460 -0
  362. package/documentation/integrating.md +144 -105
  363. package/documentation/meta-tools.md +411 -34
  364. package/documentation/operating.md +28 -4
  365. package/documentation/operator-tests.md +11 -2
  366. package/documentation/operator-ui.md +103 -33
  367. package/documentation/provider-auth-migration.md +38 -63
  368. package/documentation/provider-migration-0.29.md +10 -5
  369. package/documentation/provider-migration-0.35.md +975 -0
  370. package/documentation/providers.md +965 -0
  371. package/examples/worker/AGENTS.md +23 -9
  372. package/examples/worker/README.md +254 -135
  373. package/examples/worker/alchemy.run.ts +116 -0
  374. package/examples/worker/gitignore +10 -0
  375. package/examples/worker/src/connecta.config.ts +23 -21
  376. package/examples/worker/wrangler.jsonc +14 -5
  377. package/guides/connecting-agents.md +157 -0
  378. package/guides/how-it-works.md +156 -0
  379. package/guides/safety-and-trust.md +153 -0
  380. package/package.json +27 -42
  381. package/templates/node/.env.example +3 -0
  382. package/templates/node/README.md +22 -2
  383. package/templates/node/docker-compose.yml +1 -0
  384. package/templates/node/package.json +3 -2
  385. package/templates/node/src/connecta.config.ts +8 -0
  386. package/dist/catalog-drift.d.ts +0 -134
  387. package/dist/catalog-drift.js +0 -493
  388. package/dist/providers/_shared/request-token.js +0 -83
  389. package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
  390. package/dist/providers/_shared/rest/tools.d.ts +0 -141
  391. package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
  392. package/dist/providers/basecamp/index.d.ts +0 -43
  393. package/dist/providers/basecamp/index.js +0 -997
  394. package/dist/providers/basecamp/skill.generated.js +0 -9
  395. package/dist/providers/cloudflare/rest.d.ts +0 -37
  396. package/dist/providers/cloudflare/rest.js +0 -1078
  397. package/dist/providers/github/hosted.js +0 -218
  398. package/dist/providers/github/rest.js +0 -321
  399. package/dist/providers/infisical/index.d.ts +0 -7
  400. package/dist/providers/infisical/index.js +0 -713
  401. package/dist/providers/infisical/skill.generated.js +0 -8
  402. package/dist/providers/linear/index.d.ts +0 -55
  403. package/dist/providers/linear/index.js +0 -352
  404. package/dist/providers/linear/skill.generated.js +0 -8
  405. package/dist/providers/mixpanel/index.d.ts +0 -57
  406. package/dist/providers/mixpanel/index.js +0 -468
  407. package/dist/providers/mixpanel/operations.js +0 -20
  408. package/dist/providers/mixpanel/rest.js +0 -603
  409. package/dist/providers/mixpanel/skill.generated.js +0 -10
  410. package/dist/providers/mixpanel/value-safety.js +0 -16
  411. package/dist/providers/notion/index.d.ts +0 -65
  412. package/dist/providers/notion/index.js +0 -368
  413. package/dist/providers/notion/openapi.generated.js +0 -75
  414. package/dist/providers/notion/rest.d.ts +0 -24
  415. package/dist/providers/notion/rest.js +0 -1226
  416. package/dist/providers/notion/skill.generated.js +0 -11
  417. package/dist/providers/notion/value-safety.js +0 -56
  418. package/dist/providers/revenuecat/index.d.ts +0 -49
  419. package/dist/providers/revenuecat/index.js +0 -557
  420. package/dist/providers/revenuecat/skill.generated.js +0 -11
  421. package/dist/providers/stripe/rest.d.ts +0 -14
  422. package/dist/providers/stripe/rest.js +0 -402
  423. package/dist/providers/vercel/rest.d.ts +0 -20
  424. package/dist/providers/vercel/rest.js +0 -1020
  425. package/dist/providers/wispr-flow/index.d.ts +0 -14
  426. package/dist/providers/wispr-flow/index.js +0 -117
  427. package/dist/providers/wispr-flow/skill.generated.js +0 -8
  428. /package/dist/providers/github/{key.js → lib/key.js} +0 -0
@@ -1,5 +1,7 @@
1
- import { withAuthorizationHandoff } from "./authorization-handoff.js";
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.catalog(id, callOptions));
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, structuredClone(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
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
- async catalogFailure(connector, failure) {
548
- const classified = classifyCallError(failure, "catalog_lookup_failed");
549
- const error = {
550
- connector: connector.id,
551
- code: classified.code,
552
- // Catalog discovery reports recovery facts, never connector-authored
553
- // error text. Clipping a downstream message does not make it safe.
554
- message: classified.code === "downstream_oauth_required"
555
- ? `Connector "${connector.id}" requires downstream OAuth authorization. When an auth error includes a handoff, give it to the user or operator.`
556
- : classified.code === "auth_required"
557
- ? `Connector "${connector.id}" requires operator-managed credentials or configuration. When an auth error includes a handoff, give it to the user or operator.`
558
- : classified.code === "provider_permission_denied"
559
- ? `Connector "${connector.id}" requires permission from the provider's resource owner or administrator. Reconnecting alone will not grant access.`
560
- : `Connector "${connector.id}" catalog lookup failed (${classified.code}).`,
561
- retryable: classified.retryable,
562
- ...(classified.retryAfterMs === undefined ? {} : { retryAfterMs: classified.retryAfterMs }),
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.retry = `Retry discovery after the operator completes recovery for "${connector.id}".`;
578
- }
579
- else if (error.code === "provider_permission_denied") {
580
- error.retry =
581
- "Ask the provider's resource owner or administrator to grant the required permission or scope, then retry. Reconnecting alone will not grant access.";
582
- }
583
- return withAuthorizationHandoff(this.requestScope, error);
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
- async search(args, onCatalogReference) {
586
- if (args.query !== undefined && typeof args.query !== "string") {
587
- throw new DiscoveryPolicyError("invalid_args", "query must be a string. Omit it or use an empty string to browse the catalog.");
588
- }
589
- if (args.connector !== undefined && boundedEchoText(args.connector) !== args.connector) {
590
- // The scope is echoed back as `queryAnalysis.connectorScope`; a clipped
591
- // copy could name a different connector, so refuse instead of clamping.
592
- throw new DiscoveryPolicyError("invalid_args", "connector must be at most 512 UTF-8 bytes.");
593
- }
594
- const query = args.query ?? "";
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
- : undefined;
619
- const connectors = absence ? [] : args.connector ? (scopedConnector ? [scopedConnector] : []) : visibleConnectors;
620
- // Timeout messages name the route the caller used.
621
- const catalogs = await runEdge(this.discoveryCatalogs(connectors.map((connector) => connector.id), this.searchRoute));
622
- const searchableCatalogs = catalogs.map((catalog) => Result.map(catalog, (tools) => toolsForSafety(tools, safety)));
623
- const matches = [];
624
- let matchMode = "all";
625
- const statistics = lexicalCorpusStatistics(searchableCatalogs.flatMap((catalog) => (Result.isSuccess(catalog) ? [catalog.success] : [])), retrievalQuery);
626
- const trimmedQuery = query.trim();
627
- const isBrowse = trimmedQuery.length === 0;
628
- const queryTerms = lexicalQueryTerms(retrievalQuery);
629
- const rawPhrase = lexicalQueryTerms(query).join(" ");
630
- const queryTermCount = queryTerms.length;
631
- const unsearchableQuery = !isBrowse && queryTermCount === 0;
632
- const analysisTerms = unsearchableQuery ? [trimmedQuery] : queryTerms;
633
- const analyzedTerms = analysisTerms.slice(0, MAX_QUERY_TERMS);
634
- const displayTerm = (term) => boundedQueryTerm(term).text;
635
- const queryMetadataTruncated = analysisTerms.length > analyzedTerms.length || analyzedTerms.some((term) => boundedQueryTerm(term).truncated);
636
- const collectMatches = (mode) => {
637
- const collected = [];
638
- let orderBase = 0;
639
- searchableCatalogs.forEach((catalog, connectorIndex) => {
640
- const connector = connectors[connectorIndex];
641
- if (!connector) {
642
- throw new Error("Catalog result has no corresponding connector");
643
- }
644
- if (Result.isSuccess(catalog)) {
645
- const connectorRank = args.connector ? 0 : connectorMatchRank(query, connector);
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 schemaFormat = withSchemas === "typescript" ? undefined : withSchemas;
727
- const renderedInput = signature
728
- ? { schema: undefined, truncated: signature.inputTruncated }
729
- : schemaFormat
730
- ? renderSearchSchema(input, schemaFormat)
731
- : undefined;
732
- const renderedOutput = signature
733
- ? { schema: undefined, truncated: signature.outputTruncated }
734
- : schemaFormat && output.schema
735
- ? renderSearchSchema(output.schema, schemaFormat)
736
- : undefined;
737
- const schemaKeys = withSchemas && args.includeSchemaKeys ? schemaKeyMetadata(input, output.schema) : undefined;
738
- const description = summarizeDiscoveryDescription(match.tool.description, args.fullDescriptions === true);
739
- const requiredReasons = guideRequiredReasons(match.connector, match.tool, renderedInput?.truncated === true || renderedOutput?.truncated === true);
740
- const guideSummary = connectorGuideSummary(match.connector);
741
- return {
742
- connector: match.connector,
743
- ...(connectorGuide(match.connector)
744
- ? {
745
- guide: connectorSkillName(match.connector.id),
746
- ...(guideSummary ? { guideSummary } : {}),
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
- tool: {
750
- name: match.tool.name,
751
- address: `${match.connector.id}.${match.tool.name}`,
752
- ...(description !== undefined ? { description } : {}),
753
- ...(schemaFormat
754
- ? {
755
- inputSchema: renderedInput?.schema,
756
- }
757
- : {}),
758
- ...(withSchemas ? { schemaFormat: withSchemas === "json" ? "json" : "text" } : {}),
759
- ...(signature ? { signature: signature.text } : {}),
760
- ...(renderedInput?.truncated ? { inputSchemaTruncated: true } : {}),
761
- ...(schemaFormat && output.schema
762
- ? {
763
- outputSchema: renderedOutput?.schema,
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
- ...(withSchemas && output.source ? { outputSchemaSource: output.source } : {}),
767
- ...(renderedOutput?.truncated ? { outputSchemaTruncated: true } : {}),
768
- ...(schemaKeys && !renderedInput?.truncated
769
- ? {
770
- ...(schemaKeys.inputKeys ? { inputKeys: schemaKeys.inputKeys } : {}),
771
- ...(schemaKeys.requiredInputKeys ? { requiredInputKeys: schemaKeys.requiredInputKeys } : {}),
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
- ...(schemaKeys?.outputKeys && !renderedOutput?.truncated ? { outputKeys: schemaKeys.outputKeys } : {}),
775
- ...(match.tool.annotations ? { annotations: match.tool.annotations } : {}),
776
- classification: match.tool.classification,
777
- ...(requiredReasons
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
- guideRequired: true,
780
- guideRequiredReasons: requiredReasons,
815
+ guide: connectorSkillName(match.connector.id),
816
+ ...(guideSummary ? { guideSummary } : {}),
781
817
  }
782
818
  : {}),
783
- },
784
- };
785
- });
786
- const nextOffset = offset + entries.length < matches.length ? offset + entries.length : undefined;
787
- const pageTools = new Set(pageMatches.map((match) => match.tool));
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
- unknownConnector: true,
953
- configuredConnectors: this.registry.listConnectors().map((connector) => connector.id),
825
+ inputSchema: renderedInput?.schema,
954
826
  }
955
827
  : {}),
956
- ...(unavailableCatalogs > 0 ? { unavailableConnectorCount: unavailableCatalogs } : {}),
957
- ...(scopedCatalogError ? { catalogError: scopedCatalogError } : {}),
958
- ...(scopedGuide
828
+ ...(withSchemas ? { schemaFormat: withSchemas === "json" ? "json" : "text" } : {}),
829
+ ...(signature ? { signature: signature.text } : {}),
830
+ ...(renderedInput?.truncated ? { inputSchemaTruncated: true } : {}),
831
+ ...(schemaFormat && output.schema
959
832
  ? {
960
- guide: scopedGuide.guide,
961
- ...(scopedGuide.guideSummary ? { guideSummary: scopedGuide.guideSummary } : {}),
962
- ...(scopedGuide.required
963
- ? {
964
- guideRequired: true,
965
- guideRequiredReasons: ["connector_required"],
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 input = tool.inputSchema ?? { type: "object" };
1049
- const output = this.outputSchema(addressResolution.connector.id, tool);
1050
- const description = summarizeDescription(tool.description, args.fullDescriptions === true);
1051
- const signature = format === "typescript"
1052
- ? typescriptSignature(input, output.schema, {
1053
- observed: output.source === "observed",
1054
- description: true,
1055
- })
1056
- : undefined;
1057
- const compactInput = format === "compact" ? compactDescriptionSchema(input) : undefined;
1058
- const compactOutput = format === "compact" && output.schema ? compactDescriptionSchema(output.schema) : undefined;
1059
- const inputTruncated = compactInput?.truncated === true || signature?.inputTruncated === true;
1060
- const outputTruncated = compactOutput?.truncated === true || signature?.outputTruncated === true;
1061
- const requiredReasons = guideRequiredReasons(addressResolution.connector, tool, inputTruncated || outputTruncated);
1062
- const guideSummary = connectorGuideSummary(addressResolution.connector);
1063
- return {
1064
- address,
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
- ...(requiredReasons
1075
- ? {
1076
- guideRequired: true,
1077
- guideRequiredReasons: requiredReasons,
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
- ...(signature ? { signature: signature.text } : { inputSchema: compactInput?.text ?? input }),
1081
- ...(inputTruncated ? { inputSchemaTruncated: true } : {}),
1082
- ...(outputTruncated ? { outputSchemaTruncated: true } : {}),
1083
- ...(output.schema && !signature
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
- outputSchema: compactOutput?.text ?? output.schema,
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 {