@zackbart/connecta 0.34.1 → 0.36.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 (441) hide show
  1. package/CHANGELOG.md +418 -0
  2. package/README.md +65 -105
  3. package/SECURITY.md +3 -2
  4. package/bin/connecta.mjs +41 -16
  5. package/bin/version.mjs +1 -1
  6. package/dist/access-tokens.d.ts +2 -1
  7. package/dist/access-tokens.js +66 -30
  8. package/dist/activity.d.ts +1 -1
  9. package/dist/activity.js +2 -0
  10. package/dist/agent-config.d.ts +23 -0
  11. package/dist/agent-config.js +59 -0
  12. package/dist/agent-description.js +16 -0
  13. package/dist/agent-identity.js +4 -4
  14. package/dist/agent-observation.js +39 -0
  15. package/dist/auth/clerk-sdk/client.d.ts +2 -0
  16. package/dist/auth/clerk-sdk/generated.js +554 -563
  17. package/dist/auth/clerk-transport.js +70 -3
  18. package/dist/auth/clerk.d.ts +13 -0
  19. package/dist/auth/clerk.js +653 -246
  20. package/dist/auth/cloudflare-access.js +46 -42
  21. package/dist/auth/downstream-client-metadata.js +2 -2
  22. package/dist/auth/downstream-oauth.js +2044 -1161
  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 +751 -281
  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 +29 -7
  53. package/dist/connectors/api-connector.js +145 -57
  54. package/dist/connectors/guarded-fetch.d.ts +51 -17
  55. package/dist/connectors/guarded-fetch.js +215 -121
  56. package/dist/connectors/negotiation-cache.js +20 -10
  57. package/dist/connectors/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 +227 -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 +379 -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/contract.d.ts +8 -3
  109. package/dist/operator-ui/generated.js +4 -4
  110. package/dist/operator-ui/model.d.ts +2 -3
  111. package/dist/program-writes.js +47 -26
  112. package/dist/provider-call-facts.d.ts +115 -0
  113. package/dist/provider-call-facts.js +182 -0
  114. package/dist/provider-call.d.ts +112 -0
  115. package/dist/provider-call.js +271 -0
  116. package/dist/provider.d.ts +65 -31
  117. package/dist/provider.js +116 -39
  118. package/dist/providers/_shared/google/hatch.js +91 -0
  119. package/dist/providers/_shared/google/kit.js +427 -0
  120. package/dist/providers/_shared/google/result-size.js +15 -2
  121. package/dist/providers/_shared/google/service-account.d.ts +2 -1
  122. package/dist/providers/_shared/google/service-account.js +8 -4
  123. package/dist/providers/_shared/google/workspace.d.ts +6 -210
  124. package/dist/providers/_shared/google/workspace.js +6 -425
  125. package/dist/providers/_shared/rest/compact-index.js +86 -0
  126. package/dist/providers/_shared/rest/kit.js +146 -0
  127. package/dist/providers/_shared/rest/operation-index.js +230 -14
  128. package/dist/providers/_shared/rest/response-media.js +11 -0
  129. package/dist/providers/_shared/rest/value-safety.js +353 -195
  130. package/dist/providers/_shared/sensitive-fields.js +27 -0
  131. package/dist/providers/breeze/api.js +584 -0
  132. package/dist/providers/breeze/index.d.ts +1 -3
  133. package/dist/providers/breeze/index.js +67 -1420
  134. package/dist/providers/breeze/openapi.generated.js +60 -0
  135. package/dist/providers/breeze/skill.generated.js +2 -6
  136. package/dist/providers/breeze/tools.js +696 -0
  137. package/dist/providers/ccb/api.js +496 -0
  138. package/dist/providers/ccb/index.d.ts +15 -37
  139. package/dist/providers/ccb/index.js +77 -1702
  140. package/dist/providers/ccb/lib/constants.d.ts +34 -0
  141. package/dist/providers/ccb/lib/constants.js +74 -0
  142. package/dist/providers/ccb/lib/guide.js +9 -0
  143. package/dist/providers/ccb/lib/verdicts.js +387 -0
  144. package/dist/providers/ccb/openapi.generated.js +397 -0
  145. package/dist/providers/ccb/safety.js +295 -0
  146. package/dist/providers/ccb/skill.generated.js +2 -6
  147. package/dist/providers/ccb/tools.js +853 -0
  148. package/dist/providers/cloudflare/api.js +1066 -0
  149. package/dist/providers/cloudflare/index.d.ts +5 -22
  150. package/dist/providers/cloudflare/index.js +79 -197
  151. package/dist/providers/cloudflare/lib/config.d.ts +16 -0
  152. package/dist/providers/cloudflare/lib/config.js +2 -0
  153. package/dist/providers/cloudflare/lib/discovery.js +29 -0
  154. package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
  155. package/dist/providers/cloudflare/lib/hatch.js +103 -0
  156. package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
  157. package/dist/providers/cloudflare/lib/response-media.js +75 -0
  158. package/dist/providers/cloudflare/openapi.generated.js +4 -3
  159. package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
  160. package/dist/providers/cloudflare/skill.generated.js +2 -10
  161. package/dist/providers/cloudflare/tools.js +360 -0
  162. package/dist/providers/docs/api.js +51 -0
  163. package/dist/providers/docs/index.d.ts +1 -12
  164. package/dist/providers/docs/index.js +53 -1165
  165. package/dist/providers/docs/lib/constants.d.ts +18 -0
  166. package/dist/providers/docs/lib/constants.js +18 -0
  167. package/dist/providers/docs/lib/discovery.js +26 -0
  168. package/dist/providers/docs/lib/requests.js +48 -0
  169. package/dist/providers/docs/lib/schemas.js +168 -0
  170. package/dist/providers/docs/openapi.generated.js +16 -0
  171. package/dist/providers/docs/safety.js +54 -0
  172. package/dist/providers/docs/skill.generated.js +2 -6
  173. package/dist/providers/docs/tools.js +801 -0
  174. package/dist/providers/drive/api.js +177 -0
  175. package/dist/providers/drive/index.d.ts +1 -12
  176. package/dist/providers/drive/index.js +38 -1448
  177. package/dist/providers/drive/lib/constants.d.ts +6 -0
  178. package/dist/providers/drive/lib/constants.js +6 -0
  179. package/dist/providers/drive/lib/contracts.js +532 -0
  180. package/dist/providers/drive/lib/hatch.js +197 -0
  181. package/dist/providers/drive/openapi.generated.js +77 -0
  182. package/dist/providers/drive/safety.js +90 -0
  183. package/dist/providers/drive/skill.generated.js +2 -6
  184. package/dist/providers/drive/tools.js +1290 -0
  185. package/dist/providers/forms/api.js +71 -0
  186. package/dist/providers/forms/index.d.ts +1 -12
  187. package/dist/providers/forms/index.js +49 -1145
  188. package/dist/providers/forms/lib/constants.d.ts +14 -0
  189. package/dist/providers/forms/lib/constants.js +19 -0
  190. package/dist/providers/forms/lib/hatch.js +72 -0
  191. package/dist/providers/forms/openapi.generated.js +23 -0
  192. package/dist/providers/forms/safety.js +34 -0
  193. package/dist/providers/forms/skill.generated.js +2 -8
  194. package/dist/providers/forms/tools.js +1023 -0
  195. package/dist/providers/github/api.js +150 -0
  196. package/dist/providers/github/index.d.ts +4 -5
  197. package/dist/providers/github/index.js +30 -87
  198. package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
  199. package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
  200. package/dist/providers/github/lib/discovery.js +132 -0
  201. package/dist/providers/github/lib/operations.js +58 -0
  202. package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
  203. package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
  204. package/dist/providers/github/openapi.generated.js +1216 -0
  205. package/dist/providers/github/safety.js +216 -0
  206. package/dist/providers/github/skill.generated.js +2 -5
  207. package/dist/providers/github/tools.js +506 -0
  208. package/dist/providers/gmail/api.js +114 -0
  209. package/dist/providers/gmail/index.d.ts +1 -12
  210. package/dist/providers/gmail/index.js +73 -1621
  211. package/dist/providers/gmail/lib/constants.d.ts +13 -0
  212. package/dist/providers/gmail/lib/constants.js +16 -0
  213. package/dist/providers/gmail/lib/index.js +173 -0
  214. package/dist/providers/gmail/lib/schemas.js +176 -0
  215. package/dist/providers/gmail/lib/threading.js +120 -0
  216. package/dist/providers/gmail/openapi.generated.js +92 -0
  217. package/dist/providers/gmail/safety.js +21 -0
  218. package/dist/providers/gmail/skill.generated.js +2 -6
  219. package/dist/providers/gmail/tools.js +1435 -0
  220. package/dist/providers/overflow/api.js +1008 -0
  221. package/dist/providers/overflow/index.d.ts +1 -17
  222. package/dist/providers/overflow/index.js +60 -1488
  223. package/dist/providers/overflow/skill.generated.js +2 -5
  224. package/dist/providers/overflow/tools.js +873 -0
  225. package/dist/providers/planning-center/api.js +467 -0
  226. package/dist/providers/planning-center/index.d.ts +4 -35
  227. package/dist/providers/planning-center/index.js +69 -2445
  228. package/dist/providers/planning-center/lib/constants.d.ts +58 -0
  229. package/dist/providers/planning-center/lib/constants.js +83 -0
  230. package/dist/providers/planning-center/lib/discovery.js +40 -0
  231. package/dist/providers/planning-center/lib/guide.js +39 -0
  232. package/dist/providers/planning-center/lib/json-api.js +83 -0
  233. package/dist/providers/planning-center/lib/verdicts.js +1758 -0
  234. package/dist/providers/planning-center/openapi.generated.js +1764 -0
  235. package/dist/providers/planning-center/safety.js +188 -0
  236. package/dist/providers/planning-center/skill.generated.js +2 -27
  237. package/dist/providers/planning-center/tools.js +1010 -0
  238. package/dist/providers/quickbooks/api.js +299 -0
  239. package/dist/providers/quickbooks/index.d.ts +13 -0
  240. package/dist/providers/quickbooks/index.js +93 -0
  241. package/dist/providers/quickbooks/lib/query.js +118 -0
  242. package/dist/providers/quickbooks/lib/writes.js +168 -0
  243. package/dist/providers/quickbooks/safety.js +222 -0
  244. package/dist/providers/quickbooks/skill.generated.js +5 -0
  245. package/dist/providers/quickbooks/tools.js +117 -0
  246. package/dist/providers/sheets/api.js +133 -0
  247. package/dist/providers/sheets/index.d.ts +4 -16
  248. package/dist/providers/sheets/index.js +61 -1172
  249. package/dist/providers/sheets/lib/constants.d.ts +14 -0
  250. package/dist/providers/sheets/lib/constants.js +14 -0
  251. package/dist/providers/sheets/lib/hatch.js +163 -0
  252. package/dist/providers/sheets/lib/requests.js +105 -0
  253. package/dist/providers/sheets/lib/schemas.js +138 -0
  254. package/dist/providers/sheets/openapi.generated.js +30 -0
  255. package/dist/providers/sheets/safety.js +27 -0
  256. package/dist/providers/sheets/skill.generated.js +2 -7
  257. package/dist/providers/sheets/tools.js +733 -0
  258. package/dist/providers/slides/api.js +147 -0
  259. package/dist/providers/slides/index.d.ts +2 -10
  260. package/dist/providers/slides/index.js +48 -2555
  261. package/dist/providers/slides/lib/constants.d.ts +11 -0
  262. package/dist/providers/slides/lib/constants.js +11 -0
  263. package/dist/providers/slides/lib/requests.js +60 -0
  264. package/dist/providers/slides/lib/schemas.js +565 -0
  265. package/dist/providers/slides/openapi.generated.js +18 -0
  266. package/dist/providers/slides/skill.generated.js +2 -6
  267. package/dist/providers/slides/tools.js +1968 -0
  268. package/dist/providers/stripe/api.js +401 -0
  269. package/dist/providers/stripe/index.d.ts +5 -27
  270. package/dist/providers/stripe/index.js +74 -210
  271. package/dist/providers/stripe/lib/config.d.ts +4 -0
  272. package/dist/providers/stripe/lib/config.js +2 -0
  273. package/dist/providers/stripe/openapi.generated.js +4 -3
  274. package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
  275. package/dist/providers/stripe/skill.generated.js +2 -11
  276. package/dist/providers/stripe/tools.js +77 -0
  277. package/dist/providers/tithely/api.js +531 -0
  278. package/dist/providers/tithely/index.d.ts +17 -0
  279. package/dist/providers/tithely/index.js +86 -965
  280. package/dist/providers/tithely/skill.generated.js +2 -5
  281. package/dist/providers/tithely/tools.js +426 -0
  282. package/dist/providers/vercel/api.js +551 -0
  283. package/dist/providers/vercel/index.d.ts +9 -25
  284. package/dist/providers/vercel/index.js +65 -260
  285. package/dist/providers/vercel/lib/config.d.ts +2 -0
  286. package/dist/providers/vercel/lib/config.js +2 -0
  287. package/dist/providers/vercel/lib/index.js +117 -0
  288. package/dist/providers/vercel/lib/logs.js +100 -0
  289. package/dist/providers/vercel/lib/pin.js +270 -0
  290. package/dist/providers/vercel/openapi.generated.js +10 -5
  291. package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
  292. package/dist/providers/vercel/skill.generated.js +2 -7
  293. package/dist/providers/vercel/tools.js +642 -0
  294. package/dist/redaction-identity.js +47 -0
  295. package/dist/redaction-marker.js +125 -0
  296. package/dist/registry.d.ts +21 -15
  297. package/dist/registry.js +409 -376
  298. package/dist/routes/access-tokens.js +1 -1
  299. package/dist/routes/actor-labels.js +1 -1
  300. package/dist/routes/connect.js +1 -1
  301. package/dist/routes/dispatch.js +103 -0
  302. package/dist/routes/mcp.js +170 -79
  303. package/dist/routes/oauth.js +6 -1
  304. package/dist/routes/operator.js +2 -2
  305. package/dist/routes/shared.d.ts +5 -4
  306. package/dist/routes/shared.js +244 -177
  307. package/dist/routes/skills.js +10 -8
  308. package/dist/routes/ui-config.js +27 -3
  309. package/dist/routes/ui-data.js +21 -20
  310. package/dist/routes/ui.js +1 -1
  311. package/dist/runtime/call-admission.js +0 -26
  312. package/dist/runtime/call-entry.js +30 -0
  313. package/dist/runtime/call.js +552 -0
  314. package/dist/runtime/connector-scope.js +7 -0
  315. package/dist/runtime/credential-probe.js +34 -0
  316. package/dist/runtime/executor.js +40 -0
  317. package/dist/runtime/native-storage.js +63 -0
  318. package/dist/runtime/registry.js +27 -0
  319. package/dist/runtime/request-scope.js +72 -0
  320. package/dist/runtime/run.js +15 -7
  321. package/dist/runtime/shared-read.js +34 -5
  322. package/dist/runtime/storage.js +34 -21
  323. package/dist/sent-secrets.d.ts +194 -37
  324. package/dist/sent-secrets.js +1743 -340
  325. package/dist/server.js +5 -56
  326. package/dist/skills.js +322 -223
  327. package/dist/sqlite.js +15 -14
  328. package/dist/storage/d1-copy.js +255 -0
  329. package/dist/storage/d1-driver.js +15 -0
  330. package/dist/storage/keys.js +28 -0
  331. package/dist/storage/sql-driver.js +88 -0
  332. package/dist/storage/sql-native.js +544 -0
  333. package/dist/storage/sql-schema.d.ts +6 -0
  334. package/dist/storage/sql-schema.js +15 -0
  335. package/dist/storage/sql.d.ts +14 -20
  336. package/dist/storage/sql.js +29 -535
  337. package/dist/timeout.js +0 -26
  338. package/dist/tool-safety.js +20 -10
  339. package/dist/types.d.ts +9 -109
  340. package/dist/ui.d.ts +1 -1
  341. package/dist/ui.js +0 -1
  342. package/dist/upward.js +3 -0
  343. package/dist/usage-guide.js +3 -1
  344. package/dist/validate.d.ts +4 -2
  345. package/dist/validate.js +86 -9
  346. package/dist/version.d.ts +1 -1
  347. package/dist/version.js +1 -1
  348. package/dist/worker.js +312 -292
  349. package/dist/workspace-subject.js +5 -1
  350. package/dist/write-outcome.js +454 -34
  351. package/documentation/README.md +28 -7
  352. package/documentation/architecture.md +2409 -255
  353. package/documentation/auth.md +867 -114
  354. package/documentation/code-mode.md +49 -16
  355. package/documentation/deploying.md +164 -20
  356. package/documentation/hosted-mcp-migration.md +460 -0
  357. package/documentation/integrating.md +129 -144
  358. package/documentation/meta-tools.md +409 -32
  359. package/documentation/operating.md +27 -3
  360. package/documentation/operator-tests.md +3 -0
  361. package/documentation/operator-ui.md +84 -48
  362. package/documentation/provider-auth-migration.md +16 -63
  363. package/documentation/provider-migration-0.29.md +10 -5
  364. package/documentation/provider-migration-0.35.md +975 -0
  365. package/documentation/providers.md +1347 -0
  366. package/examples/worker/AGENTS.md +23 -9
  367. package/examples/worker/README.md +254 -135
  368. package/examples/worker/alchemy.run.ts +116 -0
  369. package/examples/worker/gitignore +10 -0
  370. package/examples/worker/src/connecta.config.ts +23 -21
  371. package/examples/worker/wrangler.jsonc +14 -5
  372. package/guides/connecting-agents.md +157 -0
  373. package/guides/how-it-works.md +156 -0
  374. package/guides/safety-and-trust.md +153 -0
  375. package/package.json +30 -50
  376. package/templates/node/.env.example +3 -0
  377. package/templates/node/README.md +22 -2
  378. package/templates/node/docker-compose.yml +1 -0
  379. package/templates/node/package.json +3 -2
  380. package/templates/node/src/connecta.config.ts +8 -0
  381. package/dist/catalog-drift.d.ts +0 -138
  382. package/dist/catalog-drift.js +0 -578
  383. package/dist/operator-ui/agents.d.ts +0 -74
  384. package/dist/operator-ui/agents.js +0 -104
  385. package/dist/operator-ui/config-summary.js +0 -565
  386. package/dist/operator-ui/connection-map.js +0 -376
  387. package/dist/operator-ui/endpoints.js +0 -221
  388. package/dist/operator-ui/fix-prompts.d.ts +0 -18
  389. package/dist/operator-ui/fix-prompts.js +0 -87
  390. package/dist/operator-ui/health.js +0 -52
  391. package/dist/operator-ui/member-page.js +0 -97
  392. package/dist/operator-ui/overview.js +0 -123
  393. package/dist/operator-ui/setup-commands.js +0 -51
  394. package/dist/operator-ui/view.d.ts +0 -401
  395. package/dist/operator-ui/view.js +0 -705
  396. package/dist/providers/_shared/request-token.js +0 -83
  397. package/dist/providers/_shared/rest/dispatch.js +0 -41
  398. package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
  399. package/dist/providers/_shared/rest/tools.d.ts +0 -141
  400. package/dist/providers/_shared/rest/tools.js +0 -569
  401. package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
  402. package/dist/providers/basecamp/index.d.ts +0 -45
  403. package/dist/providers/basecamp/index.js +0 -997
  404. package/dist/providers/basecamp/skill.generated.js +0 -9
  405. package/dist/providers/cloudflare/rest.d.ts +0 -37
  406. package/dist/providers/cloudflare/rest.js +0 -1097
  407. package/dist/providers/github/hosted.js +0 -218
  408. package/dist/providers/github/rest.js +0 -321
  409. package/dist/providers/infisical/index.d.ts +0 -7
  410. package/dist/providers/infisical/index.js +0 -713
  411. package/dist/providers/infisical/skill.generated.js +0 -8
  412. package/dist/providers/intercom/index.d.ts +0 -24
  413. package/dist/providers/intercom/index.js +0 -121
  414. package/dist/providers/intercom/skill.generated.js +0 -8
  415. package/dist/providers/linear/index.d.ts +0 -55
  416. package/dist/providers/linear/index.js +0 -537
  417. package/dist/providers/linear/skill.generated.js +0 -8
  418. package/dist/providers/mixpanel/index.d.ts +0 -59
  419. package/dist/providers/mixpanel/index.js +0 -472
  420. package/dist/providers/mixpanel/operations.js +0 -20
  421. package/dist/providers/mixpanel/rest.js +0 -603
  422. package/dist/providers/mixpanel/skill.generated.js +0 -10
  423. package/dist/providers/mixpanel/value-safety.js +0 -16
  424. package/dist/providers/notion/index.d.ts +0 -67
  425. package/dist/providers/notion/index.js +0 -368
  426. package/dist/providers/notion/openapi.generated.js +0 -75
  427. package/dist/providers/notion/rest.d.ts +0 -24
  428. package/dist/providers/notion/rest.js +0 -1226
  429. package/dist/providers/notion/skill.generated.js +0 -11
  430. package/dist/providers/notion/value-safety.js +0 -56
  431. package/dist/providers/revenuecat/index.d.ts +0 -51
  432. package/dist/providers/revenuecat/index.js +0 -697
  433. package/dist/providers/revenuecat/skill.generated.js +0 -11
  434. package/dist/providers/stripe/rest.d.ts +0 -14
  435. package/dist/providers/stripe/rest.js +0 -402
  436. package/dist/providers/vercel/rest.d.ts +0 -20
  437. package/dist/providers/vercel/rest.js +0 -1020
  438. package/dist/providers/wispr-flow/index.d.ts +0 -14
  439. package/dist/providers/wispr-flow/index.js +0 -117
  440. package/dist/providers/wispr-flow/skill.generated.js +0 -8
  441. /package/dist/providers/github/{key.js → lib/key.js} +0 -0
