@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
@@ -1,3 +1,5 @@
1
+ import { canonicalJson } from "../../../canonical-json.js";
2
+ import { parseIndexDetails } from "./compact-index.js";
1
3
  // The pinned operation index a REST connector validates against before it
2
4
  // sends anything. `scripts/generate-openapi.mjs` compiles a vendor's OpenAPI
3
5
  // document into `OpenApiData` (search rows plus request-side details as one
@@ -114,16 +116,26 @@ export class OperationIndex {
114
116
  #vendor;
115
117
  #segments;
116
118
  #slashParams;
119
+ #uriTemplates;
120
+ get pathMatching() {
121
+ return this.#uriTemplates ? "uri-template" : "segments";
122
+ }
117
123
  #details;
124
+ #fields;
125
+ #requestSchemas;
118
126
  #words;
119
127
  /**
120
128
  * `vendor` is the tool-name prefix; `title` the display name in refusals.
121
129
  * `slashParams` names path parameters whose values may carry an encoded
122
130
  * "/" (`%2F`), such as an object key, and only as the path's last segment.
131
+ * `pathMatching: "uri-template"` opts into decoded literals and parameters
132
+ * embedded in a segment, such as Google's `{id}:batchUpdate`. The default
133
+ * retains exact literal segments and whole-segment parameters.
123
134
  */
124
135
  constructor(data, names) {
125
136
  const { title } = names;
126
137
  this.#slashParams = new Set(names.slashParams ?? []);
138
+ this.#uriTemplates = names.pathMatching === "uri-template";
127
139
  this.#data = data;
128
140
  this.#title = title;
129
141
  this.#vendor = names.vendor;
@@ -145,6 +157,46 @@ export class OperationIndex {
145
157
  this.#segments = this.ops.map((op) => op.path.slice(1).split("/"));
146
158
  this.methods = [...METHODS].filter((method) => this.ops.some((op) => op.method === method));
147
159
  }
160
+ /** Content binding for discovery continuations; labels alone never identify an index. */
161
+ async cursorRevision() {
162
+ const bytes = new TextEncoder().encode(canonicalJson([
163
+ this.#data,
164
+ this.revision,
165
+ [...this.#slashParams].sort(),
166
+ ...(this.#uriTemplates ? ["uri-template"] : []),
167
+ ]));
168
+ return Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)), (byte) => byte.toString(16).padStart(2, "0")).join("");
169
+ }
170
+ /** The request and response properties this operation declares; none when the index carries no fields. */
171
+ fields(op) {
172
+ if (!this.#fields) {
173
+ const data = this.#data.fields;
174
+ if (typeof data === "string") {
175
+ // Generated REST indexes share response paths and complete rows. Kit
176
+ // vendor indexes retain their directly declared body/response fields.
177
+ const { p, d, o } = JSON.parse(data);
178
+ const rows = d.map((ids) => ids.map((id) => ({ in: "response", path: p[id] })));
179
+ this.#fields = o.map((id) => rows[id]);
180
+ }
181
+ else
182
+ this.#fields = data ?? [];
183
+ }
184
+ return this.#fields[op.row] ?? [];
185
+ }
186
+ /**
187
+ * The names a result's first dot-path segment may take: the success
188
+ * response's own properties, or its items' properties when it is an array.
189
+ * Undefined when the operation declares no response contract.
190
+ */
191
+ responseRoots(op) {
192
+ const paths = this.fields(op).filter((field) => field.in === "response");
193
+ if (paths.length === 0)
194
+ return undefined;
195
+ return new Set(paths.map((field) => field.path
196
+ .replace(/^\[\]\./, "")
197
+ .split(".")[0]
198
+ .replace(/\[\]$/, "")));
199
+ }
148
200
  /** The operation with this exact method and path template, if any. */
149
201
  operation(method, template) {
150
202
  return this.ops.find((op) => op.method === method && op.path === template);
@@ -165,11 +217,29 @@ export class OperationIndex {
165
217
  return;
166
218
  let score = 0;
167
219
  for (const [index, part] of template.entries()) {
168
- if (part.startsWith("{"))
220
+ if (!this.#uriTemplates) {
221
+ if (part.startsWith("{"))
222
+ continue;
223
+ if (part !== segments[index])
224
+ return;
225
+ score += 1;
169
226
  continue;
170
- if (part !== segments[index])
171
- return;
172
- score += 1;
227
+ }
228
+ const parameter = /^(.*)\{([^{}]+)\}(.*)$/.exec(part);
229
+ const value = decodeURIComponent(segments[index]);
230
+ if (parameter) {
231
+ if (!value.startsWith(parameter[1]) ||
232
+ !value.endsWith(parameter[3]) ||
233
+ value.length <= parameter[1].length + parameter[3].length)
234
+ return;
235
+ if (parameter[1] || parameter[3])
236
+ score += 0.5;
237
+ }
238
+ else {
239
+ if (part !== value)
240
+ return;
241
+ score += 1;
242
+ }
173
243
  }
174
244
  if (score > best.score)
175
245
  best = { score, rows: [row] };
@@ -195,9 +265,34 @@ export class OperationIndex {
195
265
  }
196
266
  const params = {};
197
267
  template.forEach((part, index) => {
198
- if (part.startsWith("{"))
199
- params[part.slice(1, -1)] = segments[index];
268
+ if (!this.#uriTemplates) {
269
+ if (part.startsWith("{"))
270
+ params[part.slice(1, -1)] = segments[index];
271
+ return;
272
+ }
273
+ const parameter = /^(.*)\{([^{}]+)\}(.*)$/.exec(part);
274
+ if (parameter) {
275
+ const raw = segments[index];
276
+ const prefix = parameter[1], suffix = parameter[3];
277
+ params[parameter[2]] =
278
+ raw.startsWith(prefix) && raw.endsWith(suffix)
279
+ ? raw.slice(prefix.length, suffix ? -suffix.length : undefined)
280
+ : encodeURIComponent(decodeURIComponent(raw).slice(prefix.length, suffix ? -suffix.length : undefined));
281
+ }
200
282
  });
283
+ const issues = [];
284
+ const repair = [];
285
+ for (const [name, at, , schema] of this.#row(op)?.[0] ?? []) {
286
+ if (at === "path" && params[name] !== undefined)
287
+ this.#check(schema, decodeURIComponent(params[name]), `/path/${name}`, issues, repair);
288
+ }
289
+ if (issues.length) {
290
+ const shown = issues
291
+ .slice(0, 5)
292
+ .map((issue) => `${issue.path} (${issue.code}: ${issue.expected})`)
293
+ .join("; ");
294
+ invalid(`Not sent: the path does not match ${op.method} ${op.path} in the pinned API index: ${shown}. Substitute real ids matching ${this.#vendorTool("details")}.${issues.some((issue) => issue.expected?.includes("safe integer")) ? " Integer ids use canonical ASCII decimal notation within the safe-integer range." : ""}`, issues.slice(0, MAX_ISSUES));
295
+ }
201
296
  return { op, params };
202
297
  }
203
298
  /** Up to five operations whose paths most resemble a missed one. */
@@ -231,6 +326,9 @@ export class OperationIndex {
231
326
  * the path, summary, operationId, or tag (plural-insensitive, prefixes of
232
327
  * three letters count), and path matches weigh most. A query that starts
233
328
  * with "/" also matches the templates that fit it.
329
+ * Standard hatches opt into partial matches, ordered by matched term count
330
+ * before relevance, including the person/people alias. Legacy callers keep
331
+ * the all-term contract.
234
332
  */
235
333
  search(query, options) {
236
334
  this.#words ??= this.ops.map((op) => ({
@@ -261,6 +359,12 @@ export class OperationIndex {
261
359
  if (set.has(term))
262
360
  return 2;
263
361
  for (const word of set) {
362
+ if (options.partial && ((term === "person" && word === "people") || (term === "people" && word === "person")))
363
+ return 2;
364
+ if (options.partial &&
365
+ ((term.endsWith("y") && word === `${term.slice(0, -1)}ies`) ||
366
+ (word.endsWith("y") && term === `${word.slice(0, -1)}ies`)))
367
+ return 2;
264
368
  if (word === `${term}s` || term === `${word}s` || word === `${term}es` || term === `${word}es`)
265
369
  return 2;
266
370
  if (term.length >= 3 && word.startsWith(term))
@@ -280,12 +384,13 @@ export class OperationIndex {
280
384
  matched += 1;
281
385
  score += path * 3 + summary * 1.5 + id;
282
386
  }
283
- if (!fits.has(op.row) && (terms.length === 0 || matched < terms.length))
387
+ if (!fits.has(op.row) && (terms.length === 0 || matched < (options.partial ? 1 : terms.length)))
284
388
  return [];
285
- return [{ op, score }];
389
+ return [{ op, score, matched }];
286
390
  });
287
391
  return ranked
288
- .sort((a, b) => b.score - a.score ||
392
+ .sort((a, b) => (options.partial ? b.matched - a.matched : 0) ||
393
+ b.score - a.score ||
289
394
  a.op.path.split("/").length - b.op.path.split("/").length ||
290
395
  a.op.path.localeCompare(b.op.path))
291
396
  .slice(0, options.limit)
@@ -302,8 +407,16 @@ export class OperationIndex {
302
407
  const declared = row ? row[0] : [];
303
408
  // A generator may omit plain-string path parameters (`pathParams:
304
409
  // "typed"`); the template still names them, so the contract does too.
305
- const implied = this.#segments[op.row].filter((part) => part.startsWith("{") && !declared.some(([name, at]) => at === "path" && name === part.slice(1, -1)))
306
- .map((part) => [part.slice(1, -1), "path", 1, { t: "string" }]);
410
+ const implied = this.#segments[op.row].flatMap((part) => {
411
+ const name = this.#uriTemplates
412
+ ? /\{([^{}]+)\}/.exec(part)?.[1]
413
+ : part.startsWith("{")
414
+ ? part.slice(1, -1)
415
+ : undefined;
416
+ return name !== undefined && !declared.some(([key, at]) => at === "path" && key === name)
417
+ ? [[name, "path", 1, { t: "string" }]]
418
+ : [];
419
+ });
307
420
  const parameters = [...implied, ...declared]
308
421
  .filter(([name]) => only === undefined || name === only)
309
422
  .map(([name, at, required, schema, description]) => ({
@@ -317,7 +430,22 @@ export class OperationIndex {
317
430
  if (row && row[1] !== 0) {
318
431
  const [contentType, schema] = row[1];
319
432
  const node = this.#deref(schema);
320
- if (only !== undefined && node.p?.[only] !== undefined) {
433
+ const full = this.#requestSchema(op);
434
+ if (full && only === undefined) {
435
+ body = { contentType, ...(row[1][2] === 1 ? { required: true } : {}), schema: full };
436
+ }
437
+ else if (full && only !== undefined && full["properties"]?.[only]) {
438
+ body = {
439
+ contentType,
440
+ schema: {
441
+ type: "object",
442
+ properties: { [only]: full["properties"][only] },
443
+ ...(Array.isArray(full["required"]) && full["required"].includes(only) ? { required: [only] } : {}),
444
+ ...(full["$defs"] ? { $defs: full["$defs"] } : {}),
445
+ },
446
+ };
447
+ }
448
+ else if (only !== undefined && node.p?.[only] !== undefined) {
321
449
  body = {
322
450
  contentType,
323
451
  schema: {
@@ -402,8 +530,40 @@ export class OperationIndex {
402
530
  #vendorTool(kind) {
403
531
  return `${this.#vendor}_api_${kind}`;
404
532
  }
533
+ #requestSchema(op) {
534
+ if (!this.#data.requestSchemas)
535
+ return undefined;
536
+ this.#requestSchemas ??= JSON.parse(this.#data.requestSchemas);
537
+ const graph = this.#requestSchemas;
538
+ const root = graph.roots[op.row];
539
+ if (!root)
540
+ return undefined;
541
+ const definitions = {};
542
+ const visit = (value) => {
543
+ if (!value || typeof value !== "object")
544
+ return;
545
+ if (Array.isArray(value)) {
546
+ for (const child of value)
547
+ visit(child);
548
+ return;
549
+ }
550
+ const object = value;
551
+ if (typeof object["$ref"] === "string" && object["$ref"].startsWith("#/$defs/")) {
552
+ const name = object["$ref"].slice(8);
553
+ if (!Object.hasOwn(definitions, name)) {
554
+ definitions[name] = graph.definitions[name] ?? {};
555
+ visit(definitions[name]);
556
+ }
557
+ }
558
+ for (const child of Object.values(object))
559
+ visit(child);
560
+ };
561
+ visit(root);
562
+ const resolved = typeof root["$ref"] === "string" ? graph.definitions[root["$ref"].slice(8)] : root;
563
+ return { ...resolved, ...(Object.keys(definitions).length ? { $defs: definitions } : {}) };
564
+ }
405
565
  #row(op) {
406
- this.#details ??= JSON.parse(this.#data.details);
566
+ this.#details ??= parseIndexDetails(this.#data);
407
567
  const row = this.#details.o[op.row];
408
568
  return row === 0 ? undefined : row;
409
569
  }
@@ -427,6 +587,12 @@ export class OperationIndex {
427
587
  out["format"] = resolved.f;
428
588
  if (resolved.n === 1)
429
589
  out["nullable"] = true;
590
+ if (resolved.l !== undefined)
591
+ out["minimum"] = resolved.l;
592
+ if (resolved.u !== undefined)
593
+ out["maximum"] = resolved.u;
594
+ if (resolved.deprecated === true)
595
+ out["deprecated"] = true;
430
596
  if (resolved.a)
431
597
  out["anyOf"] = resolved.a.map((branch) => this.#schema(branch));
432
598
  if (resolved.p) {
@@ -446,6 +612,35 @@ export class OperationIndex {
446
612
  if (issues.length >= MAX_ISSUES)
447
613
  return;
448
614
  const schema = this.#deref(node);
615
+ const path = at.startsWith("/path/");
616
+ const types = typesOf(schema);
617
+ if (path && !schema.a && !types.includes("string") && (types.includes("integer") || types.includes("number"))) {
618
+ const integer = types.includes("integer") && !types.includes("number");
619
+ const numeric = typeof value === "string" ? Number(value) : NaN;
620
+ const syntax = integer ? /^(?:0|-?[1-9][0-9]*)$/ : /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/;
621
+ if (typeof value !== "string" ||
622
+ !syntax.test(value) ||
623
+ Object.is(numeric, -0) ||
624
+ !Number.isFinite(numeric) ||
625
+ Math.abs(numeric) > Number.MAX_SAFE_INTEGER ||
626
+ (integer && !Number.isSafeInteger(numeric))) {
627
+ issues.push({
628
+ path: at,
629
+ code: "type",
630
+ expected: integer ? "a canonical ASCII decimal safe integer" : "a canonical ASCII decimal number",
631
+ });
632
+ return;
633
+ }
634
+ value = numeric;
635
+ }
636
+ else if (path && types.length && !types.includes("string") && !schema.a) {
637
+ if (types.includes("boolean") && (value === "true" || value === "false"))
638
+ value = value === "true";
639
+ else {
640
+ issues.push({ path: at, code: "type", expected: types.join(" or ") });
641
+ return;
642
+ }
643
+ }
449
644
  if (schema.x === 1)
450
645
  return;
451
646
  // Conflicting composed constraints (disjoint enums or types) admit nothing,
@@ -454,6 +649,28 @@ export class OperationIndex {
454
649
  issues.push({ path: at, code: "enum", expected: "no value: this field's combined constraints admit none" });
455
650
  return;
456
651
  }
652
+ // Only newly opted-in bounded schemas enforce scalar numeric values;
653
+ // existing indexes retain their previous validation behavior. HTTP query
654
+ // strings may represent numbers, but must match the declared numeric type.
655
+ if (schema.l !== undefined || schema.u !== undefined) {
656
+ if (value === null && schema.n === 1)
657
+ return;
658
+ const integer = typesOf(schema).includes("integer");
659
+ const syntax = integer ? /^-?\d+$/ : /^-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
660
+ const numeric = typeof value === "number"
661
+ ? value
662
+ : at.startsWith("/query/") && typeof value === "string" && syntax.test(value)
663
+ ? Number(value)
664
+ : NaN;
665
+ if (!Number.isFinite(numeric) || (integer && !Number.isSafeInteger(numeric))) {
666
+ issues.push({ path: at, code: "type", expected: integer ? "integer" : "finite number" });
667
+ return;
668
+ }
669
+ if (schema.l !== undefined && numeric < schema.l)
670
+ issues.push({ path: at, code: "minimum", expected: `at least ${schema.l}` });
671
+ if (schema.u !== undefined && numeric > schema.u)
672
+ issues.push({ path: at, code: "maximum", expected: `at most ${schema.u}` });
673
+ }
457
674
  if (value === null || value === "")
458
675
  return;
459
676
  if (schema.a) {
@@ -472,7 +689,6 @@ export class OperationIndex {
472
689
  }
473
690
  return;
474
691
  }
475
- const types = typesOf(schema);
476
692
  if (schema.e && (typeof value === "string" || typeof value === "number" || typeof value === "boolean")) {
477
693
  if (!schema.e.some((option) => option === value || String(option) === String(value))) {
478
694
  issues.push({ path: at, code: "enum", expected: oneOf(schema.e.map(String)) });
@@ -0,0 +1,11 @@
1
+ /** Match a reviewed non-JSON success media type only on its pinned operation. */
2
+ export function responseMedia(media, status, operation, contentType = "") {
3
+ if (status < 200 || status > 299)
4
+ return undefined;
5
+ const type = contentType.split(";", 1)[0].trim().toLowerCase();
6
+ const path = operation.template.replace(/:([^/]+)/g, "{$1}");
7
+ if (!type ||
8
+ !media[`${operation.method} ${path}`]?.some((declared) => declared === type || (declared.endsWith("/*") && type.startsWith(declared.slice(0, -1)))))
9
+ return undefined;
10
+ return /^text\/|\bxml\b|\bcsv\b|\b(?:x-ndjson|jsonl|stream\+json)\b/.test(type) ? "text" : "binary";
11
+ }
@@ -1,6 +1,41 @@
1
+ // Connecta's generic REST connector over one vendor's pinned operation index.
2
+ //
3
+ // `restTools(vendor)` returns four tools named `<vendor>_api_search`,
4
+ // `_api_details`, `_api_read`, and `_api_write`. The first two read the index
5
+ // and send nothing. `_api_read` sends GET, plus the vendor's reviewed
6
+ // read-only POSTs; classification is per tool name, so that rule is enforced
7
+ // here in the handler, not inferred. `_api_write` sends every other method and
8
+ // is always destructive, with `method` and `path` top-level arguments so a
9
+ // host's approval prompt shows them.
10
+ //
11
+ // Every call is matched to an operation and checked against its contract
12
+ // before transport (unknown path → nearest operations; unknown or missing
13
+ // parameters → `validation.issues`), then the vendor's reviewed refusals run,
14
+ // then the vendor frames the request. Every vendor answers in one envelope:
15
+ // `{ status, data, page?: { hasMore, next?, param? } }`, with an optional
16
+ // `select` projection and no default projection. A write is dispatched once
17
+ // (INV-9): nothing here retries it or replays it against another
18
+ // implementation.
19
+ //
20
+ // A vendor adopts this module with configuration only: its transport, its
21
+ // failure mapper, and optional `scope` (defaults and pins), `encode` (body
22
+ // and query framing), `page` (cursor extraction), `refuse` (argument-dependent refusals),
23
+ // `readPosts`, and an idempotency header. Every vendor ships a reviewed
24
+ // value-safety table (`valueSafety`, `value-safety.ts` here): its refusals run
25
+ // in the generic tools before any request, and its redaction runs on every
26
+ // successful body in `callRest`, after `result` and before cursors and
27
+ // `select`. Optional hooks cover the rest: `path` (fill default ids before
28
+ // matching), `admit` (an awaited check in `callRest`, which every tool passes,
29
+ // such as an account pin), `result` (unwrap a vendor envelope), a reviewed
30
+ // `headers` allowlist, and `textBodies`. Named tools reuse `restCall` and
31
+ // `callRest` to share the same validation-free request path.
32
+ import { callClassification } from "../../../call-classification.js";
33
+ import { providerFailureMessage } from "../../../write-outcome.js";
1
34
  import { guardedFetch, } from "../../../connectors/guarded-fetch.js";
2
35
  import { IDEMPOTENCY_KEY_MAX_LENGTH, recordRecovery } from "../../../call-recovery.js";
3
- import { ConnectorCallError, unavailableCallError } from "../../../errors.js";
36
+ import { providerRequests } from "../../../provider-call.js";
37
+ import { ConnectorCallError, keyResendAdvice, unavailableCallError } from "../../../errors.js";
38
+ import { encodeForAgent, redactBeforeCut } from "../../../sent-secrets.js";
4
39
  const READ = { readOnlyHint: true };
5
40
  const DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true };
6
41
  const MAX_SELECT = 50;
@@ -10,6 +45,24 @@ function isRecord(value) {
10
45
  function invalid(message) {
11
46
  throw new ConnectorCallError("invalid_args", message);
12
47
  }
48
+ /** A header value as fetch sends it: leading and trailing HTTP whitespace removed. */
49
+ function sentValue(value) {
50
+ return value.replace(/^[\t\n\r ]+|[\t\n\r ]+$/g, "");
51
+ }
52
+ /**
53
+ * Why fetch would not send `value` as a header value the vendor receives:
54
+ * blank once trimmed, or a byte fetch rejects (CR, LF, NUL, any other control
55
+ * character but tab, DEL, or a code point past Latin-1). Undici refuses the
56
+ * whole request for any of them; workerd at least for CR, LF and NUL.
57
+ */
58
+ function unsendableKey(value) {
59
+ const sent = sentValue(value);
60
+ if (sent === "")
61
+ return "is blank; pass a key with at least one non-whitespace character";
62
+ if (!/^[\t\x20-\x7e\x80-\xff]*$/.test(sent))
63
+ return "contains a character an HTTP header cannot carry (a control character, or one past Latin-1); pass a printable key";
64
+ return undefined;
65
+ }
13
66
  /**
14
67
  * A guarded transport whose requests, response bodies included, end after
15
68
  * `timeoutMs`: a stream the vendor never closes cannot hold a call open past
@@ -67,13 +120,6 @@ async function failureBody(response) {
67
120
  return undefined;
68
121
  }
69
122
  }
70
- function base64(bytes) {
71
- let binary = "";
72
- for (let offset = 0; offset < bytes.length; offset += 0x8000) {
73
- binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
74
- }
75
- return btoa(binary);
76
- }
77
123
  async function successBody(vendor, response, ctx) {
78
124
  const type = (response.headers.get("content-type") ?? "").toLowerCase();
79
125
  try {
@@ -109,7 +155,8 @@ async function successBody(vendor, response, ctx) {
109
155
  return text === "" ? null : text;
110
156
  }
111
157
  const bytes = await response.bytes();
112
- return bytes.length === 0 ? null : { contentType: type, bytes: bytes.length, base64: base64(bytes) };
158
+ // Checked before encoding: no later pass reads through base64 a field does not declare.
159
+ return bytes.length === 0 ? null : { contentType: type, bytes: bytes.length, base64: encodeForAgent(ctx, bytes) };
113
160
  }
114
161
  catch (error) {
115
162
  if (error instanceof ConnectorCallError)
@@ -127,36 +174,6 @@ async function successBody(vendor, response, ctx) {
127
174
  throw unavailableCallError(error, undefined, `${vendor.title} answered, but its response could not be read.`);
128
175
  }
129
176
  }
130
- /**
131
- * Network failures that prove nothing was sent, but only when `fetch` itself
132
- * rejected before any response existed: no connection was ever established,
133
- * so the vendor cannot have acted. Once a response arrived, the same codes
134
- * while reading its body prove nothing of the kind.
135
- */
136
- const NEVER_CONNECTED = new Set([
137
- "ECONNREFUSED",
138
- "ENOTFOUND",
139
- "EAI_AGAIN",
140
- "EAI_FAIL",
141
- "EHOSTUNREACH",
142
- ]);
143
- /**
144
- * A request that is not safe to repeat (a write without an idempotency key)
145
- * must never be advertised as retryable after it may have reached the vendor
146
- * (INV-9). Definite answers pass through: refusals and 4xx verdicts, a failure
147
- * proven to precede any connection, and timeouts, which the invocation layer
148
- * already reports as `write_outcome_unknown`. Exported so a named write tool
149
- * with its own transport (an upload) applies the same rule.
150
- */
151
- export function unknownOutcome(vendor, error, responded) {
152
- if (!(error instanceof ConnectorCallError) || error.code !== "unavailable" || !error.retryable)
153
- return error;
154
- const code = error.details?.code;
155
- if (code === "timeout" || (!responded && code !== undefined && NEVER_CONNECTED.has(code)))
156
- return error;
157
- return new ConnectorCallError("connector_call_failed", `${error.message} Whether the write took effect at ${vendor.title} is unknown: it carried no idempotency key, ` +
158
- "so check the target before repeating it.", { retryable: false });
159
- }
160
177
  /**
161
178
  * Send one prepared call and read it into the envelope. No validation and no
162
179
  * refusal tables run here: the generic tools do both first, and named tools
@@ -169,14 +186,19 @@ export async function callRest(vendor, call, ctx, visibleHeaders = {}) {
169
186
  invalid(send);
170
187
  await vendor.admit?.(call, ctx);
171
188
  const framing = vendor.encode ? vendor.encode(call) : defaultFraming(vendor, call);
172
- // Safe to repeat: a read, or a write that carried the vendor's idempotency key.
173
189
  const header = vendor.idempotencyHeader?.toLowerCase();
174
- const repeatable = call.method === "GET" ||
175
- call.method === "HEAD" ||
176
- (vendor.readPosts ?? []).some(([method, path]) => method === call.method && path === call.op.path) ||
177
- (header !== undefined && Object.keys(visibleHeaders).some((name) => name.toLowerCase() === header));
190
+ const idempotencyKey = header === undefined
191
+ ? undefined
192
+ : Object.entries(visibleHeaders).find(([name, value]) => name.toLowerCase() === header && unsendableKey(value) === undefined)?.[1];
178
193
  const request = {
179
194
  method: call.method,
195
+ classification: call.method === "GET" ||
196
+ call.method === "HEAD" ||
197
+ (vendor.readPosts ?? []).some(([method, path]) => method === call.method && path === call.op.path)
198
+ ? "read"
199
+ : "write",
200
+ // Only a key the vendor receives proves the write repeatable.
201
+ ...(idempotencyKey === undefined ? {} : { idempotencyKey }),
180
202
  path: call.path,
181
203
  ...(framing.query ? { query: framing.query } : {}),
182
204
  ...(framing.headers ? { headers: framing.headers } : {}),
@@ -187,30 +209,22 @@ export async function callRest(vendor, call, ctx, visibleHeaders = {}) {
187
209
  ? { body: framing.body }
188
210
  : {}),
189
211
  };
190
- // Set once a response exists: from then on the connection is proven, and no
191
- // network failure means nothing was sent.
192
- let responded = false;
193
- try {
194
- return await send(request, ctx, async (response) => {
195
- responded = true;
196
- if (!response.ok)
197
- throw vendor.failure(response.status, response.headers, await failureBody(response), call);
198
- // HEAD answers with headers alone; they are its data.
199
- const body = call.method === "HEAD" ? headerData(response.headers) : await successBody(vendor, response, ctx);
200
- // Order: unwrap the vendor's envelope, then redact (every success, HEAD
201
- // header data and named tools included), then cursors (and, in the
202
- // tools, `select`). Cursors read the envelope when the vendor unwraps
203
- // one, and the redacted body otherwise.
204
- const unwrapped = vendor.result && call.method !== "HEAD" ? vendor.result(body, call, response) : body;
205
- const data = vendor.valueSafety.redact(unwrapped, call.method, call.op.path);
206
- const page = vendor.page?.(vendor.result ? body : data, call);
207
- return { status: response.status, data, ...(page ? { page } : {}) };
208
- });
209
- }
210
- catch (error) {
211
- // The whole transport call: connect/send failures, 5xx, and a lost body alike.
212
- throw repeatable ? error : unknownOutcome(vendor, error, responded);
213
- }
212
+ const write = (callClassification(ctx) ?? request.classification) === "write";
213
+ return await send(request, ctx, async (response) => {
214
+ // Mappers cut the vendor's prose short, so it is redacted whole first.
215
+ if (!response.ok)
216
+ throw vendor.failure(response.status, response.headers, redactBeforeCut(ctx, await failureBody(response)), call, write);
217
+ // HEAD answers with headers alone; they are its data.
218
+ const body = call.method === "HEAD" ? headerData(response.headers) : await successBody(vendor, response, ctx);
219
+ // Order: unwrap the vendor's envelope, then redact (every success, HEAD
220
+ // header data and named tools included), then cursors (and, in the
221
+ // tools, `select`). Cursors read the envelope when the vendor unwraps
222
+ // one, and the redacted body otherwise.
223
+ const unwrapped = vendor.result && call.method !== "HEAD" ? vendor.result(body, call, response, ctx) : body;
224
+ const data = vendor.valueSafety.redact(unwrapped, call.method, call.op.path);
225
+ const page = vendor.page?.(vendor.result ? body : data, call);
226
+ return { status: response.status, data, ...(page ? { page } : {}) };
227
+ });
214
228
  }
215
229
  /** A HEAD response's headers as data, without cookies. */
216
230
  function headerData(headers) {
@@ -340,6 +354,9 @@ export function restTools(vendor) {
340
354
  op.method === "HEAD" ||
341
355
  readPosts.some(([method, path]) => method === op.method && path === op.path);
342
356
  const writeMethods = index.methods.filter((method) => method !== "GET" && method !== "HEAD");
357
+ // The methods an idempotency key is sent with: never DELETE (see the handler).
358
+ const keyed = vendor.idempotencyHeader ? writeMethods.filter((method) => method !== "DELETE") : [];
359
+ const keyedText = keyed.join(" or ");
343
360
  const head = index.methods.includes("HEAD");
344
361
  const readMethods = [...new Set(["GET", ...(head ? ["HEAD"] : []), ...readPosts.map(([method]) => method)])];
345
362
  const routeOf = (op) => (reads(op) ? tool("read") : tool("write"));
@@ -490,7 +507,7 @@ export function restTools(vendor) {
490
507
  {
491
508
  name: tool("write"),
492
509
  description: `Call a ${title} API write (${writeMethods.join(", ")}); method and path are stated for approval. ` +
493
- `Checked against the pinned index first.${vendor.idempotencyHeader ? " Pass idempotencyKey to retry safely." : ""}`,
510
+ `Checked against the pinned index first.${keyed.length ? ` Pass idempotencyKey to retry a ${keyedText} safely${keyed.length < writeMethods.length ? "; a DELETE sends no key" : ""}.` : ""}`,
494
511
  annotations: DESTRUCTIVE,
495
512
  inputSchema: closed({
496
513
  method: { type: "string", enum: writeMethods, description: "The operation's HTTP method." },
@@ -507,7 +524,7 @@ export function restTools(vendor) {
507
524
  type: "string",
508
525
  minLength: 1,
509
526
  maxLength: IDEMPOTENCY_KEY_MAX_LENGTH,
510
- description: "Reuse to retry this exact write; one is generated and returned when omitted.",
527
+ description: `Reuse to retry this exact ${keyedText}; one is generated and returned when omitted.`,
511
528
  },
512
529
  }
513
530
  : {}),
@@ -539,6 +556,11 @@ export function restTools(vendor) {
539
556
  index.check(call.op, call.query, call.body);
540
557
  const header = vendor.idempotencyHeader && call.method !== "DELETE" ? vendor.idempotencyHeader : undefined;
541
558
  const given = typeof args["idempotencyKey"] === "string" ? args["idempotencyKey"] : undefined;
559
+ // A blank key reaches the vendor empty, and one fetch cannot carry
560
+ // never leaves: neither proves that a retry cannot repeat the write.
561
+ const unsendable = given === undefined ? undefined : unsendableKey(given);
562
+ if (unsendable)
563
+ invalid(`idempotencyKey ${unsendable}, or omit it.`);
542
564
  const key = header ? (given ?? crypto.randomUUID()) : undefined;
543
565
  // Recorded before dispatch: if the invocation deadline interrupts this
544
566
  // call, no failure of ours arrives, and the invocation returns the key
@@ -552,16 +574,17 @@ export function restTools(vendor) {
552
574
  catch (error) {
553
575
  // A generated key exists nowhere else, so a failure must carry it:
554
576
  // only that key lets the caller check or retry this exact write.
577
+ // The key stays in the message as a diagnostic; the advice to
578
+ // reuse it is a segment the invocation composes only where the key
579
+ // protects the whole logical call (keyResendAdvice), and exists
580
+ // only once the ledger shows a dispatched request carrying it.
555
581
  if (!key || given || !(error instanceof ConnectorCallError))
556
582
  throw error;
557
- throw new ConnectorCallError(error.code, `${error.message} ${header}: ${key}. Reusing it with the exact original arguments retries this write ` +
558
- "without repeating it only while the vendor retains the key; after that, look the object up before retrying.", {
559
- retryable: error.retryable,
560
- ...(error.retryAfterMs !== undefined ? { retryAfterMs: error.retryAfterMs } : {}),
561
- ...(error.validation ? { validation: error.validation } : {}),
562
- ...(error.repair ? { repair: error.repair } : {}),
563
- ...(error.details ? { details: error.details } : {}),
564
- });
583
+ const failure = providerFailureMessage(error, `${error.message} ${header}: ${key}.`);
584
+ if (!providerRequests(ctx)?.some((request) => request.dispatched && request.key === key))
585
+ throw failure;
586
+ throw keyResendAdvice(failure, key, " Reusing it with the exact original arguments retries this write without repeating it only while " +
587
+ "the vendor retains the key; after that, look the object up before retrying.");
565
588
  }
566
589
  },
567
590
  },