@zackbart/connecta 0.34.1 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (429) hide show
  1. package/CHANGELOG.md +363 -0
  2. package/README.md +62 -106
  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 +12 -7
  53. package/dist/connectors/api-connector.js +142 -56
  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/remote-mcp.d.ts +10 -30
  58. package/dist/connectors/remote-mcp.js +955 -563
  59. package/dist/d1.js +10 -243
  60. package/dist/derived-context.js +5 -0
  61. package/dist/describe-config.d.ts +3 -1
  62. package/dist/describe-config.js +2 -0
  63. package/dist/downstream-input.js +347 -260
  64. package/dist/errors.d.ts +87 -6
  65. package/dist/errors.js +114 -4
  66. package/dist/execute.js +87 -50
  67. package/dist/executor-admission.js +3 -0
  68. package/dist/executors/quickjs.js +163 -112
  69. package/dist/guarded-transport.js +50 -0
  70. package/dist/guest-runtime.js +1 -1
  71. package/dist/http/client.js +439 -0
  72. package/dist/http/errors.js +558 -0
  73. package/dist/http/fields.js +49 -0
  74. package/dist/http/hatch-paging.js +318 -0
  75. package/dist/http/hatch.js +1279 -0
  76. package/dist/http/json-schema.js +267 -0
  77. package/dist/http/node-transport.js +241 -0
  78. package/dist/http/oauth.js +162 -0
  79. package/dist/http/paging.js +228 -0
  80. package/dist/http/provider.js +221 -0
  81. package/dist/http/safety.js +82 -0
  82. package/dist/http/services.js +79 -0
  83. package/dist/http/tools.js +383 -0
  84. package/dist/http/vendor-index.js +138 -0
  85. package/dist/http/vendor.js +448 -0
  86. package/dist/http/write-request.js +66 -0
  87. package/dist/http-policy.d.ts +11 -0
  88. package/dist/http-policy.js +45 -0
  89. package/dist/inbound-credential.js +1 -1
  90. package/dist/index.d.ts +2 -2
  91. package/dist/index.js +4 -0
  92. package/dist/invocation-auth.js +30 -19
  93. package/dist/invocation.js +218 -85
  94. package/dist/mcp-result.js +0 -4
  95. package/dist/membership.d.ts +4 -0
  96. package/dist/meta-output.js +1 -1
  97. package/dist/meta-tools.js +531 -406
  98. package/dist/native-recovery.js +52 -0
  99. package/dist/native-tools.js +382 -0
  100. package/dist/node.d.ts +4 -3
  101. package/dist/node.js +45 -8
  102. package/dist/oauth-handoff.js +177 -104
  103. package/dist/oauth-partition.js +6 -20
  104. package/dist/oauth-sealing.js +6 -7
  105. package/dist/operation-vocabulary.js +101 -0
  106. package/dist/operator-record.d.ts +2 -2
  107. package/dist/operator-record.js +17 -4
  108. package/dist/operator-ui/agents.d.ts +5 -4
  109. package/dist/operator-ui/agents.js +17 -7
  110. package/dist/operator-ui/config-summary.js +5 -4
  111. package/dist/operator-ui/connection-map.js +7 -5
  112. package/dist/operator-ui/contract.d.ts +7 -3
  113. package/dist/operator-ui/endpoints.js +4 -13
  114. package/dist/operator-ui/fix-prompts.d.ts +1 -1
  115. package/dist/operator-ui/fix-prompts.js +0 -8
  116. package/dist/operator-ui/generated.js +3 -3
  117. package/dist/operator-ui/model.d.ts +2 -3
  118. package/dist/operator-ui/overview.js +5 -2
  119. package/dist/operator-ui/setup-commands.js +0 -4
  120. package/dist/operator-ui/view.d.ts +2 -25
  121. package/dist/operator-ui/view.js +2 -76
  122. package/dist/program-writes.js +47 -26
  123. package/dist/provider-call-facts.d.ts +115 -0
  124. package/dist/provider-call-facts.js +182 -0
  125. package/dist/provider-call.d.ts +112 -0
  126. package/dist/provider-call.js +271 -0
  127. package/dist/provider.d.ts +65 -31
  128. package/dist/provider.js +116 -39
  129. package/dist/providers/_shared/google/hatch.js +91 -0
  130. package/dist/providers/_shared/google/kit.js +537 -0
  131. package/dist/providers/_shared/google/result-size.js +15 -2
  132. package/dist/providers/_shared/google/service-account.d.ts +2 -1
  133. package/dist/providers/_shared/google/service-account.js +8 -4
  134. package/dist/providers/_shared/google/workspace.d.ts +6 -210
  135. package/dist/providers/_shared/google/workspace.js +6 -425
  136. package/dist/providers/_shared/rest/compact-index.js +86 -0
  137. package/dist/providers/_shared/rest/dispatch.js +3 -23
  138. package/dist/providers/_shared/rest/kit.js +546 -0
  139. package/dist/providers/_shared/rest/operation-index.js +230 -14
  140. package/dist/providers/_shared/rest/response-media.js +11 -0
  141. package/dist/providers/_shared/rest/tools.js +101 -78
  142. package/dist/providers/_shared/rest/value-safety.js +353 -195
  143. package/dist/providers/_shared/sensitive-fields.js +27 -0
  144. package/dist/providers/breeze/api.js +584 -0
  145. package/dist/providers/breeze/index.d.ts +1 -3
  146. package/dist/providers/breeze/index.js +67 -1420
  147. package/dist/providers/breeze/openapi.generated.js +60 -0
  148. package/dist/providers/breeze/skill.generated.js +2 -6
  149. package/dist/providers/breeze/tools.js +696 -0
  150. package/dist/providers/ccb/api.js +496 -0
  151. package/dist/providers/ccb/index.d.ts +15 -37
  152. package/dist/providers/ccb/index.js +77 -1702
  153. package/dist/providers/ccb/lib/constants.d.ts +34 -0
  154. package/dist/providers/ccb/lib/constants.js +74 -0
  155. package/dist/providers/ccb/lib/guide.js +9 -0
  156. package/dist/providers/ccb/lib/verdicts.js +387 -0
  157. package/dist/providers/ccb/openapi.generated.js +397 -0
  158. package/dist/providers/ccb/safety.js +295 -0
  159. package/dist/providers/ccb/skill.generated.js +2 -6
  160. package/dist/providers/ccb/tools.js +853 -0
  161. package/dist/providers/cloudflare/api.js +1066 -0
  162. package/dist/providers/cloudflare/index.d.ts +5 -22
  163. package/dist/providers/cloudflare/index.js +79 -197
  164. package/dist/providers/cloudflare/lib/config.d.ts +16 -0
  165. package/dist/providers/cloudflare/lib/config.js +2 -0
  166. package/dist/providers/cloudflare/lib/discovery.js +29 -0
  167. package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
  168. package/dist/providers/cloudflare/lib/hatch.js +103 -0
  169. package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
  170. package/dist/providers/cloudflare/lib/response-media.js +75 -0
  171. package/dist/providers/cloudflare/openapi.generated.js +4 -3
  172. package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
  173. package/dist/providers/cloudflare/skill.generated.js +2 -10
  174. package/dist/providers/cloudflare/tools.js +360 -0
  175. package/dist/providers/docs/api.js +51 -0
  176. package/dist/providers/docs/index.d.ts +1 -12
  177. package/dist/providers/docs/index.js +53 -1165
  178. package/dist/providers/docs/lib/constants.d.ts +18 -0
  179. package/dist/providers/docs/lib/constants.js +18 -0
  180. package/dist/providers/docs/lib/discovery.js +26 -0
  181. package/dist/providers/docs/lib/requests.js +48 -0
  182. package/dist/providers/docs/lib/schemas.js +168 -0
  183. package/dist/providers/docs/openapi.generated.js +16 -0
  184. package/dist/providers/docs/safety.js +54 -0
  185. package/dist/providers/docs/skill.generated.js +2 -6
  186. package/dist/providers/docs/tools.js +802 -0
  187. package/dist/providers/drive/api.js +177 -0
  188. package/dist/providers/drive/index.d.ts +1 -12
  189. package/dist/providers/drive/index.js +38 -1448
  190. package/dist/providers/drive/lib/constants.d.ts +6 -0
  191. package/dist/providers/drive/lib/constants.js +6 -0
  192. package/dist/providers/drive/lib/contracts.js +532 -0
  193. package/dist/providers/drive/lib/hatch.js +197 -0
  194. package/dist/providers/drive/openapi.generated.js +77 -0
  195. package/dist/providers/drive/safety.js +90 -0
  196. package/dist/providers/drive/skill.generated.js +2 -6
  197. package/dist/providers/drive/tools.js +1290 -0
  198. package/dist/providers/forms/api.js +71 -0
  199. package/dist/providers/forms/index.d.ts +1 -12
  200. package/dist/providers/forms/index.js +49 -1145
  201. package/dist/providers/forms/lib/constants.d.ts +14 -0
  202. package/dist/providers/forms/lib/constants.js +19 -0
  203. package/dist/providers/forms/lib/hatch.js +72 -0
  204. package/dist/providers/forms/openapi.generated.js +23 -0
  205. package/dist/providers/forms/safety.js +34 -0
  206. package/dist/providers/forms/skill.generated.js +2 -8
  207. package/dist/providers/forms/tools.js +1024 -0
  208. package/dist/providers/github/api.js +150 -0
  209. package/dist/providers/github/index.d.ts +4 -5
  210. package/dist/providers/github/index.js +30 -87
  211. package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
  212. package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
  213. package/dist/providers/github/lib/discovery.js +132 -0
  214. package/dist/providers/github/lib/operations.js +58 -0
  215. package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
  216. package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
  217. package/dist/providers/github/openapi.generated.js +1216 -0
  218. package/dist/providers/github/safety.js +216 -0
  219. package/dist/providers/github/skill.generated.js +2 -5
  220. package/dist/providers/github/tools.js +506 -0
  221. package/dist/providers/gmail/api.js +114 -0
  222. package/dist/providers/gmail/index.d.ts +1 -12
  223. package/dist/providers/gmail/index.js +73 -1621
  224. package/dist/providers/gmail/lib/constants.d.ts +13 -0
  225. package/dist/providers/gmail/lib/constants.js +16 -0
  226. package/dist/providers/gmail/lib/index.js +173 -0
  227. package/dist/providers/gmail/lib/schemas.js +176 -0
  228. package/dist/providers/gmail/lib/threading.js +120 -0
  229. package/dist/providers/gmail/openapi.generated.js +92 -0
  230. package/dist/providers/gmail/safety.js +21 -0
  231. package/dist/providers/gmail/skill.generated.js +2 -6
  232. package/dist/providers/gmail/tools.js +1435 -0
  233. package/dist/providers/overflow/api.js +1008 -0
  234. package/dist/providers/overflow/index.d.ts +1 -17
  235. package/dist/providers/overflow/index.js +60 -1488
  236. package/dist/providers/overflow/skill.generated.js +2 -5
  237. package/dist/providers/overflow/tools.js +873 -0
  238. package/dist/providers/planning-center/api.js +467 -0
  239. package/dist/providers/planning-center/index.d.ts +4 -35
  240. package/dist/providers/planning-center/index.js +69 -2445
  241. package/dist/providers/planning-center/lib/constants.d.ts +58 -0
  242. package/dist/providers/planning-center/lib/constants.js +83 -0
  243. package/dist/providers/planning-center/lib/discovery.js +40 -0
  244. package/dist/providers/planning-center/lib/guide.js +39 -0
  245. package/dist/providers/planning-center/lib/json-api.js +83 -0
  246. package/dist/providers/planning-center/lib/verdicts.js +1758 -0
  247. package/dist/providers/planning-center/openapi.generated.js +1764 -0
  248. package/dist/providers/planning-center/safety.js +188 -0
  249. package/dist/providers/planning-center/skill.generated.js +2 -27
  250. package/dist/providers/planning-center/tools.js +1010 -0
  251. package/dist/providers/sheets/api.js +133 -0
  252. package/dist/providers/sheets/index.d.ts +4 -16
  253. package/dist/providers/sheets/index.js +61 -1172
  254. package/dist/providers/sheets/lib/constants.d.ts +14 -0
  255. package/dist/providers/sheets/lib/constants.js +14 -0
  256. package/dist/providers/sheets/lib/hatch.js +163 -0
  257. package/dist/providers/sheets/lib/requests.js +105 -0
  258. package/dist/providers/sheets/lib/schemas.js +138 -0
  259. package/dist/providers/sheets/openapi.generated.js +30 -0
  260. package/dist/providers/sheets/safety.js +27 -0
  261. package/dist/providers/sheets/skill.generated.js +2 -7
  262. package/dist/providers/sheets/tools.js +734 -0
  263. package/dist/providers/slides/api.js +147 -0
  264. package/dist/providers/slides/index.d.ts +2 -10
  265. package/dist/providers/slides/index.js +48 -2555
  266. package/dist/providers/slides/lib/constants.d.ts +11 -0
  267. package/dist/providers/slides/lib/constants.js +11 -0
  268. package/dist/providers/slides/lib/requests.js +60 -0
  269. package/dist/providers/slides/lib/schemas.js +565 -0
  270. package/dist/providers/slides/openapi.generated.js +18 -0
  271. package/dist/providers/slides/skill.generated.js +2 -6
  272. package/dist/providers/slides/tools.js +1968 -0
  273. package/dist/providers/stripe/api.js +401 -0
  274. package/dist/providers/stripe/index.d.ts +5 -27
  275. package/dist/providers/stripe/index.js +74 -210
  276. package/dist/providers/stripe/lib/config.d.ts +4 -0
  277. package/dist/providers/stripe/lib/config.js +2 -0
  278. package/dist/providers/stripe/openapi.generated.js +4 -3
  279. package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
  280. package/dist/providers/stripe/skill.generated.js +2 -11
  281. package/dist/providers/stripe/tools.js +77 -0
  282. package/dist/providers/tithely/api.js +531 -0
  283. package/dist/providers/tithely/index.d.ts +17 -0
  284. package/dist/providers/tithely/index.js +86 -965
  285. package/dist/providers/tithely/skill.generated.js +2 -5
  286. package/dist/providers/tithely/tools.js +426 -0
  287. package/dist/providers/vercel/api.js +551 -0
  288. package/dist/providers/vercel/index.d.ts +9 -25
  289. package/dist/providers/vercel/index.js +66 -260
  290. package/dist/providers/vercel/lib/config.d.ts +2 -0
  291. package/dist/providers/vercel/lib/config.js +2 -0
  292. package/dist/providers/vercel/lib/index.js +117 -0
  293. package/dist/providers/vercel/lib/logs.js +100 -0
  294. package/dist/providers/vercel/lib/pin.js +270 -0
  295. package/dist/providers/vercel/openapi.generated.js +10 -5
  296. package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
  297. package/dist/providers/vercel/skill.generated.js +2 -7
  298. package/dist/providers/vercel/tools.js +642 -0
  299. package/dist/redaction-identity.js +47 -0
  300. package/dist/redaction-marker.js +125 -0
  301. package/dist/registry.d.ts +21 -15
  302. package/dist/registry.js +409 -376
  303. package/dist/routes/access-tokens.js +1 -1
  304. package/dist/routes/actor-labels.js +1 -1
  305. package/dist/routes/connect.js +1 -1
  306. package/dist/routes/dispatch.js +103 -0
  307. package/dist/routes/mcp.js +170 -79
  308. package/dist/routes/oauth.js +3 -1
  309. package/dist/routes/operator.js +2 -2
  310. package/dist/routes/shared.d.ts +5 -4
  311. package/dist/routes/shared.js +244 -177
  312. package/dist/routes/skills.js +10 -8
  313. package/dist/routes/ui-config.js +27 -3
  314. package/dist/routes/ui-data.js +21 -20
  315. package/dist/routes/ui.js +1 -1
  316. package/dist/runtime/call-admission.js +0 -26
  317. package/dist/runtime/call-entry.js +30 -0
  318. package/dist/runtime/call.js +556 -0
  319. package/dist/runtime/connector-scope.js +7 -0
  320. package/dist/runtime/credential-probe.js +34 -0
  321. package/dist/runtime/executor.js +40 -0
  322. package/dist/runtime/native-storage.js +63 -0
  323. package/dist/runtime/registry.js +27 -0
  324. package/dist/runtime/request-scope.js +72 -0
  325. package/dist/runtime/run.js +15 -7
  326. package/dist/runtime/shared-read.js +34 -5
  327. package/dist/runtime/storage.js +34 -21
  328. package/dist/sent-secrets.d.ts +194 -37
  329. package/dist/sent-secrets.js +1743 -340
  330. package/dist/server.js +5 -56
  331. package/dist/skills.js +322 -222
  332. package/dist/sqlite.js +15 -14
  333. package/dist/storage/d1-copy.js +255 -0
  334. package/dist/storage/d1-driver.js +15 -0
  335. package/dist/storage/keys.js +28 -0
  336. package/dist/storage/sql-driver.js +88 -0
  337. package/dist/storage/sql-native.js +544 -0
  338. package/dist/storage/sql-schema.d.ts +6 -0
  339. package/dist/storage/sql-schema.js +15 -0
  340. package/dist/storage/sql.d.ts +14 -20
  341. package/dist/storage/sql.js +29 -535
  342. package/dist/timeout.js +0 -26
  343. package/dist/tool-safety.js +20 -10
  344. package/dist/types.d.ts +7 -109
  345. package/dist/upward.js +3 -0
  346. package/dist/usage-guide.js +3 -1
  347. package/dist/validate.d.ts +4 -2
  348. package/dist/validate.js +86 -9
  349. package/dist/version.d.ts +1 -1
  350. package/dist/version.js +1 -1
  351. package/dist/worker.js +312 -292
  352. package/dist/workspace-subject.js +5 -1
  353. package/dist/write-outcome.js +454 -34
  354. package/documentation/README.md +27 -7
  355. package/documentation/architecture.md +2547 -226
  356. package/documentation/auth.md +821 -98
  357. package/documentation/code-mode.md +49 -16
  358. package/documentation/deploying.md +164 -20
  359. package/documentation/hosted-mcp-migration.md +460 -0
  360. package/documentation/integrating.md +129 -144
  361. package/documentation/meta-tools.md +409 -32
  362. package/documentation/operating.md +27 -3
  363. package/documentation/operator-tests.md +3 -0
  364. package/documentation/operator-ui.md +68 -45
  365. package/documentation/provider-auth-migration.md +16 -63
  366. package/documentation/provider-migration-0.29.md +10 -5
  367. package/documentation/provider-migration-0.35.md +975 -0
  368. package/documentation/providers.md +965 -0
  369. package/examples/worker/AGENTS.md +23 -9
  370. package/examples/worker/README.md +254 -135
  371. package/examples/worker/alchemy.run.ts +116 -0
  372. package/examples/worker/gitignore +10 -0
  373. package/examples/worker/src/connecta.config.ts +23 -21
  374. package/examples/worker/wrangler.jsonc +14 -5
  375. package/guides/connecting-agents.md +157 -0
  376. package/guides/how-it-works.md +156 -0
  377. package/guides/safety-and-trust.md +153 -0
  378. package/package.json +27 -46
  379. package/templates/node/.env.example +3 -0
  380. package/templates/node/README.md +22 -2
  381. package/templates/node/docker-compose.yml +1 -0
  382. package/templates/node/package.json +3 -2
  383. package/templates/node/src/connecta.config.ts +8 -0
  384. package/dist/catalog-drift.d.ts +0 -138
  385. package/dist/catalog-drift.js +0 -578
  386. package/dist/providers/_shared/request-token.js +0 -83
  387. package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
  388. package/dist/providers/_shared/rest/tools.d.ts +0 -141
  389. package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
  390. package/dist/providers/basecamp/index.d.ts +0 -45
  391. package/dist/providers/basecamp/index.js +0 -997
  392. package/dist/providers/basecamp/skill.generated.js +0 -9
  393. package/dist/providers/cloudflare/rest.d.ts +0 -37
  394. package/dist/providers/cloudflare/rest.js +0 -1097
  395. package/dist/providers/github/hosted.js +0 -218
  396. package/dist/providers/github/rest.js +0 -321
  397. package/dist/providers/infisical/index.d.ts +0 -7
  398. package/dist/providers/infisical/index.js +0 -713
  399. package/dist/providers/infisical/skill.generated.js +0 -8
  400. package/dist/providers/intercom/index.d.ts +0 -24
  401. package/dist/providers/intercom/index.js +0 -121
  402. package/dist/providers/intercom/skill.generated.js +0 -8
  403. package/dist/providers/linear/index.d.ts +0 -55
  404. package/dist/providers/linear/index.js +0 -537
  405. package/dist/providers/linear/skill.generated.js +0 -8
  406. package/dist/providers/mixpanel/index.d.ts +0 -59
  407. package/dist/providers/mixpanel/index.js +0 -472
  408. package/dist/providers/mixpanel/operations.js +0 -20
  409. package/dist/providers/mixpanel/rest.js +0 -603
  410. package/dist/providers/mixpanel/skill.generated.js +0 -10
  411. package/dist/providers/mixpanel/value-safety.js +0 -16
  412. package/dist/providers/notion/index.d.ts +0 -67
  413. package/dist/providers/notion/index.js +0 -368
  414. package/dist/providers/notion/openapi.generated.js +0 -75
  415. package/dist/providers/notion/rest.d.ts +0 -24
  416. package/dist/providers/notion/rest.js +0 -1226
  417. package/dist/providers/notion/skill.generated.js +0 -11
  418. package/dist/providers/notion/value-safety.js +0 -56
  419. package/dist/providers/revenuecat/index.d.ts +0 -51
  420. package/dist/providers/revenuecat/index.js +0 -697
  421. package/dist/providers/revenuecat/skill.generated.js +0 -11
  422. package/dist/providers/stripe/rest.d.ts +0 -14
  423. package/dist/providers/stripe/rest.js +0 -402
  424. package/dist/providers/vercel/rest.d.ts +0 -20
  425. package/dist/providers/vercel/rest.js +0 -1020
  426. package/dist/providers/wispr-flow/index.d.ts +0 -14
  427. package/dist/providers/wispr-flow/index.js +0 -117
  428. package/dist/providers/wispr-flow/skill.generated.js +0 -8
  429. /package/dist/providers/github/{key.js → lib/key.js} +0 -0