@@ -1,13 +1,34 @@
1
1
  import { auth, OAuthError, UnauthorizedError } from "@modelcontextprotocol/client";
2
+ import { Effect } from "effect";
2
3
  import { byteReadResponse } from "../byte-read-response.js";
3
- import { sentSecretsFor, sentSecretsFetch, shortSecretWarning } from "../sent-secrets.js";
4
- import { ConnectorCallError, msg } from "../errors.js";
5
- import { oauthPartitionFor, retainOAuthPartition, retainingOAuthPartition } from "../oauth-partition.js";
4
+ import { deriveContext } from "../derived-context.js";
5
+ import { ConnectorCallError, msg, resendAdviceOf, withResendAdvice } from "../errors.js";
6
+ import { guardedTransport, sendCredentialPost } from "../guarded-transport.js";
7
+ import { oauthPartitionFor, retainOAuthPartition } from "../oauth-partition.js";
6
8
  import { oauthSealerFor } from "../oauth-sealing.js";
7
9
  import { describeFailure } from "../operator-record.js";
8
- import { assertOAuthScope, authorizingContext, KvOAuthProvider, refreshCoordinatorsByPartition, } from "./downstream-oauth.js";
10
+ import { assertProviderCallOpen, bindProviderResponse, dispatchProviderRequest, providerRequest, readRequest, recordingFetch, recordProviderResponse, renewedAfterUnauthorized, takeProviderHandoff, vendorRefusal, } from "../provider-call.js";
11
+ import { viewSignal } from "../runtime/call.js";
12
+ import { runEdge } from "../runtime/run.js";
13
+ import { SharedRead } from "../runtime/shared-read.js";
14
+ import { sentSecretsFetch, sentSecretsFor, shortSecretWarning } from "../sent-secrets.js";
15
+ import { unsentAfterUnauthorized } from "../write-outcome.js";
9
16
  import { trackRemoteClientRequest } from "./downstream-client-metadata.js";
17
+ import { assertOAuthScope, authorizingContext, codeExchangeResponse, KvOAuthProvider, refreshCoordinatorsByPartition, } from "./downstream-oauth.js";
18
+ import { authView } from "./native-call.js";
19
+ import { authCall, authFlow, authInvoke, authMethod, authOwnMethod, authProgram, authValue, registerAuth, registerAuthMethod, } from "./native.js";
10
20
  import { trackOAuthStartReset } from "./oauth-start-reset.js";
21
+ import { bindRenewableGrant, OAUTH_CHALLENGE_MAX_BYTES } from "./renewable-grant.js";
22
+ /**
23
+ * Token-request header values Connecta itself sends, by lowercase name: JSON
24
+ * content negotiation and the media types maintained providers configure.
25
+ * Registering one would redact a vendor's name wherever its fragments recur.
26
+ * Any other configured value, under any name, may carry a credential and is
27
+ * registered.
28
+ */
29
+ export const PUBLIC_TOKEN_REQUEST_HEADERS = new Map([
30
+ ["accept", new Set(["application/json", "application/vnd.ccbchurch.v2+json"])],
31
+ ]);
11
32
  /**
12
33
  * Downstream OAuth for a hand-written `api()` connector: the authorization
13
34
  * code grant against endpoints and a pre-registered client the deployment
@@ -33,6 +54,15 @@ const RESERVED_AUTHORIZATION_PARAMS = new Set([
33
54
  "code_challenge_method",
34
55
  "resource",
35
56
  ]);
57
+ /** Protocol response fields have their own validation, never static assertions. */
58
+ const RESERVED_CALLBACK_PARAMS = new Set([
59
+ "code",
60
+ "state",
61
+ "iss",
62
+ "error",
63
+ "error_description",
64
+ "error_uri",
65
+ ]);
36
66
  /** Token-request headers the grant owns: client authentication and framing. */