@@ -0,0 +1,1279 @@
1
+ // Internal kit hatch (decisions 0010 B.4, 0011 S1, 0014). Unreachable from src/index.ts.
2
+ // The operation index validates requests; only an explicit verdict routes
3
+ // them. Connection.sendPath owns admission, dispatch and result safety.
4
+ //
5
+ // Argument and envelope names follow the REST family's hatch (`path`,
6
+ // `method`, `query`, `body`, `{ status, data }`), with S5's `fields` for its
7
+ // `select`. Discovery stays inside 0014's budget: search returns compact
8
+ // entries (8 at most, 16 KiB of JSON at most, with an explicit continuation
9
+ // cursor) and details return at most 32 KiB, cutting only optional parts and
10
+ // listing them in `truncated`; a required parameter or body field is never cut.
11
+ import { Effect, Schema } from "effect";
12
+ import { lexicalSearchContent } from "../catalog.js";
13
+ import { registerOperationVocabulary } from "../operation-vocabulary.js";
14
+ import { ConnectorCallError } from "../errors.js";
15
+ import { keyResendAdvice } from "../errors.js";
16
+ import { IDEMPOTENCY_KEY_MAX_LENGTH } from "../call-recovery.js";
17
+ import { providerFailureMessage } from "../write-outcome.js";
18
+ import { redactionAltered } from "../redaction-marker.js";
19
+ import { refuseWithheldValues } from "../providers/_shared/rest/value-safety.js";
20
+ import { Refusal, toConnectorCallError } from "./errors.js";
21
+ import { fieldsInput, fieldsProblem, selectFields } from "./fields.js";
22
+ import { Integer, isMaxChars, isMinChars } from "./json-schema.js";
23
+ import { boundCursor, StandardPage } from "./paging.js";
24
+ import { Classification, Dispatches, IdempotencyKey, RequestSecrets, Signal } from "./services.js";
25
+ import { read, write } from "./tools.js";
26
+ const indexedOperations = new WeakMap();
27
+ function operationsOf(index) {
28
+ let operations = indexedOperations.get(index);
29
+ if (!operations) {
30
+ const first = new Map();
31
+ for (const op of index.ops) {
32
+ const key = `${op.method} ${op.path}`;
33
+ // Keep the first match, as the previous per-key Array.find did.
34
+ if (!first.has(key))
35
+ first.set(key, op);
36
+ }
37
+ operations = first;
38
+ indexedOperations.set(index, operations);
39
+ }
40
+ return operations;
41
+ }
42
+ /** 0014's starting discovery budgets, measured as serialized JSON bytes of the tool's own result. */
43
+ export const SEARCH_BUDGET = { hits: 6, maxHits: 8, bytes: 16 * 1024, summaryChars: 300, refusalChars: 512 };
44
+ export const DETAILS_BUDGET_BYTES = 32 * 1024;
45
+ const text = (min, max) => Schema.String.check(isMinChars(min), isMaxChars(max));
46
+ const ObjectValue = Schema.Record(Schema.String, Schema.Unknown);
47
+ const Envelope = Schema.Struct({
48
+ status: Integer.annotate({ description: "HTTP status." }),
49
+ data: Schema.Unknown.pipe(Schema.annotateKey({ description: "Response body after value safety and the connection's privacy filtering." })),
50
+ });
51
+ const HATCH_FIELDS = fieldsInput("Dot paths to keep in data, applied to each item of an array, after value safety and privacy filtering.");
52
+ function invalid(message) {
53
+ throw new ConnectorCallError("invalid_args", message);
54
+ }
55
+ /** Keep the index's structured argument repairs, while unexpected failures remain withheld defects. */
56
+ function checked(run) {
57
+ return Effect.try({ try: run, catch: (error) => error }).pipe(Effect.catch((error) => error instanceof ConnectorCallError ? Effect.fail(new Refusal({ error })) : Effect.die(error)));
58
+ }
59
+ function record(value) {
60
+ return value !== null && typeof value === "object" && !Array.isArray(value);
61
+ }
62
+ function withheldCursor(value) {
63
+ return redactionAltered(value) || (typeof value === "string" && /%5bredacted%5d/i.test(value));
64
+ }
65
+ /** Detect the signed envelope, including damaged signatures, without admitting it as a record id. */
66
+ function signedCursorShape(value) {
67
+ if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value))
68
+ return false;
69
+ try {
70
+ const decoded = JSON.parse(atob(value.replace(/-/g, "+").replace(/_/g, "/")));
71
+ return record(decoded) && decoded["v"] === 2 && ["d", "c", "m"].every((key) => typeof decoded[key] === "string");
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ }
77
+ /** A method enum; a single method renders as a plain enum, not a one-branch anyOf. */
78
+ function methodEnum(methods) {
79
+ return methods.length === 1 ? Schema.Literal(methods[0]) : Schema.Literals(methods);
80
+ }
81
+ const bytes = (value) => new TextEncoder().encode(JSON.stringify(value)).length;
82
+ /** The binding carries ordered string pairs, preserving scalar arrays as repeated parameters. */
83
+ function parameters(values) {
84
+ return Object.entries(values).flatMap(([name, value]) => {
85
+ if (value === undefined)
86
+ return [];
87
+ return (Array.isArray(value) ? value : [value]).map((item) => {
88
+ if (typeof item !== "string" &&
89
+ typeof item !== "boolean" &&
90
+ !(typeof item === "number" && Number.isFinite(item))) {
91
+ invalid(`Parameter ${name} takes a string, finite number, boolean, or a list of them.`);
92
+ }
93
+ return [name, String(item)];
94
+ });
95
+ });
96
+ }
97
+ const cap = (text, chars) => {
98
+ if (text.length <= chars)
99
+ return text;
100
+ let end = chars - 1;
101
+ if (/[\uD800-\uDBFF]/.test(text[end - 1]))
102
+ end--;
103
+ return `${text.slice(0, end)}…`;
104
+ };
105
+ /**
106
+ * Every piece of a contract, in order: long labels, parameters, then the body.
107
+ * An object body travels as its frame (every keyword but `properties` and
108
+ * `required`: type, description, additionalProperties, …) and one piece per
109
+ * property, each carrying whether it is required, so pages and chunks
110
+ * together reconstruct the whole schema. Any other body travels whole.
111
+ */
112
+ function piecesOf(contract, capped) {
113
+ const schema = contract.body?.schema;
114
+ const properties = schema?.["properties"];
115
+ const required = new Set(Array.isArray(schema?.["required"]) ? schema["required"] : []);
116
+ const object = properties !== null && typeof properties === "object" && !Array.isArray(properties);
117
+ const frame = object
118
+ ? Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "properties" && key !== "required"))
119
+ : undefined;
120
+ return [
121
+ ...(capped.operationId ? [{ kind: "text", of: "operationId", value: contract.operationId }] : []),
122
+ ...(capped.summary ? [{ kind: "text", of: "summary", value: contract.summary }] : []),
123
+ ...(capped.refusal ? [{ kind: "text", of: "refusal", value: contract.refusal }] : []),
124
+ ...contract.parameters.map((value) => ({ kind: "parameter", of: `parameters.${value.name}`, value })),
125
+ ...(contract.body === undefined
126
+ ? []
127
+ : frame
128
+ ? [
129
+ { kind: "frame", of: "body.schema", value: frame },
130
+ ...Object.entries(properties).map(([name, value]) => ({
131
+ kind: "property",
132
+ of: `body.schema.properties.${name}`,
133
+ name,
134
+ value,
135
+ required: required.has(name),
136
+ })),
137
+ ]
138
+ : [{ kind: "body", of: "body.schema", value: contract.body.schema }]),
139
+ ];
140
+ }
141
+ /** The standard hatch over an operation index; a missing verdict refuses every method. */
142
+ export function hatch(options) {
143
+ const { name, index, connection } = options;
144
+ if (options.binding?.index !== undefined && options.binding.index !== index)
145
+ throw new Error("This hatch binding requires its reviewed operation index.");
146
+ const title = options.title ?? name;
147
+ if (options.textBodies !== undefined && options.textBodies !== true)
148
+ throw new Error("Hatch textBodies must opt in with true.");
149
+ if (options.fieldsRoot !== undefined && !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(options.fieldsRoot))
150
+ throw new Error("A hatch fieldsRoot must be a property name.");
151
+ const headerFields = Object.fromEntries(Object.entries(options.headers ?? {}).map(([key, entry]) => {
152
+ if (!/^[a-z0-9-]+$/.test(key) ||
153
+ !entry.description.trim() ||
154
+ !entry.values.length ||
155
+ entry.values.some((value) => !value || /[\r\n]/.test(value)))
156
+ throw new Error("Hatch headers need lowercase names, descriptions and closed values.");
157
+ return [
158
+ key,
159
+ Schema.optionalKey(Schema.Literals([...entry.values]).annotate({ description: entry.description })),
160
+ ];
161
+ }));
162
+ const Headers = Object.keys(headerFields).length ? Schema.Struct(headerFields) : undefined;
163
+ const Fields = options.fieldsRoot
164
+ ? fieldsInput(`Dot paths to keep in data. Envelope fields begin with ${options.fieldsRoot}, e.g. ["${options.fieldsRoot}.id"] or ["${options.fieldsRoot}[].id"], where declared.`)
165
+ : HATCH_FIELDS;
166
+ const Body = options.textBodies
167
+ ? Schema.Union([ObjectValue, Schema.Array(Schema.Unknown), Schema.String])
168
+ : undefined;
169
+ if (options.writeEnabled !== undefined && typeof options.writeEnabled !== "boolean")
170
+ throw new Error("Hatch writeEnabled must be a boolean.");
171
+ if (options.binding && !options.binding.review.trim())
172
+ throw new Error("A hatch binding needs a vendor review.");
173
+ const idempotency = options.idempotency
174
+ ? { ...options.idempotency, methods: [...options.idempotency.methods] }
175
+ : undefined;
176
+ if (!name.trim())
177
+ throw new Error("A hatch needs a nonempty tool prefix.");
178
+ const operations = operationsOf(index);
179
+ const known = (key) => operations.has(key);
180
+ // Snapshot reviews: a retained declaration cannot widen a served hatch.
181
+ const verdicts = new Map(Object.entries(options.verdicts));
182
+ for (const [key, verdict] of verdicts) {
183
+ if (!known(key))
184
+ throw new Error(`Hatch verdict ${key} is not in the API index.`);
185
+ if (!["read", "write", "destructive", "refuse"].includes(verdict)) {
186
+ throw new Error(`Hatch verdict ${key} must be read, write, destructive, or refuse.`);
187
+ }
188
+ }
189
+ const named = new Map(Object.entries(options.binding?.named ?? options.named ?? {}));
190
+ for (const [key, tool] of named) {
191
+ if (!known(key) || !tool.trim())
192
+ throw new Error(`Hatch named tool for ${key} needs an indexed operation.`);
193
+ }
194
+ const verdictOf = (op) => verdicts.get(`${op.method} ${op.path}`) ?? "refuse";
195
+ const refusalOf = (op) => connection.safety.refusal?.({ method: op.method, template: op.path }) ?? options.binding?.refusal?.(op);
196
+ const discoveryRefusals = new Map();
197
+ if (options.binding?.discoveryRefusal)
198
+ for (const op of index.ops) {
199
+ const reason = options.binding.discoveryRefusal(op);
200
+ if (reason !== undefined)
201
+ discoveryRefusals.set(`${op.method} ${op.path}`, reason);
202
+ }
203
+ const discoveryVerdictOf = (op) => discoveryRefusals.has(`${op.method} ${op.path}`) ? "refuse" : verdictOf(op);
204
+ const discoveryRefusal = (op) => discoveryRefusals.get(`${op.method} ${op.path}`) ??
205
+ (verdictOf(op) === "refuse"
206
+ ? (refusalOf(op) ?? `Connecta refuses ${op.method} ${op.path}: it has no admitted hatch verdict.`)
207
+ : undefined);
208
+ const pages = new Map(Object.entries(options.pages ?? {}).map(([key, declared]) => {
209
+ const adapters = Array.isArray(declared) ? declared : [declared];
210
+ if (!adapters.length)
211
+ throw new Error(`Hatch paging for ${key} needs a cursor adapter.`);
212
+ const seen = new Set();
213
+ const snapshot = adapters.map((adapter) => {
214
+ if (!known(key) ||
215
+ verdicts.get(key) !== "read" ||
216
+ !adapter?.param.trim() ||
217
+ !["query", "body"].includes(adapter.in) ||
218
+ typeof adapter.next !== "function" ||
219
+ typeof adapter.decode !== "function" ||
220
+ (adapter.prepareQuery !== undefined && typeof adapter.prepareQuery !== "function") ||
221
+ (adapter.total !== undefined && typeof adapter.total !== "function")) {
222
+ throw new Error(`Hatch paging for ${key} needs an indexed read and a cursor adapter.`);
223
+ }
224
+ const location = `${adapter.in}:${adapter.param}`;
225
+ if (seen.has(location))
226
+ throw new Error(`Hatch paging for ${key} repeats a cursor parameter.`);
227
+ seen.add(location);
228
+ const op = operations.get(key);
229
+ const contract = index.contract(op);
230
+ const body = contract.body?.schema;
231
+ if (adapter.in === "query"
232
+ ? !contract.parameters.some((param) => param.in === "query" && param.name === adapter.param)
233
+ : !Object.hasOwn(body?.properties ?? {}, adapter.param)) {
234
+ throw new Error(`Hatch paging for ${key} must name a declared ${adapter.in} parameter.`);
235
+ }
236
+ return Object.freeze({ ...adapter, ...(adapter.headers ? { headers: [...adapter.headers] } : {}) });
237
+ });
238
+ return [key, snapshot];
239
+ }));
240
+ const callEnvelope = Schema.Struct({
241
+ ...Envelope.fields,
242
+ ...(options.binding?.truncated
243
+ ? {
244
+ truncated: Schema.optionalKey(Schema.Array(Schema.String).annotate({ description: "Dot paths cut for size, including the data prefix." })),
245
+ }
246
+ : {}),
247
+ ...(options.binding?.warnings
248
+ ? {
249
+ warnings: Schema.optionalKey(Schema.Array(Schema.String).annotate({
250
+ description: "Reviewed warnings preserved after fields selection.",
251
+ })),
252
+ }
253
+ : {}),
254
+ });
255
+ const hasTotals = [...pages.values()].some((adapters) => adapters.some((adapter) => adapter.total !== undefined));
256
+ const envelope = pages.size
257
+ ? Schema.Struct({
258
+ ...callEnvelope.fields,
259
+ page: Schema.optionalKey(Schema.Struct({
260
+ ...StandardPage.fields,
261
+ ...(hasTotals ? { total: Schema.optionalKey(Integer.check(Schema.isGreaterThanOrEqualTo(0))) } : {}),
262
+ param: Schema.String.annotate({
263
+ description: options.pageCursor === "argument"
264
+ ? "Vendor parameter restored from cursor; pass nextCursor as the top-level cursor."
265
+ : "Put nextCursor in this vendor parameter, unchanged.",
266
+ }),
267
+ in: Schema.Literals(["query", "body"]),
268
+ })),
269
+ })
270
+ : callEnvelope;
271
+ const tool = (kind) => `${name}_api_${kind}`;
272
+ const route = (op) => {
273
+ const verdict = discoveryVerdictOf(op);
274
+ const authored = named.get(`${op.method} ${op.path}`);
275
+ return {
276
+ ...(verdict === "refuse" || (verdict !== "read" && options.writeEnabled === false)
277
+ ? {}
278
+ : { tool: tool(verdict === "read" ? "read" : "write") }),
279
+ ...(authored ? { named: authored } : {}),
280
+ };
281
+ };
282
+ const updateWords = ["update", "edit", "modify", "change", "set", "apply", "make", "rename"];
283
+ const verbs = {
284
+ delete: ["delete", "remove", "destroy", "cancel", "stop"],
285
+ remove: ["delete", "remove", "destroy"],
286
+ cancel: ["cancel", "stop", "delete"],
287
+ stop: ["cancel", "stop", "delete"],
288
+ update: updateWords,
289
+ edit: updateWords,
290
+ make: updateWords,
291
+ set: updateWords,
292
+ apply: updateWords,
293
+ change: updateWords,
294
+ rename: updateWords,
295
+ create: ["create", "add", "new"],
296
+ add: ["create", "add", "new"],
297
+ get: ["get", "retrieve", "read"],
298
+ read: ["get", "retrieve", "read"],
299
+ retrieve: ["get", "retrieve", "read"],
300
+ fetch: ["get", "retrieve", "read"],
301
+ details: ["get", "retrieve", "read"],
302
+ detail: ["get", "retrieve", "read"],
303
+ info: ["get", "retrieve", "read"],
304
+ view: ["get", "retrieve", "read"],
305
+ status: ["get", "retrieve", "read", "list"],
306
+ history: ["list", "find", "search"],
307
+ list: ["list", "find", "search"],
308
+ find: ["list", "find", "search"],
309
+ search: ["list", "find", "search"],
310
+ refund: ["refund", "reimburse"],
311
+ charge: ["charge"],
312
+ issue: ["issue"],
313
+ process: ["process"],
314
+ send: ["send"],
315
+ };
316
+ const readActions = new Set([
317
+ "get",
318
+ "read",
319
+ "list",
320
+ "find",
321
+ "search",
322
+ "details",
323
+ "detail",
324
+ "status",
325
+ "history",
326
+ "info",
327
+ "view",
328
+ "retrieve",
329
+ "fetch",
330
+ ]);
331
+ const writeActions = new Set([
332
+ "create",
333
+ "add",
334
+ "update",
335
+ "edit",
336
+ "make",
337
+ "set",
338
+ "apply",
339
+ "change",
340
+ "rename",
341
+ "send",
342
+ "delete",
343
+ "remove",
344
+ "cancel",
345
+ "stop",
346
+ "refund",
347
+ "charge",
348
+ "issue",
349
+ "process",
350
+ ]);
351
+ const queryWords = (query) => lexicalSearchContent(query)
352
+ .split(/[^a-z0-9]+/)
353
+ .filter((word) => word.length >= 2);
354
+ const writeVerbs = new Set([
355
+ "create",
356
+ "add",
357
+ "update",
358
+ "edit",
359
+ "make",
360
+ "set",
361
+ "apply",
362
+ "change",
363
+ "rename",
364
+ "send",
365
+ "delete",
366
+ "remove",
367
+ "cancel",
368
+ "stop",
369
+ ]);
370
+ const functionWords = new Set([
371
+ "by",
372
+ "its",
373
+ "my",
374
+ "your",
375
+ "their",
376
+ "his",
377
+ "her",
378
+ "no",
379
+ "yes",
380
+ "at",
381
+ "as",
382
+ "is",
383
+ "be",
384
+ "or",
385
+ "not",
386
+ ]);
387
+ let resourceTerms;
388
+ const isNoun = (term) => {
389
+ if (functionWords.has(term) || /^\d+$/.test(term))
390
+ return false;
391
+ if (options.binding?.searchNoun?.(term) === false)
392
+ return false;
393
+ if (term.length < 5 || !/\d/.test(term))
394
+ return true;
395
+ resourceTerms ??= new Set(index.ops.flatMap((op) => queryWords(op.path.replace(/\{[^}]*\}/g, ""))));
396
+ return resourceTerms.has(term);
397
+ };
398
+ const queryTerms = (query) => {
399
+ const terms = queryWords(query).filter((term) => !functionWords.has(term));
400
+ const resourcePhrase = queryWords(query.split(/(?:^|\s)(?:for|of|in|on|by|with)(?=\s|$)/i)[0]).filter(isNoun);
401
+ const lookup = terms.find((term) => readActions.has(term));
402
+ const first = terms[0];
403
+ // issue/process remain nouns unless a leading verb has another noun to act on.
404
+ const leadingVerb = first !== undefined &&
405
+ Object.hasOwn(verbs, first) &&
406
+ (!["issue", "process", "refund", "charge"].includes(first) ||
407
+ (!lookup && terms.slice(1).some((term) => !readActions.has(term) && !/^\d+$/.test(term))));
408
+ // An explicit leading write verb wins; a later read word (list, status) is its object.
409
+ if (first !== undefined && terms.length > 1 && writeVerbs.has(first)) {
410
+ // In renaming/setting, the destination after "to" is a supplied value.
411
+ const object = ["rename", "change", "set", "make"].includes(first)
412
+ ? queryWords(query.split(/\bto\b/i)[0]).filter((term) => !functionWords.has(term))
413
+ : terms;
414
+ const nouns = object.slice(1).filter(isNoun);
415
+ return {
416
+ terms,
417
+ resourcePhrase,
418
+ nouns,
419
+ suppliedValue: object.length > 1 && !nouns.length,
420
+ verbWords: (first === "make" && /\bmake\s+(?:an?|new)\b/i.test(query)) ||
421
+ (first === "set" && /\bset\s+up\b/i.test(query))
422
+ ? verbs.create
423
+ : verbs[first],
424
+ intent: "write",
425
+ };
426
+ }
427
+ const verb = lookup ?? (leadingVerb ? first : undefined);
428
+ return {
429
+ terms,
430
+ resourcePhrase,
431
+ suppliedValue: false,
432
+ nouns: terms.filter((term, position) => isNoun(term) &&
433
+ !readActions.has(term) &&
434
+ !(position === 0 && leadingVerb && !["refund", "charge"].includes(term))),
435
+ verbWords: verb === undefined ? undefined : verbs[verb],
436
+ intent: (lookup ? "read" : leadingVerb && writeActions.has(first) ? "write" : undefined),
437
+ };
438
+ };
439
+ // Compare inflections symmetrically: campus/campuses must not stem campus to campu.
440
+ const plural = (word) => (word.endsWith("y") ? `${word.slice(0, -1)}ies` : `${word}es`);
441
+ const sameNoun = (word, term) => word === term ||
442
+ word === `${term}s` ||
443
+ term === `${word}s` ||
444
+ word === plural(term) ||
445
+ term === plural(word) ||
446
+ (word === "people" && term === "person") ||
447
+ (word === "person" && term === "people") ||
448
+ (word === "mail" && term === "email") ||
449
+ (word === "email" && term === "mail");
450
+ const matches = (word, term) => sameNoun(word, term) || (term.length >= 3 && word.startsWith(term));
451
+ // Intent and the requested verb precede resource and noun-phrase ties.
452
+ // Stable lexical order remains the final key; every match stays reachable.
453
+ const rank = (found, query, pathQuery) => {
454
+ if (pathQuery)
455
+ return [...found];
456
+ const { nouns, intent, verbWords } = query;
457
+ const headNouns = query.resourcePhrase.length ? query.resourcePhrase : nouns;
458
+ const phrase = intent === undefined && nouns.length > 1;
459
+ // Modifiers may precede the resource in action/read queries. Noun phrases
460
+ // retain their broader resource match without the half-coverage threshold.
461
+ const half = Math.ceil(nouns.length / 2);
462
+ const features = found
463
+ .map((op, position) => {
464
+ const parts = op.path.toLowerCase().split("/").filter(Boolean);
465
+ const literal = parts.filter((part) => !part.startsWith("{")).flatMap(words);
466
+ const parameter = parts.findIndex((part) => part.startsWith("{"));
467
+ const root = parameter < 0 ? parts : parts.slice(0, parameter);
468
+ const description = words(`${op.summary} ${op.operationId}`);
469
+ const verbal = [...description, ...words(op.path)];
470
+ const text = [...verbal, ...words(op.tag)];
471
+ const resources = parts.filter((part) => !part.startsWith("{") && !/^v\d+$/.test(part)).flatMap(words);
472
+ const authored = named.get(`${op.method} ${op.path}`);
473
+ const summary = phrase ? queryWords(op.summary) : [];
474
+ return {
475
+ op,
476
+ position,
477
+ parts,
478
+ literal,
479
+ root,
480
+ parameter,
481
+ text,
482
+ resources,
483
+ terminal: parts
484
+ .filter((part) => !part.startsWith("{") && !/^v\d+$/.test(part))
485
+ .slice(-1)
486
+ .some((part) => words(part).some((word) => sameNoun(word, headNouns.at(-1) ?? "") || sameNoun(word, headNouns.join("")))),
487
+ readDescription: readActions.has(description[0] ?? ""),
488
+ authoredCoverage: nouns.filter((term) => description.slice(1).some((word) => !readActions.has(word) && matches(word, term))).length,
489
+ authored: Number(authored !== undefined && !authored.startsWith(`${name}_api_`)),
490
+ phrasePrefix: Number(nouns.every((term, position) => summary[position] === term)),
491
+ identity: [...literal, ...words(op.operationId)],
492
+ verbMatch: verbWords !== undefined && verbal.some((word) => verbWords.includes(word)),
493
+ descriptive: nouns.filter((term) => description.some((word) => matches(word, term))).length,
494
+ covered: nouns.filter((term) => text.some((word) => matches(word, term))).length,
495
+ };
496
+ })
497
+ .filter(({ identity, covered }) => {
498
+ return (nouns.length === 0 ||
499
+ covered === nouns.length ||
500
+ ((intent === undefined || covered >= half) &&
501
+ nouns.some((term) => identity.some((word) => matches(word, term)))));
502
+ });
503
+ const terminalExists = features.some((feature) => feature.terminal);
504
+ const focus = nouns.find((term) => features.some(({ literal }) => literal.some((word) => sameNoun(word, term))));
505
+ const resourceNouns = nouns.filter((term) => features.some(({ literal }) => literal.some((word) => sameNoun(word, term))));
506
+ const resourceOnly = nouns.length === 1 || (query.terms[0] === "list" && resourceNouns.length === 1);
507
+ const admittedCoverage = Math.max(0, ...features
508
+ .filter(({ op, verbMatch }) => verbMatch &&
509
+ (intent === "read"
510
+ ? discoveryVerdictOf(op) === "read"
511
+ : ["write", "destructive"].includes(discoveryVerdictOf(op))))
512
+ .map(({ covered }) => covered));
513
+ const keyed = features.map(({ op, position, parts, literal, root, parameter, verbMatch, covered, descriptive, resources, terminal, readDescription, authoredCoverage, authored, phrasePrefix, }) => ({
514
+ op,
515
+ position,
516
+ intent: Number(intent === undefined ||
517
+ (intent === "read"
518
+ ? discoveryVerdictOf(op) === "read"
519
+ : ["write", "destructive"].includes(discoveryVerdictOf(op))) ||
520
+ (discoveryVerdictOf(op) === "refuse" && verbMatch && covered > admittedCoverage)),
521
+ verbMatch: Number(verbMatch),
522
+ covered,
523
+ phrasePrefix,
524
+ completeAuthored: Number(authored && descriptive === nouns.length),
525
+ phraseCoverage: nouns.filter((term) => resources.some((word) => sameNoun(word, term))).length,
526
+ resourceHead: Number(terminal || (terminalExists && authored && authoredCoverage === nouns.length && readDescription)),
527
+ readable: Number(discoveryVerdictOf(op) === "read"),
528
+ focused: Number((intent !== undefined || resourceOnly) &&
529
+ focus !== undefined &&
530
+ literal.some((word) => sameNoun(word, focus))),
531
+ exact: Number(focus !== undefined && sameNoun((root.at(-1) ?? "").replace(/-list(?:-all)?$/, ""), focus)),
532
+ nested: parameter < 0 ? 0 : parts.slice(parameter).filter((part) => !part.startsWith("{")).length,
533
+ namespace: Number(focus !== undefined && sameNoun((root[0] ?? "").replace(/-list(?:-all)?$/, ""), focus)),
534
+ depth: parts.length,
535
+ authored,
536
+ }));
537
+ return keyed
538
+ .sort((a, b) => b.intent - a.intent ||
539
+ b.verbMatch - a.verbMatch ||
540
+ b.focused - a.focused ||
541
+ (phrase
542
+ ? b.covered - a.covered ||
543
+ b.phrasePrefix - a.phrasePrefix ||
544
+ b.resourceHead - a.resourceHead ||
545
+ (a.resourceHead && b.resourceHead ? b.readable - a.readable : 0) ||
546
+ a.depth - a.authored - (b.depth - b.authored) ||
547
+ b.phraseCoverage - a.phraseCoverage ||
548
+ b.authored - a.authored
549
+ : 0) ||
550
+ (query.terms[0] === "list" && !resourceOnly ? b.completeAuthored - a.completeAuthored : 0) ||
551
+ (resourceOnly
552
+ ? b.exact - a.exact ||
553
+ a.nested - b.nested ||
554
+ b.namespace - a.namespace ||
555
+ a.depth - b.depth ||
556
+ b.authored - a.authored
557
+ : 0) ||
558
+ a.position - b.position)
559
+ .map(({ op }) => op);
560
+ };
561
+ // Find operations by the request or response properties they declare: a
562
+ // query word must equal a word of a property's name. Rarer words weigh more,
563
+ // and words three in four operations carry (id, status) decide nothing.
564
+ // Reads, then the shallowest best match, lead.
565
+ const words = (text) => text
566
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
567
+ .toLowerCase()
568
+ .split(/[^a-z0-9]+/)
569
+ .filter((word) => word.length >= 2);
570
+ const same = (term, word) => term === word || term === `${word}s` || word === `${term}s`;
571
+ const byField = (query, method, found) => {
572
+ const candidates = index.ops.filter((op) => method === undefined || op.method === method);
573
+ const leaf = (field) => words(field.path.split(".").at(-1));
574
+ // Eligibility and rarity count every declared path, nested ones included; depth only lowers rank.
575
+ const carries = (op, term) => index.fields(op).some((field) => leaf(field).some((word) => same(term, word)));
576
+ const weights = new Map([...new Set(words(query))].flatMap((term) => {
577
+ const share = candidates.filter((op) => carries(op, term)).length;
578
+ return share > 0 && share <= Math.max(1, (candidates.length * 3) / 4)
579
+ ? [[term, Math.log2(1 + candidates.length / share)]]
580
+ : [];
581
+ }));
582
+ if (weights.size === 0)
583
+ return [];
584
+ const order = new Map(index.ops.map((op, position) => [op, position]));
585
+ return candidates
586
+ .filter((op) => !found.has(op))
587
+ .flatMap((op) => {
588
+ const best = index
589
+ .fields(op)
590
+ .map((field) => ({
591
+ field,
592
+ score: [...weights].reduce((sum, [term, weight]) => sum + (leaf(field).some((word) => same(term, word)) ? weight : 0), 0),
593
+ depth: field.path.split(".").length + (field.in === "body" ? 0.5 : 0),
594
+ }))
595
+ .filter(({ score }) => score > 0)
596
+ .sort((a, b) => b.score - a.score || a.depth - b.depth)[0];
597
+ return best ? [{ op, ...best }] : [];
598
+ })
599
+ .sort((a, b) => b.score - a.score ||
600
+ Number(b.op.method === "GET") - Number(a.op.method === "GET") ||
601
+ a.depth - b.depth ||
602
+ a.op.path.split("/").length - b.op.path.split("/").length ||
603
+ order.get(a.op) - order.get(b.op))
604
+ .map(({ op, field }) => ({ op, field }));
605
+ };
606
+ // Nouns an operation's text or declared field names cover.
607
+ const coverage = (op, nouns) => {
608
+ const own = [
609
+ ...words(`${op.summary} ${op.operationId} ${op.path} ${op.tag}`),
610
+ ...index.fields(op).flatMap((field) => words(field.path.split(".").at(-1))),
611
+ ];
612
+ return nouns.filter((term) => own.some((word) => matches(word, term))).length;
613
+ };
614
+ const methodsWith = (wanted) => index.methods.filter((method) => index.ops.some((op) => op.method === method && wanted(verdictOf(op))));
615
+ const readMethods = methodsWith((verdict) => verdict === "read");
616
+ const defaultRead = readMethods.includes("GET") || readMethods.length !== 1 ? "GET" : readMethods[0];
617
+ const writeMethods = methodsWith((verdict) => verdict === "write" || verdict === "destructive");
618
+ if (idempotency &&
619
+ (!idempotency.methods.length || idempotency.methods.some((method) => !writeMethods.includes(method))))
620
+ throw new Error("Hatch idempotency methods must name at least one admitted write method.");
621
+ const Method = methodEnum(index.methods);
622
+ const Path = text(2, 2048).annotate({
623
+ description: `Concrete path from search with ids filled in${options.pathExample ? `, e.g. ${options.pathExample}` : ""}. No query string.`,
624
+ });
625
+ const Query = ObjectValue.annotate({ description: "Query parameters as JSON, named as details lists them." });
626
+ const Routing = {
627
+ tool: Schema.optionalKey(Schema.String.annotate({ description: "The tool that calls it; absent when refused." })),
628
+ named: Schema.optionalKey(Schema.String.annotate({
629
+ description: "A named tool serving this operation with a projected result; prefer it.",
630
+ })),
631
+ };
632
+ const Matched = Schema.optionalKey(Schema.String.annotate({
633
+ description: "The request or response field this operation was found by; call its tool to read the field.",
634
+ }));
635
+ const RefusalReason = Schema.optionalKey(Schema.String.annotate({
636
+ description: "Reviewed refusal reason, separate from the pinned summary. Cut text is marked in truncated; details serves the complete text, paged if needed.",
637
+ }));
638
+ const searchTool = tool("search");
639
+ const detailsTool = tool("details");
640
+ const tools = [
641
+ read(connection, {
642
+ name: searchTool,
643
+ description: `Find ${title} API operations by keyword or path. Returns up to ${SEARCH_BUDGET.maxHits} compact matches with routing and separate refusal reasons; reads no account data.`,
644
+ input: Schema.Struct({
645
+ query: text(1, 200).annotate({ description: "Keywords or a path, e.g. refund charge." }),
646
+ method: Schema.optionalKey(Method.annotate({ description: "Only operations with this method." })),
647
+ limit: Schema.optionalKey(Integer.check(Schema.isBetween({ minimum: 1, maximum: SEARCH_BUDGET.maxHits })).annotate({
648
+ description: "Most operations to return.",
649
+ default: SEARCH_BUDGET.hits,
650
+ })),
651
+ cursor: Schema.optionalKey(text(1, 512).annotate({ description: "page.nextCursor from the previous search with the same query." })),
652
+ }),
653
+ output: Schema.Struct({
654
+ operations: Schema.Array(Schema.Struct({
655
+ method: Method,
656
+ path: Schema.String,
657
+ operationId: Schema.String,
658
+ summary: Schema.String,
659
+ refusal: Schema.optionalKey(text(1, SEARCH_BUDGET.refusalChars).annotate({
660
+ description: "Reviewed refusal, capped separately from summary. A cut reason is marked in truncated; details returns it whole.",
661
+ })),
662
+ ...Routing,
663
+ matched: Matched,
664
+ })),
665
+ revision: Schema.String.annotate({ description: "Operation index revision." }),
666
+ truncated: Schema.optionalKey(Schema.Array(Schema.String).annotate({
667
+ description: `Entry labels cut for size; ${detailsTool} on that method and path returns them whole.`,
668
+ })),
669
+ page: StandardPage,
670
+ }),
671
+ run: (args) => Effect.gen(function* () {
672
+ const scope = {
673
+ discoveryFormat: "summary-refusal-1",
674
+ ...(options.binding?.cursorScope === undefined ? {} : { deploymentPolicy: options.binding.cursorScope }),
675
+ revision: yield* Effect.promise(() => index.cursorRevision()),
676
+ query: args.query,
677
+ ...(args.method ? { method: args.method } : {}),
678
+ };
679
+ const continuation = args.cursor === undefined ? undefined : yield* boundCursor.decode(searchTool, scope, args.cursor);
680
+ const offset = continuation === undefined ? 0 : Number(continuation);
681
+ if (!Number.isSafeInteger(offset) || (continuation !== undefined && !/^[1-9][0-9]*$/.test(continuation)))
682
+ return yield* Effect.fail(new Refusal({
683
+ error: new ConnectorCallError("invalid_args", "This search cursor is not one search returned."),
684
+ }));
685
+ const pathQuery = args.query.trim().startsWith("/");
686
+ const query = queryTerms(args.query);
687
+ const primary = rank(yield* checked(() => index.search(pathQuery
688
+ ? args.query
689
+ : [...query.terms, ...(query.suppliedValue ? (query.verbWords ?? []) : [])].join(" "), {
690
+ ...scope,
691
+ limit: index.ops.length,
692
+ partial: !pathQuery && query.terms.length > 0,
693
+ })), query, pathQuery);
694
+ // Operations found only by a field they return or take rank after every path, id or summary match.
695
+ const ranked = [
696
+ ...primary.map((op) => ({ op })),
697
+ ...byField(pathQuery ? args.query : (query.nouns.length ? query.nouns : query.terms).join(" "), args.method, new Set(primary)).filter(({ op }) => pathQuery ||
698
+ query.intent === undefined ||
699
+ coverage(op, query.nouns) >= Math.ceil(query.nouns.length / 2)),
700
+ ];
701
+ const reachable = ranked.filter(({ op }) => [...op.path].length <= 2048).length;
702
+ if (continuation !== undefined && offset >= reachable)
703
+ return yield* Effect.fail(new Refusal({
704
+ error: new ConnectorCallError("invalid_args", "This search cursor is not one search returned."),
705
+ }));
706
+ // Search entries are compact: long labels are cut (details serves them whole), and a
707
+ // path the read and write tools cannot take (over 2,048 characters) is never routed.
708
+ const truncated = [];
709
+ const operations = ranked
710
+ .filter(({ op }) => [...op.path].length <= 2048)
711
+ .slice(offset, offset + (args.limit ?? SEARCH_BUDGET.hits))
712
+ .map(({ op, field }, position) => {
713
+ const { named: authored, ...routed } = route(op);
714
+ const refusal = discoveryRefusal(op);
715
+ const label = (key, text, max = SEARCH_BUDGET.summaryChars) => {
716
+ if (text.length > max)
717
+ truncated.push(`operations[${position}].${key}`);
718
+ return cap(text, max);
719
+ };
720
+ return {
721
+ method: op.method,
722
+ path: op.path,
723
+ operationId: label("operationId", op.operationId),
724
+ summary: label("summary", op.summary),
725
+ ...(refusal ? { refusal: label("refusal", refusal, SEARCH_BUDGET.refusalChars) } : {}),
726
+ ...routed,
727
+ // A named tool's projection may omit the matched field, so a field match points at the hatch only.
728
+ ...(field
729
+ ? { matched: label("matched", `${field.in} field ${field.path}`) }
730
+ : authored
731
+ ? { named: authored }
732
+ : {}),
733
+ };
734
+ });
735
+ // The budget holds for the whole answer, page and cursor included, before it leaves the tool;
736
+ // what it cuts stays reachable by cursor.
737
+ const answer = function* () {
738
+ const next = offset + operations.length;
739
+ const shown = truncated.filter((path) => Number(/\[(\d+)\]/.exec(path)[1]) < operations.length);
740
+ return {
741
+ operations,
742
+ revision: index.revision,
743
+ ...(shown.length ? { truncated: shown } : {}),
744
+ page: next < reachable
745
+ ? { hasMore: true, nextCursor: yield* boundCursor.encode(searchTool, scope, String(next)) }
746
+ : { hasMore: false },
747
+ };
748
+ };
749
+ let result = yield* answer();
750
+ while (operations.length > 1 && bytes(result) > SEARCH_BUDGET.bytes) {
751
+ operations.pop();
752
+ result = yield* answer();
753
+ }
754
+ return result;
755
+ }),
756
+ }),
757
+ read(connection, {
758
+ name: detailsTool,
759
+ description: `Read one ${title} API operation's request contract and refusal, including methods outside the call schemas. Reads no account data.`,
760
+ input: Schema.Struct({
761
+ method: Method.annotate({ description: "The operation's HTTP method." }),
762
+ path: Path.annotate({ description: "The path template from search, or a concrete path." }),
763
+ param: Schema.optionalKey(text(1, 200).annotate({ description: "Return only this parameter, in full." })),
764
+ cursor: Schema.optionalKey(text(1, 1024).annotate({
765
+ description: "page.nextCursor from the previous details call with the same method, path and param.",
766
+ })),
767
+ }),
768
+ output: Schema.Struct({
769
+ method: Method,
770
+ path: Schema.String,
771
+ operationId: Schema.String,
772
+ summary: Schema.String,
773
+ refusal: RefusalReason,
774
+ server: Schema.optionalKey(Schema.String),
775
+ parameters: Schema.Array(Schema.Unknown).annotate({ description: "Path and query parameters." }),
776
+ body: Schema.optionalKey(ObjectValue.annotate({
777
+ description: "Body content type and schema. When paged: this page's properties, the required names it delivers or chunks, and the schema's other keywords on the page that carries them.",
778
+ })),
779
+ verdict: Schema.Literals(["read", "write", "destructive", "refuse"]),
780
+ ...Routing,
781
+ revision: Schema.String,
782
+ chunk: Schema.optionalKey(Schema.Struct({
783
+ of: Schema.String.annotate({ description: "The piece this text belongs to, such as parameters.limit." }),
784
+ text: Schema.String.annotate({
785
+ description: "Part of that piece's JSON; join the parts in page order, then parse them.",
786
+ }),
787
+ })),
788
+ truncated: Schema.optionalKey(Schema.Array(Schema.String).annotate({
789
+ description: "Labels cut for size; their full text arrives as chunks.",
790
+ })),
791
+ page: Schema.optionalKey(StandardPage.annotate({
792
+ description: "Present when the contract exceeds one answer: pass nextCursor back for the next pieces.",
793
+ })),
794
+ }),
795
+ run: (args) => Effect.gen(function* () {
796
+ const { op, contract } = yield* checked(() => {
797
+ const op = index.operation(args.method, args.path) ?? index.resolve(args.method, args.path).op;
798
+ const refusal = discoveryRefusal(op);
799
+ return { op, contract: { ...index.contract(op, args.param), ...(refusal ? { refusal } : {}) } };
800
+ });
801
+ const tail = { verdict: discoveryVerdictOf(op), ...route(op), revision: index.revision };
802
+ const whole = { ...contract, ...tail };
803
+ if (args.cursor === undefined && bytes(whole) <= DETAILS_BUDGET_BYTES)
804
+ return whole;
805
+ // Bounded pages: every parameter and body property arrives whole on some page, and a
806
+ // piece too large for one page arrives as consecutive chunks of its JSON. Nothing is dropped.
807
+ const scope = {
808
+ discoveryFormat: "summary-refusal-1",
809
+ ...(options.binding?.cursorScope === undefined ? {} : { deploymentPolicy: options.binding.cursorScope }),
810
+ revision: yield* Effect.promise(() => index.cursorRevision()),
811
+ method: args.method,
812
+ path: args.path,
813
+ ...(args.param ? { param: args.param } : {}),
814
+ };
815
+ let at = { i: 0, o: 0 };
816
+ if (args.cursor !== undefined) {
817
+ const decoded = yield* boundCursor.decode(detailsTool, scope, args.cursor);
818
+ const parsed = yield* Effect.try({
819
+ try: () => JSON.parse(decoded),
820
+ catch: () => undefined,
821
+ }).pipe(Effect.orElseSucceed(() => undefined));
822
+ at = parsed;
823
+ }
824
+ const capped = {
825
+ operationId: contract.operationId.length > SEARCH_BUDGET.summaryChars,
826
+ summary: contract.summary.length > SEARCH_BUDGET.summaryChars,
827
+ refusal: (contract.refusal?.length ?? 0) > SEARCH_BUDGET.refusalChars,
828
+ };
829
+ const pieces = piecesOf(contract, capped);
830
+ if (!Number.isSafeInteger(at?.i) ||
831
+ !Number.isSafeInteger(at?.o) ||
832
+ at.i < 0 ||
833
+ at.o < 0 ||
834
+ at.i >= pieces.length ||
835
+ (args.cursor !== undefined && at.i === 0 && at.o === 0) ||
836
+ (at.o > 0 &&
837
+ (() => {
838
+ const json = JSON.stringify(pieces[at.i].value);
839
+ return at.o >= json.length || /[\uD800-\uDBFF]/.test(json[at.o - 1]);
840
+ })()))
841
+ return yield* Effect.fail(new Refusal({
842
+ error: new ConnectorCallError("invalid_args", "This details cursor is not one details returned."),
843
+ }));
844
+ const base = {
845
+ method: contract.method,
846
+ path: contract.path,
847
+ operationId: cap(contract.operationId, SEARCH_BUDGET.summaryChars),
848
+ summary: cap(contract.summary, SEARCH_BUDGET.summaryChars),
849
+ ...(contract.refusal ? { refusal: cap(contract.refusal, SEARCH_BUDGET.refusalChars) } : {}),
850
+ ...(contract.server ? { server: contract.server } : {}),
851
+ };
852
+ const truncated = ["operationId", "summary", "refusal"].filter((key) => capped[key]);
853
+ const parameters = [];
854
+ const properties = {};
855
+ const required = [];
856
+ let frame;
857
+ let body;
858
+ let chunk;
859
+ // A paged object body carries the frame keywords on the page that delivers them,
860
+ // this page's properties, and the required names of the properties it delivers or chunks.
861
+ const compose = (page) => ({
862
+ ...base,
863
+ parameters,
864
+ ...(contract.body === undefined
865
+ ? {}
866
+ : {
867
+ body: {
868
+ contentType: contract.body.contentType,
869
+ ...(contract.body.required ? { required: true } : {}),
870
+ schema: body ?? {
871
+ ...frame,
872
+ properties,
873
+ ...(required.length ? { required } : {}),
874
+ },
875
+ },
876
+ }),
877
+ ...tail,
878
+ ...(chunk ? { chunk } : {}),
879
+ ...(truncated.length ? { truncated } : {}),
880
+ page,
881
+ });
882
+ // Room for the largest cursor this contract can need.
883
+ const reserve = {
884
+ hasMore: true,
885
+ nextCursor: yield* boundCursor.encode(detailsTool, scope, `{"i":${pieces.length},"o":${Number.MAX_SAFE_INTEGER}}`),
886
+ };
887
+ const fits = () => bytes(compose(reserve)) <= DETAILS_BUDGET_BYTES;
888
+ let { i, o } = at;
889
+ while (i < pieces.length) {
890
+ const piece = pieces[i];
891
+ if (o === 0 && piece.kind !== "text") {
892
+ if (piece.kind === "parameter")
893
+ parameters.push(piece.value);
894
+ else if (piece.kind === "body")
895
+ body = piece.value;
896
+ else if (piece.kind === "frame")
897
+ frame = piece.value;
898
+ else {
899
+ properties[piece.name] = piece.value;
900
+ if (piece.required)
901
+ required.push(piece.name);
902
+ }
903
+ if (fits()) {
904
+ i += 1;
905
+ continue;
906
+ }
907
+ if (piece.kind === "parameter")
908
+ parameters.pop();
909
+ else if (piece.kind === "body")
910
+ body = undefined;
911
+ else if (piece.kind === "frame")
912
+ frame = undefined;
913
+ else {
914
+ delete properties[piece.name];
915
+ if (piece.required)
916
+ required.pop();
917
+ }
918
+ }
919
+ // A piece that cannot share a page arrives alone, in as many chunks as it needs; a
920
+ // chunked required property keeps its name in this page's required list.
921
+ if (parameters.length === 0 &&
922
+ Object.keys(properties).length === 0 &&
923
+ body === undefined &&
924
+ frame === undefined) {
925
+ const json = JSON.stringify(piece.value);
926
+ if (piece.kind === "property" && piece.required)
927
+ required.push(piece.name);
928
+ chunk = { of: piece.of, text: "" };
929
+ let low = o + 1;
930
+ let high = json.length;
931
+ let end = Math.min(o + 1, json.length);
932
+ while (low <= high) {
933
+ const middle = Math.floor((low + high) / 2);
934
+ chunk.text = json.slice(o, middle);
935
+ if (fits()) {
936
+ end = middle;
937
+ low = middle + 1;
938
+ }
939
+ else
940
+ high = middle - 1;
941
+ }
942
+ // Never split a surrogate pair across chunks.
943
+ if (end < json.length && end > o + 1 && /[\uD800-\uDBFF]/.test(json[end - 1]))
944
+ end -= 1;
945
+ chunk.text = json.slice(o, end);
946
+ o = end;
947
+ if (o >= json.length) {
948
+ i += 1;
949
+ o = 0;
950
+ }
951
+ }
952
+ break;
953
+ }
954
+ return compose(i < pieces.length
955
+ ? { hasMore: true, nextCursor: yield* boundCursor.encode(detailsTool, scope, JSON.stringify({ i, o })) }
956
+ : { hasMore: false });
957
+ }),
958
+ }),
959
+ ];
960
+ const run = (kind, args) => Effect.gen(function* () {
961
+ yield* checked(() => {
962
+ refuseWithheldValues({ path: args.path, query: args.query }, name, true);
963
+ refuseWithheldValues(args.body, name);
964
+ });
965
+ const method = args.method ?? defaultRead;
966
+ const path = yield* checked(() => options.binding?.path?.(args.path) ?? args.path);
967
+ yield* checked(() => refuseWithheldValues({ path }, name, true));
968
+ const { op, params } = yield* checked(() => index.resolve(method, path));
969
+ const call = () => ({
970
+ method,
971
+ path,
972
+ op,
973
+ params,
974
+ query,
975
+ body,
976
+ ...(args.headers ? { headers: args.headers } : {}),
977
+ });
978
+ let query = args.query ?? {};
979
+ let body = yield* checked(() => options.binding?.prepareBody ? options.binding.prepareBody(args.body, op) : args.body);
980
+ if (options.binding?.query)
981
+ query = yield* checked(() => options.binding.query(call()));
982
+ yield* checked(() => {
983
+ const verdict = verdictOf(op);
984
+ if (verdict === "refuse") {
985
+ invalid(refusalOf(op) ??
986
+ options.binding?.refuse?.(call()) ??
987
+ `Connecta refuses ${method} ${op.path}: it has no admitted hatch verdict.`);
988
+ }
989
+ if ((verdict === "read") !== (kind === "read")) {
990
+ invalid(`${method} ${op.path} must be called with ${tool(verdict === "read" ? "read" : "write")}.`);
991
+ }
992
+ });
993
+ const adapters = pages.get(`${method} ${op.path}`) ?? [];
994
+ let adapter = adapters[0];
995
+ const at = (entry) => entry.in === "query" ? query[entry.param] : record(body) ? body[entry.param] : undefined;
996
+ const supplied = adapters.filter((entry) => at(entry) !== undefined);
997
+ const separateCursor = options.pageCursor === "argument";
998
+ yield* checked(() => {
999
+ if (supplied.length > 1)
1000
+ invalid("Choose one paging direction; do not combine cursor parameters.");
1001
+ if (args.cursor !== undefined && !adapter)
1002
+ invalid("This operation does not page; omit cursor.");
1003
+ if (args.cursor !== undefined && supplied.length)
1004
+ invalid("Pass cursor without a raw paging anchor.");
1005
+ if (separateCursor && supplied.some((entry) => entry.initialAnchor && signedCursorShape(at(entry))))
1006
+ invalid("Pass the signed paging cursor as top-level cursor, not as a raw query anchor.");
1007
+ });
1008
+ const withoutCursors = (values, location) => Object.fromEntries(Object.entries(values).filter(([key]) => !adapters.some((entry) => entry.in === location && entry.param === key)));
1009
+ const scope = adapter
1010
+ ? {
1011
+ ...(options.binding?.cursorScope === undefined ? {} : { deploymentPolicy: options.binding.cursorScope }),
1012
+ revision: yield* Effect.promise(() => index.cursorRevision()),
1013
+ method,
1014
+ path,
1015
+ query: withoutCursors(query, "query"),
1016
+ ...(adapters.some((entry) => entry.in === "body")
1017
+ ? { body: withoutCursors(record(body) ? body : {}, "body") }
1018
+ : body === undefined
1019
+ ? {}
1020
+ : { body }),
1021
+ ...(args.fields ? { fields: args.fields } : {}),
1022
+ ...(args.headers ? { headers: args.headers } : {}),
1023
+ }
1024
+ : {};
1025
+ if (supplied[0])
1026
+ adapter = supplied[0];
1027
+ const value = separateCursor ? args.cursor : adapter && at(adapter);
1028
+ // Timestamp pagination can also carry a first-page time-window anchor.
1029
+ // Only an explicit adapter opt-in admits a plain numeric anchor; opaque
1030
+ // string continuations still require a signed token.
1031
+ const numericAnchor = !separateCursor &&
1032
+ adapter?.initialAnchor &&
1033
+ (typeof value === "number" || (typeof value === "string" && /^\d+$/.test(value)));
1034
+ if (adapter && numericAnchor) {
1035
+ const restored = yield* checked(() => adapter.decode(value));
1036
+ if (adapter.in === "query")
1037
+ query = { ...query, [adapter.param]: restored };
1038
+ else
1039
+ body = { ...(record(body) ? body : {}), [adapter.param]: restored };
1040
+ }
1041
+ else if (adapter && value !== undefined) {
1042
+ if (adapter.cursorInputMessage && !signedCursorShape(value))
1043
+ yield* checked(() => invalid(adapter.cursorInputMessage));
1044
+ if (typeof value !== "string")
1045
+ yield* checked(() => invalid("Pass the opaque page.nextCursor unchanged."));
1046
+ const cursor = yield* boundCursor.decode(tool(kind), scope, value);
1047
+ const restored = yield* checked(() => {
1048
+ let token;
1049
+ try {
1050
+ token = JSON.parse(cursor);
1051
+ }
1052
+ catch {
1053
+ invalid("This paging cursor contains no valid vendor value.");
1054
+ }
1055
+ if (separateCursor) {
1056
+ if (!record(token))
1057
+ invalid("This paging cursor contains no valid vendor parameter.");
1058
+ const payload = token;
1059
+ const selected = adapters.find((entry) => entry.param === payload["param"] && entry.in === payload["in"]);
1060
+ if (!selected)
1061
+ invalid("This paging cursor belongs to a different vendor parameter.");
1062
+ adapter = selected;
1063
+ token = token["value"];
1064
+ }
1065
+ if ((typeof token !== "string" && typeof token !== "number") || withheldCursor(token)) {
1066
+ invalid("This paging cursor contains no usable vendor value; withheld values cannot be sent.");
1067
+ }
1068
+ const decoded = adapter.decode(token);
1069
+ if (withheldCursor(decoded))
1070
+ invalid("This paging cursor contains a withheld vendor value.");
1071
+ return decoded;
1072
+ });
1073
+ if (adapter.in === "query")
1074
+ query = { ...query, [adapter.param]: restored };
1075
+ else
1076
+ body = { ...(record(body) ? body : {}), [adapter.param]: restored };
1077
+ }
1078
+ else if (separateCursor && supplied.length) {
1079
+ yield* checked(() => {
1080
+ if (!adapter?.initialAnchor)
1081
+ invalid(adapter?.cursorInputMessage ?? "This vendor continuation is accepted only as an opaque cursor.");
1082
+ });
1083
+ }
1084
+ if (adapter?.prepareQuery)
1085
+ query = yield* checked(() => adapter.prepareQuery(query));
1086
+ const request = yield* checked(() => {
1087
+ refuseWithheldValues({ path, query }, name, true);
1088
+ refuseWithheldValues(body, name);
1089
+ const refusal = options.binding?.refuse?.(call());
1090
+ if (refusal)
1091
+ invalid(refusal);
1092
+ const contentType = index.bodyType(op)?.split(";", 1)[0].trim().toLowerCase();
1093
+ if (op.server && options.binding?.allowOtherServers !== true)
1094
+ invalid(`${method} ${op.path} uses a different server; this connection cannot send it.`);
1095
+ if (options.binding?.allowOtherBodyTypes !== true &&
1096
+ contentType &&
1097
+ contentType !== "application/x-www-form-urlencoded" &&
1098
+ !/^application\/(?:[^/]+\+)?json$/.test(contentType)) {
1099
+ invalid(`${method} ${op.path} takes ${contentType}; this connection's path binding supports only JSON and form bodies.`);
1100
+ }
1101
+ if ((method === "GET" || method === "HEAD") && body !== undefined)
1102
+ invalid(`A ${method} operation takes query, not body.`);
1103
+ index.check(op, query, body);
1104
+ if (args.fields?.length) {
1105
+ const roots = index.responseRoots(op);
1106
+ if (!roots)
1107
+ invalid(`${method} ${op.path} declares no response properties, so fields cannot narrow it.`);
1108
+ const problem = fieldsProblem(args.fields, roots, "data");
1109
+ if (problem) {
1110
+ if (options.fieldsRoot && roots.has(options.fieldsRoot))
1111
+ invalid(`${problem.message} Use fields:["${options.fieldsRoot}.id"] or fields:["${options.fieldsRoot}[].id"] for declared record or list fields.`);
1112
+ throw problem;
1113
+ }
1114
+ }
1115
+ if (options.binding)
1116
+ return undefined;
1117
+ return {
1118
+ method,
1119
+ path,
1120
+ template: op.path,
1121
+ query: parameters(query),
1122
+ ...(args.headers ? { headers: args.headers } : {}),
1123
+ ...(body === undefined
1124
+ ? {}
1125
+ : contentType === "application/x-www-form-urlencoded"
1126
+ ? { form: parameters(record(body) ? body : invalid("A form body must be an object.")) }
1127
+ : { json: JSON.stringify(body) }),
1128
+ };
1129
+ });
1130
+ const key = kind === "write" ? (yield* IdempotencyKey)?.key : undefined;
1131
+ const sendOptions = adapter?.headers ? { responseHeaders: adapter.headers } : undefined;
1132
+ const send = options.binding
1133
+ ? options.binding.send(call(), sendOptions)
1134
+ : connection.sendPath(request, sendOptions);
1135
+ const answer = yield* send.pipe(Effect.catch((failure) => Effect.gen(function* () {
1136
+ if (!key || args.idempotencyKey)
1137
+ return yield* Effect.fail(failure);
1138
+ const dispatches = yield* Dispatches;
1139
+ const error = toConnectorCallError(failure, connection.errors, {
1140
+ classification: yield* Classification,
1141
+ dispatches,
1142
+ withholds: connection.safety.withholdsErrors,
1143
+ signal: yield* Signal,
1144
+ });
1145
+ const diagnostic = providerFailureMessage(error, `${error.message} ${idempotency.header}: ${key}.`);
1146
+ return yield* Effect.fail(new Refusal({
1147
+ error: dispatches.some((d) => d.request?.dispatched && d.request.key === key)
1148
+ ? keyResendAdvice(diagnostic, key, " Reusing it with the exact original arguments retries this write without repeating it only while the vendor retains the key; after that, look the object up before retrying.")
1149
+ : diagnostic,
1150
+ }));
1151
+ })));
1152
+ const next = adapter
1153
+ ? yield* checked(() => adapter.next(answer.data, { path, query, headers: answer.headers ?? {} }))
1154
+ : undefined;
1155
+ let page;
1156
+ if (adapter && next !== undefined) {
1157
+ const total = adapter.total ? yield* checked(() => adapter.total(answer.data)) : undefined;
1158
+ const secrets = yield* RequestSecrets;
1159
+ if (next !== null &&
1160
+ typeof next !== "object" &&
1161
+ (withheldCursor(next) || secrets.redact(String(next)) !== String(next))) {
1162
+ yield* checked(() => {
1163
+ throw new ConnectorCallError("connector_call_failed", "The vendor returned a withheld paging cursor.", {
1164
+ retryable: false,
1165
+ });
1166
+ });
1167
+ }
1168
+ page = {
1169
+ hasMore: next !== null,
1170
+ ...(typeof total === "number" && Number.isSafeInteger(total) && total >= 0 ? { total } : {}),
1171
+ ...(next === null || typeof next === "object"
1172
+ ? {}
1173
+ : {
1174
+ nextCursor: yield* boundCursor.encode(tool(kind), scope, JSON.stringify(separateCursor ? { param: adapter.param, in: adapter.in, value: next } : next), (json) => secrets.encode(new TextEncoder().encode(json), { url: true, claims: true })),
1175
+ }),
1176
+ param: adapter.param,
1177
+ in: adapter.in,
1178
+ };
1179
+ }
1180
+ return {
1181
+ status: answer.status,
1182
+ data: selectFields(answer.data === undefined ? null : answer.data, args.fields),
1183
+ ...(page ? { page } : {}),
1184
+ ...(options.binding?.truncated && "truncated" in answer && answer.truncated?.length
1185
+ ? { truncated: answer.truncated }
1186
+ : {}),
1187
+ ...(options.binding?.warnings && "warnings" in answer && answer.warnings?.length
1188
+ ? { warnings: answer.warnings }
1189
+ : {}),
1190
+ ...(key ? { idempotencyKey: key } : {}),
1191
+ };
1192
+ });
1193
+ const note = (sentence) => (sentence ? ` ${sentence}` : "");
1194
+ const nonGetReads = readMethods.filter((method) => method !== "GET" && method !== "HEAD");
1195
+ // Field order follows the REST hatch: path, method, body, query, then fields.
1196
+ const readFields = { path: Path };
1197
+ // `method` is omitted only when GET is the one reviewed read method; a sole other method is the
1198
+ // default, and several non-GET read methods must be named.
1199
+ if (readMethods.length > 0 && !(readMethods.length === 1 && readMethods[0] === "GET")) {
1200
+ const methods = methodEnum(readMethods);
1201
+ readFields["method"] =
1202
+ readMethods.includes("GET") || readMethods.length === 1
1203
+ ? Schema.optionalKey(methods.annotate({
1204
+ description: `${defaultRead} by default; another method only for an operation search routes here.`,
1205
+ }))
1206
+ : methods.annotate({ description: "The reviewed read method of the operation search routes here." });
1207
+ }
1208
+ if (nonGetReads.length)
1209
+ readFields["body"] = Schema.optionalKey((Body ?? ObjectValue).annotate({ description: "Body for a reviewed read-only POST." }));
1210
+ readFields["query"] = Schema.optionalKey(Query);
1211
+ readFields["fields"] = Schema.optionalKey(Fields);
1212
+ if (Headers)
1213
+ readFields["headers"] = Schema.optionalKey(Headers);
1214
+ if (options.pageCursor === "argument")
1215
+ readFields["cursor"] = Schema.optionalKey(text(1, 4096).annotate({
1216
+ description: "Opaque page.nextCursor from this same read; omit raw query anchors when continuing.",
1217
+ }));
1218
+ const readSpec = {
1219
+ ...(options.writeSteps ? { writeSteps: options.writeSteps } : {}),
1220
+ name: tool("read"),
1221
+ description: `Call a reviewed ${title} API read${nonGetReads.length ? `, including reviewed read-only ${nonGetReads.join(" and ")}` : ""}. The operation index checks arguments before anything is sent.${note(options.notes?.read)}`,
1222
+ input: Schema.Struct(readFields),
1223
+ output: envelope,
1224
+ // The explicit per-operation read table replaces REST readPosts. The
1225
+ // builder's review permits its non-GET reads; run checks the exact row.
1226
+ review: "Only operations explicitly classified read in the hatch's snapshotted verdict table are admitted.",
1227
+ };
1228
+ // Let the Schema infer the program arguments rather than the shared field declarations.
1229
+ tools.push(read(connection, { ...readSpec, run: (args) => run("read", args) }));
1230
+ if (writeMethods.length > 0 && options.writeEnabled !== false) {
1231
+ const writeFields = {
1232
+ method: methodEnum(writeMethods).annotate({ description: "The operation's HTTP method." }),
1233
+ path: Path,
1234
+ query: Schema.optionalKey(Query),
1235
+ body: Schema.optionalKey((Body ?? Schema.Union([ObjectValue, Schema.Array(Schema.Unknown)])).annotate({
1236
+ description: "Request body as JSON fields, named as details lists them; form operations are encoded for you.",
1237
+ })),
1238
+ fields: Schema.optionalKey(Fields),
1239
+ };
1240
+ if (Headers)
1241
+ writeFields["headers"] = Schema.optionalKey(Headers);
1242
+ if (idempotency)
1243
+ writeFields["idempotencyKey"] = Schema.optionalKey(text(1, IDEMPOTENCY_KEY_MAX_LENGTH).annotate({
1244
+ description: "Reuse this key with the exact original arguments; a key is generated and returned when omitted for a supported method.",
1245
+ }));
1246
+ tools.push(write(connection, {
1247
+ ...(options.writeSteps ? { writeSteps: options.writeSteps } : {}),
1248
+ name: tool("write"),
1249
+ description: `Call a reviewed ${title} API write; method and path are stated for approval, and the operation index checks arguments first.${note(options.notes?.write)}`,
1250
+ input: Schema.Struct(writeFields),
1251
+ output: idempotency
1252
+ ? Schema.Struct({ ...callEnvelope.fields, idempotencyKey: Schema.optionalKey(Schema.String) })
1253
+ : callEnvelope,
1254
+ ...(idempotency
1255
+ ? {
1256
+ idempotency: {
1257
+ header: idempotency.header,
1258
+ key: (args) => idempotency.methods.includes(args["method"])
1259
+ ? (args["idempotencyKey"] ?? crypto.randomUUID())
1260
+ : undefined,
1261
+ },
1262
+ }
1263
+ : {}),
1264
+ destructive: true,
1265
+ run: (args) => run("write", args),
1266
+ }));
1267
+ }
1268
+ registerOperationVocabulary(tools[0], function* () {
1269
+ for (const op of index.ops) {
1270
+ const verdict = discoveryVerdictOf(op);
1271
+ yield {
1272
+ text: `${op.path.slice(0, 384)} ${op.operationId.slice(0, 128)} ${op.summary.slice(0, 384)} ${op.tag.slice(0, 128)}`,
1273
+ route: verdict === "refuse" ? searchTool : route(op).tool,
1274
+ write: verdict !== "read",
1275
+ };
1276
+ }
1277
+ });
1278
+ return tools;
1279
+ }