37
67
  const RESERVED_TOKEN_HEADERS = new Set([
38
68
  "authorization",
@@ -71,65 +101,91 @@ function settingsFor(id, config) {
71
101
  if (!config || typeof config !== "object") {
72
102
  throw new Error(`[connecta] connector "${id}" oauth must be an object.`);
73
103
  }
104
+ const invalid = (detail) => {
105
+ throw new Error(`[connecta] connector "${id}" oauth.${detail}`);
106
+ };
74
107
  const authorizationEndpoint = configuredUrl(id, "authorizationEndpoint", config.authorizationEndpoint);
75
108
  const tokenEndpoint = configuredUrl(id, "tokenEndpoint", config.tokenEndpoint);
109
+ const revocationEndpoint = config.revocationEndpoint === undefined
110
+ ? undefined
111
+ : configuredUrl(id, "revocationEndpoint", config.revocationEndpoint);
112
+ if (revocationEndpoint?.search)
113
+ invalid(`revocationEndpoint must not carry a query.`);
114
+ const revocationRequestFormat = config.revocationRequestFormat ?? "form";
115
+ if ((revocationRequestFormat !== "form" && revocationRequestFormat !== "json") ||
116
+ (config.revocationRequestFormat !== undefined && !revocationEndpoint))
117
+ invalid(`revocationRequestFormat requires an endpoint and form or json.`);
76
118
  if (tokenEndpoint.search) {
77
- throw new Error(`[connecta] connector "${id}" oauth.tokenEndpoint must not carry a query.`);
119
+ invalid(`tokenEndpoint must not carry a query.`);
78
120
  }
79
121
  if (typeof config.clientId !== "string" || config.clientId.trim() === "") {
80
- throw new Error(`[connecta] connector "${id}" oauth.clientId must be a non-empty string.`);
122
+ invalid(`clientId must be a non-empty string.`);
81
123
  }
82
124
  if (config.clientSecret !== undefined && (typeof config.clientSecret !== "string" || config.clientSecret === "")) {
83
125
  // Usually an unset environment variable read as "". Never echo it.
84
- throw new Error(`[connecta] connector "${id}" oauth.clientSecret must be a non-empty string when set.`);
126
+ invalid(`clientSecret must be a non-empty string when set.`);
85
127
  }
86
128
  const clientAuthentication = config.tokenEndpointAuthMethod ?? (config.clientSecret !== undefined ? "client_secret_basic" : "none");
87
129
  if (!CLIENT_AUTHENTICATION.has(clientAuthentication)) {
88
- throw new Error(`[connecta] connector "${id}" oauth.tokenEndpointAuthMethod must be ` +
89
- '"client_secret_basic", "client_secret_post", or "none".');
130
+ invalid(`tokenEndpointAuthMethod must be ` + '"client_secret_basic", "client_secret_post", or "none".');
90
131
  }
91
132
  if (clientAuthentication === "none" && config.clientSecret !== undefined) {
92
133
  throw new Error(`[connecta] connector "${id}" oauth declares a clientSecret with ` +
93
134
  'tokenEndpointAuthMethod "none"; a public client sends no secret.');
94
135
  }
95
136
  if (clientAuthentication !== "none" && config.clientSecret === undefined) {
96
- throw new Error(`[connecta] connector "${id}" oauth.tokenEndpointAuthMethod ` + `"${clientAuthentication}" needs a clientSecret.`);
137
+ invalid(`tokenEndpointAuthMethod ` + `"${clientAuthentication}" needs a clientSecret.`);
97
138
  }
98
139
  assertOAuthScope(id, config.scope);
99
140
  const authorizationParams = [];
100
141
  for (const [name, value] of Object.entries(config.authorizationParams ?? {})) {
101
142
  if (RESERVED_AUTHORIZATION_PARAMS.has(name)) {
102
- throw new Error(`[connecta] connector "${id}" oauth.authorizationParams may not set ` +
103
- `"${name}"; the authorization request owns it.`);
143
+ invalid(`authorizationParams may not set ` + `"${name}"; the authorization request owns it.`);
104
144
  }
105
145
  if (typeof value !== "string") {
106
- throw new Error(`[connecta] connector "${id}" oauth.authorizationParams.${name} must be a string.`);
146
+ invalid(`authorizationParams.${name} must be a string.`);
107
147
  }
108
148
  authorizationParams.push([name, value]);
109
149
  }
150
+ const expectedCallbackParameters = [];
151
+ if (config.expectedCallbackParameters !== undefined &&
152
+ (!config.expectedCallbackParameters ||
153
+ typeof config.expectedCallbackParameters !== "object" ||
154
+ Array.isArray(config.expectedCallbackParameters))) {
155
+ invalid(`expectedCallbackParameters must be a string record.`);
156
+ }
157
+ for (const [name, value] of Object.entries(config.expectedCallbackParameters ?? {})) {
158
+ if (!/^[A-Za-z0-9_.-]+$/.test(name) ||
159
+ RESERVED_CALLBACK_PARAMS.has(name) ||
160
+ typeof value !== "string" ||
161
+ value === "") {
162
+ invalid(`expectedCallbackParameters must use nonempty parameter names ` +
163
+ "(ASCII letters, digits, dots, underscores or hyphens), nonempty string values, and no OAuth response fields.");
164
+ }
165
+ expectedCallbackParameters.push([name, value]);
166
+ }
110
167
  const tokenRequestHeaders = [];
111
168
  for (const [name, value] of Object.entries(config.tokenRequestHeaders ?? {})) {
112
169
  if (!HEADER_NAME.test(name)) {
113
- throw new Error(`[connecta] connector "${id}" oauth.tokenRequestHeaders has an invalid header name.`);
170
+ invalid(`tokenRequestHeaders has an invalid header name.`);
114
171
  }
115
172
  if (RESERVED_TOKEN_HEADERS.has(name.toLowerCase())) {
116
- throw new Error(`[connecta] connector "${id}" oauth.tokenRequestHeaders may not set ` + `"${name}"; the token request owns it.`);
173
+ invalid(`tokenRequestHeaders may not set ` + `"${name}"; the token request owns it.`);
117
174
  }
118
175
  if (typeof value !== "string" || value.includes("\r") || value.includes("\n") || value.includes("\0")) {
119
- throw new Error(`[connecta] connector "${id}" oauth.tokenRequestHeaders.${name} must be a single-line string.`);
176
+ invalid(`tokenRequestHeaders.${name} must be a single-line string.`);
120
177
  }
121
178
  tokenRequestHeaders.push([name, value]);
122
179
  }
123
180
  const origins = Array.isArray(config.apiOrigins) ? config.apiOrigins : [];
124
181
  if (origins.length === 0) {
125
- throw new Error(`[connecta] connector "${id}" oauth.apiOrigins must name at least one ` +
126
- "origin; ctx.oauth.fetch sends the access token nowhere else.");
182
+ invalid(`apiOrigins must name at least one ` + "origin; ctx.oauth.fetch sends the access token nowhere else.");
127
183
  }
128
184
  const apiOrigins = new Set();
129
185
  for (const origin of origins) {
130
186
  const url = configuredUrl(id, "apiOrigins", origin);
131
187
  if (url.origin !== origin) {
132
- throw new Error(`[connecta] connector "${id}" oauth.apiOrigins entries must be exact ` +
188
+ invalid(`apiOrigins entries must be exact ` +
133
189
  'origins, like "https://api.example.com", with no path or trailing slash.');
134
190
  }
135
191
  apiOrigins.add(url.origin);
@@ -137,12 +193,15 @@ function settingsFor(id, config) {
137
193
  return {
138
194
  authorizationEndpoint,
139
195
  tokenEndpoint,
196
+ ...(revocationEndpoint ? { revocationEndpoint } : {}),
197
+ revocationRequestFormat,
140
198
  clientId: config.clientId,
141
199
  ...(config.clientSecret !== undefined ? { clientSecret: config.clientSecret } : {}),
142
200
  clientAuthentication,
143
201
  ...(config.scope !== undefined ? { scope: config.scope } : {}),
144
202
  pkce: config.pkce ?? true,
145
203
  authorizationParams,
204
+ expectedCallbackParameters,
146
205
  tokenRequestHeaders,
147
206
  apiOrigins,
148
207
  serverUrl: new URL(origins[0]),
@@ -159,8 +218,8 @@ class StaticOAuthProvider extends KvOAuthProvider {
159
218
  settings;
160
219
  /** Set only for the code exchange of a PKCE-less flow. */
161
220
  exchangeCode;
162
- constructor(connectorId, ctx, coordinator, settings) {
163
- super(connectorId, ctx.storage, `${ctx.baseUrl}/oauth/callback/${connectorId}`, coordinator, ctx.allowAuthorization === true, oauthSealerFor(ctx), ctx.signal,
221
+ constructor(connectorId, ctx, coordinator, settings, requestOwned = false) {
222
+ super(connectorId, ctx.storage, `${ctx.baseUrl}/oauth/callback/${connectorId}`, coordinator, ctx.allowAuthorization === true, oauthSealerFor(ctx), viewSignal(ctx, requestOwned),
164
223
  // No client binding: the client is configuration, so there is no
165
224
  // registration for a restart to carry forward or discard.
166
225
  undefined, (reset) => trackOAuthStartReset(ctx.requestScope ?? ctx, reset), undefined, settings.scope, undefined, { secrets: sentSecretsFor(ctx) });
@@ -171,7 +230,17 @@ class StaticOAuthProvider extends KvOAuthProvider {
171
230
  * never written to storage, sealed or otherwise: it is deployment config,
172
231
  * not grant state, and an operator disconnect cannot delete it.
173
232
  */
174
- async clientInformation() {
233
+ clientInformation() {
234
+ return runEdge(authOwnMethod(StaticOAuthProvider.prototype, this, "clientInformation", []));
235
+ }
236
+ configuredAuthorizationBinding() {
237
+ return {
238
+ issuer: this.settings.identity,
239
+ clientId: this.settings.clientId,
240
+ tokenEndpoint: this.settings.tokenEndpoint.href,
241
+ };
242
+ }
243
+ configuredClientInformation() {
175
244
  const { settings } = this;
176
245
  return {
177
246
  client_id: settings.clientId,
@@ -181,15 +250,21 @@ class StaticOAuthProvider extends KvOAuthProvider {
181
250
  issuer: settings.identity,
182
251
  };
183
252
  }
184
- configuredAuthorizationBinding() {
253
+ revocationSnapshot(body) {
254
+ const { settings } = this;
185
255
  return {
186
- issuer: this.settings.identity,
187
- clientId: this.settings.clientId,
188
- tokenEndpoint: this.settings.tokenEndpoint.href,
256
+ body: {
257
+ ...body,
258
+ client: { value: this.configuredClientInformation() },
259
+ discovery: {
260
+ authorizationServerUrl: settings.identity,
261
+ authorizationServerMetadata: { revocation_endpoint: settings.revocationEndpoint.href },
262
+ },
263
+ },
189
264
  };
190
265
  }
191
- async saveClientInformation() {
192
- // Nothing to save: the SDK only saves a client it registered or stamped.
266
+ saveClientInformation() {
267
+ return runEdge(authOwnMethod(StaticOAuthProvider.prototype, this, "saveClientInformation", []));
193
268
  }
194
269
  /**
195
270
  * Discovery as a constant. Supplying both halves keeps the SDK from
@@ -198,28 +273,15 @@ class StaticOAuthProvider extends KvOAuthProvider {
198
273
  * The metadata names no `issuer`, so the SDK binds grants to the configured
199
274
  * token endpoint and does not demand an RFC 9207 `iss` it cannot know.
200
275
  */
201
- async discoveryState() {
202
- const { settings } = this;
203
- const metadata = {
204
- authorization_endpoint: settings.authorizationEndpoint.href,
205
- token_endpoint: settings.tokenEndpoint.href,
206
- response_types_supported: ["code"],
207
- grant_types_supported: ["authorization_code", "refresh_token"],
208
- code_challenge_methods_supported: ["S256"],
209
- token_endpoint_auth_methods_supported: [settings.clientAuthentication],
210
- };
211
- return {
212
- authorizationServerUrl: settings.identity,
213
- authorizationServerMetadata: metadata,
214
- resourceMetadata: { resource: settings.serverUrl.origin },
215
- };
276
+ discoveryState() {
277
+ return runEdge(authOwnMethod(StaticOAuthProvider.prototype, this, "discoveryState", []));
216
278
  }
217
- async saveDiscoveryState() {
218
- // Configuration is the discovery; there is nothing to remember.
279
+ saveDiscoveryState() {
280
+ return runEdge(authOwnMethod(StaticOAuthProvider.prototype, this, "saveDiscoveryState", []));
219
281
  }
220
282
  /** No RFC 8707 `resource` parameter: a plain REST API names no resource. */
221
- async validateResourceURL() {
222
- return undefined;
283
+ validateResourceURL() {
284
+ return runEdge(authOwnMethod(StaticOAuthProvider.prototype, this, "validateResourceURL", []));
223
285
  }
224
286
  /**
225
287
  * The code exchange without `code_verifier`, for a flow whose authorization
@@ -239,21 +301,120 @@ class StaticOAuthProvider extends KvOAuthProvider {
239
301
  exchanging(code) {
240
302
  this.exchangeCode = code;
241
303
  }
242
- async redirectToAuthorization(authorizationUrl) {
243
- for (const [name, value] of this.settings.authorizationParams) {
244
- authorizationUrl.searchParams.set(name, value);
245
- }
246
- if (!this.settings.pkce) {
247
- authorizationUrl.searchParams.delete("code_challenge");
248
- authorizationUrl.searchParams.delete("code_challenge_method");
249
- }
250
- await super.redirectToAuthorization(authorizationUrl);
304
+ redirectToAuthorization(authorizationUrl) {
305
+ return runEdge(authOwnMethod(StaticOAuthProvider.prototype, this, "redirectToAuthorization", [authorizationUrl]));
306
+ }
307
+ static {
308
+ registerAuthMethod(StaticOAuthProvider.prototype, "clientInformation", function () {
309
+ return authValue(() => this.configuredClientInformation());
310
+ }, StaticOAuthProvider.prototype.clientInformation);
311
+ registerAuthMethod(StaticOAuthProvider.prototype, "saveClientInformation", function () {
312
+ // Nothing to save: the SDK only saves a client it registered or stamped.
313
+ return Effect.void;
314
+ }, StaticOAuthProvider.prototype.saveClientInformation);
315
+ registerAuthMethod(StaticOAuthProvider.prototype, "discoveryState", function () {
316
+ return authValue(() => {
317
+ const { settings } = this;
318
+ const metadata = {
319
+ authorization_endpoint: settings.authorizationEndpoint.href,
320
+ token_endpoint: settings.tokenEndpoint.href,
321
+ response_types_supported: ["code"],
322
+ grant_types_supported: ["authorization_code", "refresh_token"],
323
+ code_challenge_methods_supported: ["S256"],
324
+ token_endpoint_auth_methods_supported: [settings.clientAuthentication],
325
+ };
326
+ return {
327
+ authorizationServerUrl: settings.identity,
328
+ authorizationServerMetadata: metadata,
329
+ resourceMetadata: { resource: settings.serverUrl.origin },
330
+ };
331
+ });
332
+ }, StaticOAuthProvider.prototype.discoveryState);
333
+ registerAuthMethod(StaticOAuthProvider.prototype, "saveDiscoveryState", function () {
334
+ // Configuration is the discovery; there is nothing to remember.
335
+ return Effect.void;
336
+ }, StaticOAuthProvider.prototype.saveDiscoveryState);
337
+ registerAuthMethod(StaticOAuthProvider.prototype, "validateResourceURL", function () {
338
+ return authValue(() => {
339
+ return undefined;
340
+ });
341
+ }, StaticOAuthProvider.prototype.validateResourceURL);
342
+ registerAuthMethod(StaticOAuthProvider.prototype, "redirectToAuthorization", function (authorizationUrl) {
343
+ return authFlow(function* () {
344
+ for (const [name, value] of this.settings.authorizationParams) {
345
+ authorizationUrl.searchParams.set(name, value);
346
+ }
347
+ if (!this.settings.pkce) {
348
+ authorizationUrl.searchParams.delete("code_challenge");
349
+ authorizationUrl.searchParams.delete("code_challenge_method");
350
+ }
351
+ (yield authCall(KvOAuthProvider.prototype.redirectToAuthorization, [authorizationUrl], this));
352
+ }.bind(this));
353
+ }, StaticOAuthProvider.prototype.redirectToAuthorization);
251
354
  }
252
355
  }
253
356
  const AUTH_REQUIRED_MESSAGE = "Authorization required — open the URL to connect.";
357
+ /** Release the resource body before renewal, bounding even the facade's discarded challenge. */
358
+ function readChallenge(response, signal, method) {
359
+ return runEdge(authProgram(readChallengeNative, [response, signal, method]));
360
+ }
361
+ const readChallengeNative = /* @__PURE__ */ registerAuth(readChallenge, function (response, signal, method) {
362
+ return authFlow(function* () {
363
+ if (method?.toUpperCase() === "HEAD") {
364
+ void response.body?.cancel().catch(() => { });
365
+ return;
366
+ }
367
+ const oversized = () => new ConnectorCallError("connector_call_failed", "The OAuth challenge exceeded its response ceiling.", {
368
+ retryable: false,
369
+ });
370
+ if (Number(response.headers.get("content-length")) > OAUTH_CHALLENGE_MAX_BYTES) {
371
+ void response.body?.cancel().catch(() => { });
372
+ throw oversized();
373
+ }
374
+ if (!response.body)
375
+ return;
376
+ const reader = response.body.getReader();
377
+ const cancel = () => {
378
+ void reader.cancel().catch(() => { });
379
+ };
380
+ signal.addEventListener("abort", cancel, { once: true });
381
+ if (signal.aborted)
382
+ cancel();
383
+ let total = 0;
384
+ try {
385
+ while (true) {
386
+ let chunk;
387
+ try {
388
+ chunk = (yield authInvoke(reader, "read", []));
389
+ }
390
+ catch {
391
+ // Like the guarded client, a broken challenge body cannot replace its 401.
392
+ break;
393
+ }
394
+ if (signal.aborted)
395
+ throw signal.reason;
396
+ if (chunk.done)
397
+ break;
398
+ total += chunk.value.byteLength;
399
+ if (total > OAUTH_CHALLENGE_MAX_BYTES) {
400
+ cancel();
401
+ throw oversized();
402
+ }
403
+ }
404
+ }
405
+ finally {
406
+ signal.removeEventListener("abort", cancel);
407
+ reader.releaseLock();
408
+ }
409
+ });
410
+ });
254
411
  /** The grant behind one `api()` connector's `oauth`, as the hooks it exposes. */
255
412
  export function staticOAuth(id, config) {
256
413
  const settings = settingsFor(id, config);
414
+ const callbackParametersMatch = (params) => settings.expectedCallbackParameters.every(([name, expected]) => {
415
+ const values = params?.getAll(name);
416
+ return values?.length === 1 && values[0] === expected;
417
+ });
257
418
  const issuerContext = { issuer: settings.identity };
258
419
  const coordinatorFor = refreshCoordinatorsByPartition();
259
420
  // Keyed by request scope, so nothing here outlives the request that set it.
@@ -261,266 +422,575 @@ export function staticOAuth(id, config) {
261
422
  const rejectedScopes = new WeakSet();
262
423
  const scopeOf = (ctx) => ctx.requestScope ?? ctx;
263
424
  const warnShortSecret = shortSecretWarning();
264
- const providerFor = (ctx) => {
425
+ const providerFor = (ctx, requestOwned = false) => {
265
426
  warnShortSecret(settings.clientSecret, ctx.logger);
266
- return new StaticOAuthProvider(id, ctx, coordinatorFor(ctx), settings);
427
+ return new StaticOAuthProvider(id, ctx, coordinatorFor(ctx), settings, requestOwned);
267
428
  };
268
429
  /**
269
- * The global `fetch`, read per request, with the configured token-request
430
+ * The owned credential transport, with the configured token-request
270
431
  * headers laid over the SDK's own. With discovery and registration answered
271
432
  * from configuration the token endpoint is the only URL the SDK fetches here;
272
433
  * the check keeps the headers on it even if that ever changes.
273
434
  */
274
- const tokenEndpointFetch = async (ctx, input, init = {}) => {
275
- const url = new URL(input);
276
- if (settings.tokenRequestHeaders.length === 0 || `${url.origin}${url.pathname}` !== settings.identity) {
277
- trackRemoteClientRequest(sentSecretsFor(ctx), input, init);
278
- return byteReadResponse(await sentSecretsFetch(ctx)(input, { ...init, redirect: "manual" }));
279
- }
280
- const headers = new Headers(init.headers);
281
- for (const [name, value] of settings.tokenRequestHeaders)
282
- headers.set(name, value);
283
- for (const [, value] of settings.tokenRequestHeaders)
284
- sentSecretsFor(ctx).header(value);
285
- trackRemoteClientRequest(sentSecretsFor(ctx), input, { ...init, headers });
286
- return byteReadResponse(await sentSecretsFetch(ctx)(input, { ...init, headers, redirect: "manual" }));
287
- };
435
+ const tokenEndpointFetch = authMethod((secrets, input, init = {}) => {
436
+ return authFlow(function* () {
437
+ const url = new URL(input);
438
+ const request = { ...init, redirect: "manual" };
439
+ if (settings.tokenRequestHeaders.length && `${url.origin}${url.pathname}` === settings.identity) {
440
+ const headers = new Headers(init.headers);
441
+ for (const [name, value] of settings.tokenRequestHeaders)
442
+ headers.set(name, value);
443
+ for (const [name, value] of settings.tokenRequestHeaders)
444
+ if (!PUBLIC_TOKEN_REQUEST_HEADERS.get(name.toLowerCase())?.has(value))
445
+ secrets.header(value);
446
+ request.headers = headers;
447
+ }
448
+ trackRemoteClientRequest(secrets, input, request);
449
+ // The coordinator or code-exchange wrapper authorizes this send. Refresh
450
+ // claim submission may outlive the caller; this transport uses no
451
+ // request-owned capability and changes no closed resource-call ledger.
452
+ const dispatch = {
453
+ dispatched: false,
454
+ auxiliary: true,
455
+ provider: id,
456
+ method: request.method ?? "GET",
457
+ };
458
+ secrets.request(input, request);
459
+ const response = (yield authCall(sendCredentialPost, [
460
+ input,
461
+ request,
462
+ () => dispatchProviderRequest(undefined, dispatch, request.headers),
463
+ ]));
464
+ recordProviderResponse(dispatch, response.status);
465
+ return byteReadResponse(response);
466
+ });
467
+ });
288
468
  /**
289
469
  * The SDK's `auth()` over this provider. The token endpoint is fetched
290
470
  * through the refresh coordinator, with permanent spent fingerprints,
291
471
  * rotation commits, and re-consent verdicts. Credential-bearing requests
292
472
  * never follow redirects in either OAuth adapter.
293
473
  */
294
- const runAuth = (provider, ctx, exchange) => auth(provider, {
295
- serverUrl: settings.serverUrl,
296
- ...exchange,
297
- fetchFn: coordinatorFor(ctx).coordinatedFetch(provider, (input, init) => tokenEndpointFetch(ctx, input, init), ctx.signal, ctx.defer),
474
+ const runAuth = authMethod((provider, ctx, exchange, requestOwned = false) => {
475
+ return authFlow(function* () {
476
+ const secrets = sentSecretsFor(ctx);
477
+ return (yield authCall(auth, [
478
+ provider,
479
+ {
480
+ serverUrl: settings.serverUrl,
481
+ ...exchange,
482
+ fetchFn: coordinatorFor(ctx).coordinatedFetch(provider, authMethod((input, init) => {
483
+ return authFlow(function* () {
484
+ if (exchange) {
485
+ // A code exchange has no submitted refresh claim transferring
486
+ // ownership. Only explicit scope revocation stops a callback:
487
+ // the production callback route remains uninterruptible and
488
+ // does not revoke its own scope while exchanging a code.
489
+ return (yield authView(ctx, authMethod((signal) => {
490
+ return authFlow(function* () {
491
+ assertProviderCallOpen(ctx, id, true);
492
+ const response = (yield authCall(tokenEndpointFetch, [
493
+ secrets,
494
+ input,
495
+ {
496
+ ...init,
497
+ signal: init?.signal ? AbortSignal.any([init.signal, signal]) : signal,
498
+ },
499
+ ]));
500
+ // An override can ignore abort. Never hand its late tokens
501
+ // to the SDK, which would otherwise persist both credentials.
502
+ if (signal.aborted) {
503
+ void response.body?.cancel().catch(() => { });
504
+ throw signal.reason;
505
+ }
506
+ // The SDK clones and reads this body after fetch resolves.
507
+ // Revocation must cancel that read even for a custom fetch.
508
+ return codeExchangeResponse(response, signal);
509
+ });
510
+ }), true));
511
+ }
512
+ return (yield authCall(tokenEndpointFetch, [secrets, input, init]));
513
+ });
514
+ }), viewSignal(ctx, requestOwned || exchange !== undefined), ctx.defer, undefined, () => {
515
+ guardedTransport(true);
516
+ }),
517
+ },
518
+ ]));
519
+ });
298
520
  });
299
521
  // No cause, as in remoteMcp(): the `UnauthorizedError` class is the whole
300
522
  // verdict, and a logger rendering the chain renders whatever it says.
301
523
  const authRequiredError = () => new ConnectorCallError("downstream_oauth_required", `Connector "${id}" requires authorization — call authorize_connector({ connector: "${id}" }) and open the returned URL.`);
302
524
  const disconnectedMessage = `Connector "${id}" was disconnected by an operator — explicitly start authorization to reconnect it.`;
303
525
  /**
304
- * One request through the owner's grant. A 401 earns exactly one recovery:
305
- * a token another request has already rotated in, or else one coordinated
306
- * refresh. A second 401 is `auth_required`, latched for the request scope
307
- * so a program's later calls do not each spend a refresh on it.
526
+ * The token to send after `spent` drew a 401: one another request has
527
+ * already rotated in, or else one coordinated refresh.
308
528
  */
309
- const authorizedFetch = async (ctx, input, init = {}) => {
310
- const url = new URL(input);
311
- if (!settings.apiOrigins.has(url.origin)) {
312
- throw new ConnectorCallError("connector_call_failed", `Connector "${id}" sends its OAuth access token only to its declared ` +
313
- `apiOrigins, and ${url.origin} is not one of them.`, { retryable: false });
314
- }
315
- if (init.body instanceof ReadableStream) {
316
- throw new TypeError(`Connector "${id}": ctx.oauth.fetch may replay a request once after a ` +
317
- "refresh, so it cannot send a stream body. Buffer the body first.");
318
- }
319
- const headers = new Headers(init.headers);
320
- if (headers.has("authorization")) {
321
- throw new ConnectorCallError("connector_call_failed", `Connector "${id}": a request through ctx.oauth.fetch may not set ` +
322
- "Authorization; the connector's grant owns it.", { retryable: false });
323
- }
324
- const signal = init.signal && ctx.signal ? AbortSignal.any([init.signal, ctx.signal]) : (init.signal ?? ctx.signal);
325
- const send = async (accessToken) => {
326
- sentSecretsFor(ctx).add(accessToken);
327
- const sent = new Headers(headers);
328
- sent.set("Authorization", `Bearer ${accessToken}`);
329
- // The token rides to a declared origin and no further: a redirect is
330
- // handed back to the handler unfollowed rather than re-sent anywhere.
331
- // The handler reads the answer with `.json()` or `.text()`, which read
332
- // bytes here: workerd quotes a text read's non-text Content-Type in
333
- // its own log, out of the handler's reach.
334
- const response = await sentSecretsFetch(ctx)(url, {
335
- ...init,
336
- headers: sent,
337
- redirect: "manual",
338
- ...(signal ? { signal } : {}),
529
+ const renewedToken = authMethod((provider, ctx, spent, requestOwned = false) => {
530
+ return authFlow(function* () {
531
+ // Only renewal whose claim CAS was submitted may survive this call.
532
+ // Race each preparation read too, and never resume SDK work or consume a
533
+ // rotated token after closure, even if a storage promise completes late.
534
+ const live = authMethod((work) => {
535
+ return authFlow(function* () {
536
+ if (ctx.signal?.aborted)
537
+ throw ctx.signal.reason;
538
+ const value = (yield authView(ctx, work, requestOwned));
539
+ if (ctx.signal?.aborted)
540
+ throw ctx.signal.reason;
541
+ return value;
542
+ });
339
543
  });
340
- return byteReadResponse(response);
544
+ const current = (yield authCall(live, [
545
+ authMethod(() => {
546
+ return authInvoke(provider, "tokens", [issuerContext]);
547
+ }),
548
+ ]));
549
+ if (current && current.access_token !== spent)
550
+ return current.access_token;
551
+ if ((yield authCall(live, [
552
+ authMethod(() => {
553
+ return authCall(runAuth, [provider, ctx, undefined, requestOwned]);
554
+ }),
555
+ ])) !== "AUTHORIZED")
556
+ throw authRequiredError();
557
+ const refreshed = (yield authCall(live, [
558
+ authMethod(() => {
559
+ return authInvoke(provider, "tokens", [issuerContext]);
560
+ }),
561
+ ]));
562
+ if (!refreshed)
563
+ throw authRequiredError();
564
+ return refreshed.access_token;
565
+ });
566
+ });
567
+ /** One call's grant use; submitted refresh work keeps the coordinator's owner. */
568
+ const grantFor = (ctx, requestOwned = false) => {
569
+ let provider;
570
+ const use = authMethod((work) => {
571
+ return authView(ctx, authMethod((signal) => {
572
+ return authFlow(function* () {
573
+ assertProviderCallOpen(ctx, id, requestOwned);
574
+ if (rejectedScopes.has(scopeOf(ctx)))
575
+ throw authRequiredError();
576
+ const live = deriveContext(ctx, { signal });
577
+ const release = retainOAuthPartition(oauthPartitionFor(live));
578
+ try {
579
+ provider ??= new SharedRead(() => authView(live, authMethod(() => {
580
+ return authFlow(function* () {
581
+ const value = providerFor(live, requestOwned);
582
+ const epoch = (yield authInvoke(value, "beginFlow", []));
583
+ if (value.isOperatorDisconnectedEpoch(epoch)) {
584
+ throw new ConnectorCallError("downstream_oauth_required", disconnectedMessage);
585
+ }
586
+ return value;
587
+ });
588
+ }), requestOwned), undefined, ctx.requestScope ?? ctx);
589
+ return (yield authCall(work, [(yield provider.join(signal)), live]));
590
+ }
591
+ catch (error) {
592
+ if (signal.aborted)
593
+ throw signal.reason;
594
+ if (error instanceof UnauthorizedError) {
595
+ rejectedScopes.add(scopeOf(ctx));
596
+ throw authRequiredError();
597
+ }
598
+ throw error;
599
+ }
600
+ finally {
601
+ release();
602
+ }
603
+ });
604
+ }), requestOwned);
605
+ });
606
+ return {
607
+ token: authMethod(() => {
608
+ return authCall(use, [
609
+ authMethod((provider) => {
610
+ return authFlow(function* () {
611
+ const tokens = (yield authInvoke(provider, "tokens", [issuerContext]));
612
+ if (!tokens)
613
+ throw authRequiredError();
614
+ return tokens.access_token;
615
+ });
616
+ }),
617
+ ]);
618
+ }),
619
+ refresh: authMethod((stale) => {
620
+ return authCall(use, [
621
+ authMethod((provider, live) => {
622
+ return authCall(renewedToken, [provider, live, stale, requestOwned]);
623
+ }),
624
+ ]);
625
+ }),
626
+ rejected: authMethod(() => {
627
+ return authView(ctx, authMethod(() => {
628
+ return authValue(() => {
629
+ rejectedScopes.add(scopeOf(ctx));
630
+ throw authRequiredError();
631
+ });
632
+ }), requestOwned);
633
+ }),
341
634
  };
342
- if (rejectedScopes.has(scopeOf(ctx)))
343
- throw authRequiredError();
344
- const release = retainOAuthPartition(oauthPartitionFor(ctx));
345
- try {
346
- if (signal?.aborted)
347
- throw signal.reason;
635
+ };
636
+ /**
637
+ * One request through the owner's grant. A 401 earns exactly one recovery
638
+ * ({@link renewedToken}). A read is then sent once more; a write never is
639
+ * (INV-9), since a server may act on a request before it rejects the
640
+ * token. Whatever recovery meets is never reported as retryable: an
641
+ * authorization verdict keeps its code, and anything else (a disconnect or
642
+ * reset racing the refresh) leaves the outcome unknown. A second 401 is
643
+ * `auth_required`, latched for the request scope so a program's later
644
+ * calls do not each spend a refresh on it.
645
+ */
646
+ const authorizedFetch = authMethod((ctx, input, init = {}) => {
647
+ return authFlow(function* () {
648
+ let prepared = takeProviderHandoff(init);
649
+ const auxiliary = prepared?.auxiliary === true;
650
+ // Own the complete token preparation and fetch helper. The coordinator
651
+ // still owns a submitted refresh claim independently of this wait.
652
+ return (yield authView(ctx, authMethod((owned) => {
653
+ return authFlow(function* () {
654
+ // A call that already ended reads no token and sends nothing more.
655
+ assertProviderCallOpen(ctx, id, auxiliary);
656
+ const owner = prepared;
657
+ const url = new URL(input);
658
+ if (!settings.apiOrigins.has(url.origin)) {
659
+ throw new ConnectorCallError("connector_call_failed", `Connector "${id}" sends its OAuth access token only to its declared ` +
660
+ `apiOrigins, and ${url.origin} is not one of them.`, { retryable: false });
661
+ }
662
+ if (init.body instanceof ReadableStream) {
663
+ throw new TypeError(`Connector "${id}": ctx.oauth.fetch may replay a request once after a ` +
664
+ "refresh, so it cannot send a stream body. Buffer the body first.");
665
+ }
666
+ const headers = new Headers(init.headers);
667
+ if (headers.has("authorization")) {
668
+ throw new ConnectorCallError("connector_call_failed", `Connector "${id}": a request through ctx.oauth.fetch may not set ` +
669
+ "Authorization; the connector's grant owns it.", { retryable: false });
670
+ }
671
+ const signal = init.signal ? AbortSignal.any([init.signal, owned]) : owned;
672
+ ctx = deriveContext(ctx, { signal });
673
+ const detachAnswers = [];
674
+ const send = authMethod((accessToken) => {
675
+ return authFlow(function* () {
676
+ if (signal?.aborted)
677
+ throw signal.reason;
678
+ sentSecretsFor(ctx).add(accessToken);
679
+ const sent = new Headers(headers);
680
+ sent.set("Authorization", `Bearer ${accessToken}`);
681
+ // The token rides to a declared origin and no further: a redirect is
682
+ // handed back to the handler unfollowed rather than re-sent anywhere.
683
+ // The handler reads the answer with `.json()` or `.text()`, which read
684
+ // bytes here: workerd quotes a text read's non-text Content-Type in
685
+ // its own log, out of the handler's reach.
686
+ const request = {
687
+ ...init,
688
+ headers: sent,
689
+ redirect: "manual",
690
+ ...(signal ? { signal } : {}),
691
+ };
692
+ const dispatch = prepared ?? providerRequest(ctx, auxiliary, id);
693
+ prepared = undefined;
694
+ dispatchProviderRequest(ctx, dispatch, sent, request.method ?? "GET");
695
+ // A native waiter can close before fetch settles. This leaf
696
+ // observer records only immutable response facts, and cancels
697
+ // a late body; it never resumes request-owned preparation.
698
+ const pending = sentSecretsFetch(ctx)(url, request).then((response) => {
699
+ recordProviderResponse(dispatch, response.status);
700
+ if (response.status === 401) {
701
+ // Closure can settle the handler before renewal's catch maps
702
+ // this answer. Preserve its OAuth verdict in the ledger first;
703
+ // only the final write decider reports it. A live renewal keeps
704
+ // its own precise verdict and never replays a write.
705
+ const abandoned = () => {
706
+ vendorRefusal(unsentAfterUnauthorized(`Connector "${id}"'s API`, signal.reason, "downstream_oauth_required"), dispatch);
707
+ };
708
+ if (signal.aborted)
709
+ abandoned();
710
+ else {
711
+ signal.addEventListener("abort", abandoned, { once: true });
712
+ detachAnswers.push(() => signal.removeEventListener("abort", abandoned));
713
+ }
714
+ }
715
+ // A custom fetch may answer after revocation. Its physical status
716
+ // remains a fact, but its body belongs to no live helper or handler.
717
+ if (signal.aborted)
718
+ void response.body?.cancel().catch(() => { });
719
+ return response;
720
+ });
721
+ const response = (yield authValue(() => pending));
722
+ if (signal.aborted)
723
+ throw signal.reason;
724
+ const answer = byteReadResponse(response);
725
+ bindProviderResponse(answer, dispatch, owner);
726
+ return { response: answer, dispatch };
727
+ });
728
+ });
729
+ const release = retainOAuthPartition(oauthPartitionFor(ctx));
730
+ try {
731
+ if (signal?.aborted)
732
+ throw signal.reason;
733
+ const grant = grantFor(ctx, auxiliary);
734
+ const stale = (yield authInvoke(grant, "token", []));
735
+ const { response: first, dispatch } = (yield authCall(send, [stale]));
736
+ if (first.status !== 401)
737
+ return first;
738
+ try {
739
+ (yield authCall(readChallenge, [first, signal, init.method]));
740
+ }
741
+ catch (error) {
742
+ if (signal.aborted)
743
+ throw signal.reason;
744
+ if (!readRequest(dispatch)) {
745
+ throw vendorRefusal(unsentAfterUnauthorized(`Connector "${id}"'s API`, error, "downstream_oauth_required"), dispatch);
746
+ }
747
+ throw error;
748
+ }
749
+ if (!readRequest(dispatch)) {
750
+ let renewal;
751
+ try {
752
+ const fresh = (yield authInvoke(grant, "refresh", [stale]));
753
+ if (signal?.aborted)
754
+ throw signal.reason;
755
+ // The call's ledger keeps this 401's verdict, or that renewal
756
+ // replaced the token this request sent (Q8). The same token back
757
+ // renewed nothing.
758
+ renewedAfterUnauthorized(dispatch, stale, fresh);
759
+ if (fresh === stale)
760
+ renewal = authRequiredError();
761
+ }
762
+ catch (error) {
763
+ if (error instanceof UnauthorizedError)
764
+ rejectedScopes.add(scopeOf(ctx));
765
+ renewal = error instanceof UnauthorizedError ? authRequiredError() : error;
766
+ }
767
+ throw vendorRefusal(unsentAfterUnauthorized(`Connector "${id}"'s API`, renewal, "downstream_oauth_required"), dispatch);
768
+ }
769
+ const accessToken = (yield authInvoke(grant, "refresh", [stale]));
770
+ if (signal?.aborted)
771
+ throw signal.reason;
772
+ renewedAfterUnauthorized(dispatch, stale, accessToken);
773
+ const { response: second } = (yield authCall(send, [accessToken]));
774
+ if (second.status !== 401)
775
+ return second;
776
+ void second.body?.cancel().catch(() => { });
777
+ return (yield authInvoke(grant, "rejected", []));
778
+ }
779
+ catch (error) {
780
+ // The SDK can replace cancellation with its passive-consent refusal,
781
+ // and write recovery normally replaces errors with a 401 verdict. A
782
+ // closed capability must still fail promptly with its own close reason;
783
+ // the invocation's ledger retains the write's uncertainty and handoff.
784
+ if (signal?.aborted)
785
+ throw signal.reason;
786
+ if (error instanceof UnauthorizedError) {
787
+ rejectedScopes.add(scopeOf(ctx));
788
+ throw authRequiredError();
789
+ }
790
+ throw error;
791
+ }
792
+ finally {
793
+ for (const detach of detachAnswers)
794
+ detach();
795
+ release();
796
+ }
797
+ });
798
+ }), auxiliary));
799
+ });
800
+ });
801
+ const status = authMethod((ctx) => {
802
+ return authFlow(function* () {
803
+ if (rejectedScopes.has(scopeOf(ctx))) {
804
+ return {
805
+ state: "auth_required",
806
+ message: "Authorization required \u2014 the downstream rejected this connector's OAuth grant.",
807
+ };
808
+ }
809
+ // A status read never starts authorization: this provider cannot, and
810
+ // nothing here calls the SDK. A stored grant for the configured token
811
+ // endpoint is healthy until a call says otherwise — the downstream is
812
+ // asked nothing.
348
813
  const provider = providerFor(ctx);
349
- // Bind this call's reads and writes to the live epoch: a reset landing
350
- // meanwhile fails the call rather than handing it another flow's grant.
351
- const epoch = await provider.beginFlow();
352
- if (provider.isOperatorDisconnectedEpoch(epoch)) {
353
- throw new ConnectorCallError("downstream_oauth_required", disconnectedMessage);
814
+ if ((yield authInvoke(provider, "operatorDisconnected", []))) {
815
+ return { state: "auth_required", message: disconnectedMessage };
354
816
  }
355
- const tokens = await provider.tokens(issuerContext);
356
- if (!tokens)
357
- throw authRequiredError();
358
- const first = await send(tokens.access_token);
359
- if (first.status !== 401)
360
- return first;
361
- void first.body?.cancel().catch(() => { });
362
- let accessToken;
363
- const current = await provider.tokens(issuerContext);
364
- if (current && current.access_token !== tokens.access_token) {
365
- accessToken = current.access_token;
817
+ return (yield authInvoke(provider, "tokens", [issuerContext]))
818
+ ? { state: "ok" }
819
+ : { state: "auth_required", message: AUTH_REQUIRED_MESSAGE };
820
+ });
821
+ });
822
+ const startAuth = authMethod((original, opts) => {
823
+ return authFlow(function* () {
824
+ const ctx = authorizingContext(original);
825
+ const provider = providerFor(ctx);
826
+ if (ctx.signal?.aborted)
827
+ throw ctx.signal.reason;
828
+ const disconnected = opts?.force
829
+ ? false
830
+ : (yield authInvoke(provider, "operatorDisconnected", []));
831
+ if (ctx.signal?.aborted)
832
+ throw ctx.signal.reason;
833
+ if (opts?.force || disconnected) {
834
+ (yield authInvoke(provider, "resetAuthorization", []));
835
+ rejectedScopes.delete(scopeOf(original));
366
836
  }
367
837
  else {
368
- if ((await runAuth(provider, ctx)) !== "AUTHORIZED")
369
- throw authRequiredError();
370
- const refreshed = await provider.tokens(issuerContext);
371
- if (!refreshed)
372
- throw authRequiredError();
373
- accessToken = refreshed.access_token;
374
- }
375
- const second = await send(accessToken);
376
- if (second.status !== 401)
377
- return second;
378
- void second.body?.cancel().catch(() => { });
379
- rejectedScopes.add(scopeOf(ctx));
380
- throw authRequiredError();
381
- }
382
- catch (error) {
383
- if (error instanceof UnauthorizedError) {
384
- rejectedScopes.add(scopeOf(ctx));
385
- throw authRequiredError();
838
+ // Hand back a recent consent URL rather than overwrite the verifier
839
+ // the operator may be mid-consent on.
840
+ const pending = (yield authInvoke(provider, "reusablePendingAuthorizationUrl", []));
841
+ if (pending) {
842
+ return {
843
+ state: "auth_required",
844
+ authorizationUrl: pending,
845
+ authorizationReused: true,
846
+ message: AUTH_REQUIRED_MESSAGE,
847
+ };
848
+ }
386
849
  }
387
- throw error;
388
- }
389
- finally {
390
- release();
391
- }
392
- };
393
- const status = async (ctx) => {
394
- if (rejectedScopes.has(scopeOf(ctx))) {
395
- return {
396
- state: "auth_required",
397
- message: "Authorization required — the downstream rejected this connector's OAuth grant.",
398
- };
399
- }
400
- // A status read never starts authorization: this provider cannot, and
401
- // nothing here calls the SDK. A stored grant for the configured token
402
- // endpoint is healthy until a call says otherwise — the downstream is
403
- // asked nothing.
404
- const provider = providerFor(ctx);
405
- if (await provider.operatorDisconnected()) {
406
- return { state: "auth_required", message: disconnectedMessage };
407
- }
408
- return (await provider.tokens(issuerContext))
409
- ? { state: "ok" }
410
- : { state: "auth_required", message: AUTH_REQUIRED_MESSAGE };
411
- };
412
- const startAuth = async (original, opts) => {
413
- const ctx = authorizingContext(original);
414
- const provider = providerFor(ctx);
415
- if (ctx.signal?.aborted)
416
- throw ctx.signal.reason;
417
- const disconnected = opts?.force ? false : await provider.operatorDisconnected();
418
- if (ctx.signal?.aborted)
419
- throw ctx.signal.reason;
420
- if (opts?.force || disconnected) {
421
- await provider.resetAuthorization();
422
- rejectedScopes.delete(scopeOf(original));
423
- }
424
- else {
425
- // Hand back a recent consent URL rather than overwrite the verifier
426
- // the operator may be mid-consent on.
427
- const pending = await provider.reusablePendingAuthorizationUrl();
428
- if (pending) {
850
+ if (ctx.signal?.aborted)
851
+ throw ctx.signal.reason;
852
+ try {
853
+ // A grant from a since-repointed token endpoint reads as absent here,
854
+ // and the consent this start begins replaces it.
855
+ (yield authInvoke(provider, "beginFlow", []));
856
+ if ((yield authInvoke(provider, "tokens", [issuerContext]))) {
857
+ return { state: "ok", message: "Already authorized \u2014 connection is healthy." };
858
+ }
859
+ if ((yield authCall(runAuth, [provider, ctx])) === "AUTHORIZED") {
860
+ return { state: "ok", message: "Already authorized \u2014 connection is healthy." };
861
+ }
862
+ const authorizationUrl = (yield authInvoke(provider, "consentUrl", []));
429
863
  return {
430
864
  state: "auth_required",
431
- authorizationUrl: pending,
432
- authorizationReused: true,
865
+ ...(authorizationUrl !== undefined ? { authorizationUrl } : {}),
433
866
  message: AUTH_REQUIRED_MESSAGE,
434
867
  };
435
868
  }
436
- }
437
- if (ctx.signal?.aborted)
438
- throw ctx.signal.reason;
439
- try {
440
- // A grant from a since-repointed token endpoint reads as absent here,
441
- // and the consent this start begins replaces it.
442
- await provider.beginFlow();
443
- if (await provider.tokens(issuerContext)) {
444
- return { state: "ok", message: "Already authorized — connection is healthy." };
445
- }
446
- if ((await runAuth(provider, ctx)) === "AUTHORIZED") {
447
- return { state: "ok", message: "Already authorized — connection is healthy." };
869
+ catch (error) {
870
+ if (error instanceof UnauthorizedError) {
871
+ return { state: "auth_required", message: AUTH_REQUIRED_MESSAGE };
872
+ }
873
+ // Reaches the agent through authorize_connector. Text already in
874
+ // connecta's words passes with its advice to try again, since this is
875
+ // no write's failure (token responses are rebuilt before the SDK reads
876
+ // them); anything else is told from its record.
877
+ return {
878
+ state: "error",
879
+ message: error instanceof ConnectorCallError || error instanceof OAuthError
880
+ ? withResendAdvice(msg(error), resendAdviceOf(error))
881
+ : describeFailure(id, error),
882
+ };
448
883
  }
449
- const authorizationUrl = await provider.consentUrl();
450
- return {
451
- state: "auth_required",
452
- ...(authorizationUrl !== undefined ? { authorizationUrl } : {}),
453
- message: AUTH_REQUIRED_MESSAGE,
454
- };
455
- }
456
- catch (error) {
457
- if (error instanceof UnauthorizedError) {
458
- return { state: "auth_required", message: AUTH_REQUIRED_MESSAGE };
884
+ });
885
+ });
886
+ const disconnectAuth = authMethod((ctx) => {
887
+ return authFlow(function* () {
888
+ rejectedScopes.delete(scopeOf(ctx));
889
+ if (!settings.revocationEndpoint) {
890
+ (yield authInvoke(providerFor(ctx), "resetAuthorization", [true]));
891
+ return;
459
892
  }
460
- // Reaches the agent through authorize_connector. Text already in
461
- // connecta's words passes (token responses are rebuilt before the SDK
462
- // reads them); anything else is told from its record.
463
- return {
464
- state: "error",
465
- message: error instanceof ConnectorCallError || error instanceof OAuthError ? msg(error) : describeFailure(id, error),
466
- };
467
- }
468
- };
469
- const disconnectAuth = async (ctx) => {
470
- rejectedScopes.delete(scopeOf(ctx));
471
- await providerFor(ctx).resetAuthorization(true);
472
- };
473
- const verifyState = async (state, ctx) => {
474
- // The provider that verified the state captured its flow's epoch, and the
475
- // exchange must write under exactly that one.
476
- const provider = providerFor(ctx);
477
- callbackProviders.set(scopeOf(ctx), provider);
478
- return provider.verifyState(state);
479
- };
480
- const finishAuth = async (code, ctx, callbackParams) => {
481
- const provider = callbackProviders.get(scopeOf(ctx)) ?? providerFor(ctx);
482
- callbackProviders.delete(scopeOf(ctx));
483
- const authorizationCode = callbackParams?.get("code") ?? code;
484
- const iss = callbackParams?.get("iss") ?? undefined;
485
- // Every exchange names and claims its own consent, including without PKCE.
486
- const consent = callbackParams?.get("state") ?? null;
487
- if (!consent || !(await provider.verifyCallbackState(consent))) {
488
- throw new ConnectorCallError("connector_call_failed", `Connector "${id}" authorization callback matches no pending consent; nothing was exchanged.`);
489
- }
490
- provider.validateCallbackIssuer(callbackParams?.get("iss") ?? null);
491
- provider.exchanging(authorizationCode);
492
- await provider.bindFlow();
493
- const result = await runAuth(provider, ctx, {
494
- authorizationCode,
495
- ...(iss !== undefined ? { iss } : {}),
893
+ (yield authInvoke(providerFor(ctx), "disconnectAuthorization", [
894
+ authMethod((input, init = {}) => {
895
+ // Register the standard form's credentials before any JSON framing.
896
+ trackRemoteClientRequest(sentSecretsFor(ctx), input, init);
897
+ if (settings.revocationRequestFormat === "json" && init.body instanceof URLSearchParams) {
898
+ const params = new URLSearchParams(init.body);
899
+ params.delete("token_type_hint");
900
+ const headers = new Headers(init.headers);
901
+ headers.set("Content-Type", "application/json");
902
+ headers.set("Accept", "application/json");
903
+ init = { ...init, headers, body: JSON.stringify(Object.fromEntries(params)) };
904
+ }
905
+ return authCall(tokenEndpointFetch, [sentSecretsFor(ctx), input, init]);
906
+ }),
907
+ ]));
496
908
  });
497
- if (result !== "AUTHORIZED") {
498
- throw new UnauthorizedError("Failed to authorize");
499
- }
500
- rejectedScopes.delete(scopeOf(ctx));
501
- };
909
+ });
910
+ const verifyState = authMethod((state, ctx) => {
911
+ return authFlow(function* () {
912
+ // The provider that verified the state captured its flow's epoch, and the
913
+ // exchange must write under exactly that one.
914
+ const provider = providerFor(ctx);
915
+ callbackProviders.set(scopeOf(ctx), provider);
916
+ return (yield authInvoke(provider, "verifyState", [state]));
917
+ });
918
+ });
919
+ const finishAuth = authMethod((code, ctx, callbackParams) => {
920
+ return authView(ctx, authMethod(() => {
921
+ return authFlow(function* () {
922
+ const provider = callbackProviders.get(scopeOf(ctx)) ?? providerFor(ctx);
923
+ callbackProviders.delete(scopeOf(ctx));
924
+ const authorizationCode = callbackParams?.get("code") ?? code;
925
+ const iss = callbackParams?.get("iss") ?? undefined;
926
+ // Every exchange names and claims its own consent, including without PKCE.
927
+ const consent = callbackParams?.get("state") ?? null;
928
+ if (!consent || !(yield authInvoke(provider, "verifyCallbackState", [consent]))) {
929
+ throw new ConnectorCallError("connector_call_failed", `Connector "${id}" authorization callback matches no pending consent; nothing was exchanged.`);
930
+ }
931
+ if (!callbackParametersMatch(callbackParams)) {
932
+ throw new ConnectorCallError("connector_call_failed", `Connector "${id}" authorization callback did not match its configured parameters; nothing was exchanged.`);
933
+ }
934
+ provider.validateCallbackIssuer(callbackParams?.get("iss") ?? null);
935
+ provider.exchanging(authorizationCode);
936
+ (yield authInvoke(provider, "bindFlow", []));
937
+ const result = (yield authCall(runAuth, [
938
+ provider,
939
+ ctx,
940
+ {
941
+ authorizationCode,
942
+ ...(iss !== undefined ? { iss } : {}),
943
+ },
944
+ ]));
945
+ if (result !== "AUTHORIZED") {
946
+ throw new UnauthorizedError("Failed to authorize");
947
+ }
948
+ rejectedScopes.delete(scopeOf(ctx));
949
+ });
950
+ }), true);
951
+ });
502
952
  return {
503
- status: retainingOAuthPartition(status, 0),
504
- startAuth: retainingOAuthPartition(startAuth, 0),
505
- disconnectAuth: retainingOAuthPartition(disconnectAuth, 0),
506
- verifyState: retainingOAuthPartition(verifyState, 1),
507
- verifyCallbackIssuer: retainingOAuthPartition(async (issuer, ctx) => {
508
- const provider = callbackProviders.get(scopeOf(ctx));
509
- if (!provider)
510
- return false;
511
- provider.validateCallbackIssuer(issuer);
512
- return true;
513
- }, 1),
514
- consumeAuthError: retainingOAuthPartition(async (ctx) => {
515
- const provider = callbackProviders.get(scopeOf(ctx));
516
- callbackProviders.delete(scopeOf(ctx));
517
- if (!provider)
518
- throw new ConnectorCallError("connector_call_failed", "OAuth error callback matches no pending consent.");
519
- await provider.consumeAuthError();
520
- }, 0),
521
- finishAuth: retainingOAuthPartition(finishAuth, 1),
522
- access: (ctx) => ({
523
- fetch: (input, init) => authorizedFetch(ctx, input, init),
524
- }),
953
+ status: retainAuthPartition(status, 0),
954
+ startAuth: retainAuthPartition(startAuth, 0),
955
+ disconnectAuth: retainAuthPartition(disconnectAuth, 0),
956
+ verifyState: retainAuthPartition(verifyState, 1),
957
+ verifyCallbackParameters: retainAuthPartition(authMethod((params, _ctx) => authValue(() => callbackParametersMatch(params))), 1),
958
+ verifyCallbackIssuer: retainAuthPartition(authMethod((issuer, ctx) => {
959
+ return authValue(() => {
960
+ const provider = callbackProviders.get(scopeOf(ctx));
961
+ if (!provider)
962
+ return false;
963
+ provider.validateCallbackIssuer(issuer);
964
+ return true;
965
+ });
966
+ }), 1),
967
+ consumeAuthError: retainAuthPartition(authMethod((ctx) => {
968
+ return authFlow(function* () {
969
+ const provider = callbackProviders.get(scopeOf(ctx));
970
+ callbackProviders.delete(scopeOf(ctx));
971
+ if (!provider)
972
+ throw new ConnectorCallError("connector_call_failed", "OAuth error callback matches no pending consent.");
973
+ (yield authInvoke(provider, "consumeAuthError", []));
974
+ });
975
+ }), 0),
976
+ finishAuth: retainAuthPartition(finishAuth, 1),
977
+ access: (ctx) => bindRenewableGrant({
978
+ fetch: recordingFetch(authMethod((input, init) => {
979
+ return authCall(authorizedFetch, [ctx, input, init]);
980
+ })),
981
+ }, grantFor(ctx)),
525
982
  };
526
983
  }
984
+ // Hook calls keep the existing owner partition pinned while their native
985
+ // program settles. Each Promise shell enters its own registered program.
986
+ function retainAuthPartition(operation, contextIndex = 0) {
987
+ return authMethod((...args) => authFlow(function* () {
988
+ const release = retainOAuthPartition(oauthPartitionFor(args[contextIndex]));
989
+ try {
990
+ return (yield authCall(operation, args));
991
+ }
992
+ finally {
993
+ release();
994
+ }
995
+ }));
996
+ }