@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,1097 +0,0 @@
1
- import { chargeConnectorCall } from "../../call-admission-context.js";
2
- import { retryAfterMs } from "../../connectors/guarded-fetch.js";
3
- import { ConnectorCallError } from "../../errors.js";
4
- import { OperationIndex } from "../_shared/rest/operation-index.js";
5
- import { callRest, restCall, restTransport, } from "../_shared/rest/tools.js";
6
- import { inspectGraphqlQuery } from "./graphql.js";
7
- import { valueSafety, withholdEmbeddedUrlsIn, refuseWithheldLinks, refuseWithheldUpload, refuseWithheldBase64, } from "../_shared/rest/value-safety.js";
8
- import { CLOUDFLARE_VALUE_SAFETY } from "./value-safety.js";
9
- import { openapi } from "./openapi.generated.js";
10
- /** Cloudflare's v4 REST base. Override only for a proxy or a test double. */
11
- export const CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4";
12
- const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
13
- /** Zone-ownership reads a pin check may spend: per tool call, and per five minutes per connector. */
14
- const MAX_LOOKUPS_PER_CALL = 3;
15
- const MAX_LOOKUPS_PER_WINDOW = 60;
16
- const LOOKUP_WINDOW_MS = 300_000;
17
- const READ_CAP_MS = 60_000;
18
- /**
19
- * POSTs that only read, admitted by `cloudflare_api_read`, each reviewed
20
- * against the pinned spec for four disqualifiers: it persists state, it is
21
- * billed per call or runs a model, it sends traffic outside Cloudflare, or it
22
- * activates something. Requests that look like reads but are not stay writes:
23
- * D1 `…/query` and `…/raw` and Durable Object `…/query/v2` run SQL that can
24
- * write; Workers AI `…/ai/run`, AI Search and AutoRAG search (query rewriting
25
- * and reranking run billed models), browser rendering, web search and brand
26
- * logo search run models or fetch the web; monetization "checks" activate an
27
- * entitlement; flag evaluation is metered; robots.txt bulk reads fetch
28
- * origins; previews, tests, scans, exports and ownership challenges create
29
- * state or send traffic; and token or credential POSTs are refused outright
30
- * (`CLOUDFLARE_VALUE_SAFETY`).
31
- */
32
- const READ_POSTS = [
33
- [
34
- "POST",
35
- "/accounts/{account_id}/resource-library/applications/lookup",
36
- "Looks up application names in a sentence; changes no application or rule.",
37
- ],
38
- [
39
- "POST",
40
- "/accounts/{account_id}/data-security/posture/finding_types/validate",
41
- "Checks a candidate finding definition without creating or reserving it.",
42
- ],
43
- ["POST", "/accounts/{account_id}/analytics/query/{dataset}/summary", "Analytics query; aggregates only."],
44
- ["POST", "/accounts/{account_id}/analytics/query/{dataset}/timeseries", "Analytics query; aggregates only."],
45
- ["POST", "/accounts/{account_id}/analytics/query/{dataset}/top-n", "Analytics query; aggregates only."],
46
- [
47
- "POST",
48
- "/accounts/{account_id}/analytics/query/data-security/content-findings/top-n",
49
- "Analytics query; aggregates only.",
50
- ],
51
- [
52
- "POST",
53
- "/accounts/{account_id}/analytics/query/data-security/findings/summary",
54
- "Analytics query; aggregates only.",
55
- ],
56
- [
57
- "POST",
58
- "/accounts/{account_id}/analytics/query/data-security/findings/timeseries",
59
- "Analytics query; aggregates only.",
60
- ],
61
- [
62
- "POST",
63
- "/accounts/{account_id}/analytics_engine/sql",
64
- "Analytics Engine SQL is SELECT-only; the body is the query.",
65
- ],
66
- ["POST", "/accounts/{account_tag}/analytics/sql", "SQL API over analytics datasets; read-only by contract."],
67
- ["POST", "/analytics/sql", "SQL API over analytics datasets; read-only by contract."],
68
- ["POST", "/accounts/{account_id}/logs/explorer/query/sql", "Log Explorer SQL query; reads stored logs."],
69
- ["POST", "/zones/{zone_id}/logs/explorer/query/sql", "Log Explorer SQL query; reads stored logs."],
70
- ["POST", "/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/bulk/get", "Reads several KV values at once."],
71
- ["POST", "/accounts/{account_id}/vectorize/v2/indexes/{index_name}/query", "Nearest-vector query; no model runs."],
72
- ["POST", "/accounts/{account_id}/vectorize/v2/indexes/{index_name}/get_by_ids", "Reads vectors by id."],
73
- ["POST", "/accounts/{account_id}/workers/observability/telemetry/query", "Runs a telemetry query; saves nothing."],
74
- ["POST", "/accounts/{account_id}/workers/observability/telemetry/keys", "Lists telemetry keys."],
75
- ["POST", "/accounts/{account_id}/workers/observability/telemetry/values", "Lists telemetry values."],
76
- [
77
- "POST",
78
- "/accounts/{account_id}/browser-extension/config/logs/extension-events/search",
79
- "Searches extension event logs.",
80
- ],
81
- [
82
- "POST",
83
- "/accounts/{account_id}/browser-extension/config/logs/extension-events/timeseries",
84
- "Extension event aggregates.",
85
- ],
86
- ["POST", "/accounts/{account_id}/billable/usage", "Filtered usage read; Cloudflare documents it read-only."],
87
- ["POST", "/accounts/{account_id}/cloudforce-one/events", "Filters and lists threat events."],
88
- ["POST", "/accounts/{account_id}/cloudforce-one/requests", "Lists intelligence requests."],
89
- ["POST", "/accounts/{account_id}/cloudforce-one/requests/priority", "Lists priority intelligence requirements."],
90
- ["POST", "/accounts/{account_id}/cloudforce-one/requests/{request_id}/asset", "Lists request assets."],
91
- ["POST", "/accounts/{account_id}/cloudforce-one/requests/{request_id}/message", "Lists request messages."],
92
- [
93
- "POST",
94
- "/accounts/{account_id}/cloudforce-one/v2/brand-protection/takedown-notices/lookup",
95
- "Looks up takedown notices.",
96
- ],
97
- ["POST", "/accounts/{account_id}/cloudforce-one/v2/collections/{collection_id}/search", "Searches collection items."],
98
- ["POST", "/accounts/{account_id}/cloudforce-one/v2/priority-intelligence/quota", "Reads the PIR quota."],
99
- ["POST", "/accounts/{account_id}/cloudforce-one/rules/validate", "Validates a rule; stores nothing."],
100
- ["POST", "/accounts/{account_id}/cloudforce-one/rules/structured/validate", "Validates a rule; stores nothing."],
101
- ["POST", "/accounts/{account_id}/access/custom_pages/validate", "Validates a page template; stores nothing."],
102
- ["POST", "/accounts/{account_id}/dlp/patterns/validate", "Validates a regex; stores nothing."],
103
- ["POST", "/accounts/{account_id}/pipelines/v1/validate_sql", "Validates SQL; stores nothing."],
104
- ["POST", "/accounts/{account_id}/logpush/validate/origin", "Validates logpull options; stores nothing."],
105
- ["POST", "/zones/{zone_id}/logpush/validate/origin", "Validates logpull options; stores nothing."],
106
- ["POST", "/accounts/{account_id}/logpush/transformers/preview", "Stateless transformer preview."],
107
- ["POST", "/accounts/{account_id}/registrar/domain-check", "Domain availability check."],
108
- ["POST", "/accounts/{account_id}/registrar/domain-transfer-check", "Transfer eligibility check."],
109
- ["POST", "/accounts/{account_id}/registrar-sandbox/domain-check", "Domain availability check."],
110
- ["POST", "/accounts/{account_id}/pay-per-crawl/zones_can_be_enabled/query", "Reads a zone setting."],
111
- ["POST", "/accounts/{account_id}/ai-gateway/billing/topup/eligibility", "Eligibility check; starts nothing."],
112
- ["POST", "/accounts/{account_id}/ai-gateway/billing/topup/status", "Reads a payment status."],
113
- ["POST", "/accounts/{account_id}/request-tracer/trace", "Simulated trace; sends no traffic."],
114
- ["POST", "/accounts/{account_id}/magic/cloud/resources/policy-preview", "Evaluates a policy against the catalog."],
115
- ["POST", "/accounts/{account_id}/magic/cloud/onramps/{onramp_id}/export", "Renders Terraform; changes nothing."],
116
- ["POST", "/zones/{zone_id}/email/sending/subdomains/preview", "Previews DNS records; stores nothing."],
117
- ["POST", "/zones/{zone_id}/token_validation/rules/preview", "Previews a rule; stores nothing."],
118
- ];
119
- /**
120
- * Operations `cloudflare_api_upload` sends, by method and template: Workers
121
- * scripts and versions (including Workers for Platforms), KV values, R2
122
- * objects, Pages deployments, Images, Stream, DNS zone-file import, and
123
- * Snippets. Other multipart operations are refused rather than guessed at.
124
- */
125
- const UPLOADS = [
126
- ["PUT", "/accounts/{account_id}/workers/scripts/{script_name}"],
127
- ["PUT", "/accounts/{account_id}/workers/scripts/{script_name}/content"],
128
- ["PATCH", "/accounts/{account_id}/workers/scripts/{script_name}/settings"],
129
- ["POST", "/accounts/{account_id}/workers/scripts/{script_name}/versions"],
130
- ["PUT", "/accounts/{account_id}/workers/dispatch/namespaces/{dispatch_namespace}/scripts/{script_name}"],
131
- ["PUT", "/accounts/{account_id}/workers/dispatch/namespaces/{dispatch_namespace}/scripts/{script_name}/content"],
132
- ["PATCH", "/accounts/{account_id}/workers/dispatch/namespaces/{dispatch_namespace}/scripts/{script_name}/settings"],
133
- ["PUT", "/accounts/{account_id}/workers/services/{service_name}/environments/{environment_name}/content"],
134
- ["PUT", "/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/values/{key_name}"],
135
- ["PUT", "/accounts/{account_id}/r2/buckets/{bucket_name}/objects/{object_key}"],
136
- ["POST", "/accounts/{account_id}/pages/projects/{project_name}/deployments"],
137
- ["POST", "/accounts/{account_id}/images/v1"],
138
- ["POST", "/accounts/{account_id}/stream"],
139
- ["PUT", "/accounts/{account_id}/stream/{identifier}/captions/{language}"],
140
- ["POST", "/zones/{zone_id}/dns_records/import"],
141
- ["PUT", "/zones/{zone_id}/snippets/{snippet_name}"],
142
- ];
143
- /**
144
- * Operations with no account or zone in their path that a pinned connector
145
- * still admits, each holding to the pin some other way: the identity reads
146
- * name only the credential's own user, the three lists are filtered to the
147
- * pin, zone creation must name a pinned account, and the rest is public data.
148
- * Every other unscoped operation is refused under a pin (default deny).
149
- */
150
- export const PIN_SAFE_UNSCOPED = new Set([
151
- "GET /user",
152
- "GET /user/tokens/verify",
153
- "GET /accounts",
154
- "GET /zones",
155
- "POST /zones",
156
- "GET /memberships",
157
- "GET /ips",
158
- // CWS category definitions and filter-builder schema are global catalogs,
159
- // not account policy, credential, or deployment records.
160
- "GET /cws/categories/apis",
161
- "GET /cws/categories/applications",
162
- "GET /cws/categories/applications/{application_key}/versions",
163
- "GET /cws/categories/applications/{application_key}/versions/latest",
164
- "GET /cws/categories/applications/{application_key}/versions/{version_tag}",
165
- "GET /cws/categories/operations",
166
- "GET /cws/filter-schema",
167
- "POST /graphql",
168
- ]);
169
- /** Why each family of unscoped operations is refused under a pin; `test` checks every one is covered. */
170
- export const PIN_REFUSED_UNSCOPED = {
171
- accounts: "creates or moves accounts outside the pin",
172
- analytics: "queries datasets across every account; use POST /accounts/{account_tag}/analytics/sql",
173
- api: "is an internal health route",
174
- billing: "is user-level billing",
175
- certificates: "manages Origin CA certificates across every zone the user holds",
176
- internal: "is an internal route",
177
- live: "is an internal health route",
178
- memberships: "changes or reads one of the user's memberships, which may be another account's",
179
- oauth: "lists user-level OAuth scopes",
180
- organizations: "spans several accounts",
181
- pages: "uses a Pages upload token, not this connector's credential",
182
- produce: "writes to a pipeline the path does not scope",
183
- radar: "is not a GET",
184
- ready: "is an internal health route",
185
- "signed-url": "is an internal test route",
186
- subscriptions: "reads or acknowledges a stream the path does not scope",
187
- tenants: "spans several accounts",
188
- user: "acts on user-level settings shared by every account",
189
- workers: "triggers a deploy hook the path does not scope",
190
- zones: "is not a list or a create",
191
- };
192
- /** Request headers the generic tools accept; both are R2's. */
193
- const HEADERS = {
194
- "cf-r2-jurisdiction": "R2 only: default, eu, us, fedramp, or fedramp-high, for a bucket outside the default jurisdiction.",
195
- "cf-r2-storage-class": "R2 only: Standard or InfrequentAccess.",
196
- };
197
- const HEADER_VALUES = {
198
- "cf-r2-jurisdiction": ["default", "eu", "us", "fedramp", "fedramp-high"],
199
- "cf-r2-storage-class": ["Standard", "InfrequentAccess"],
200
- };
201
- /** Path parameters whose values may carry an encoded "/": R2 object keys and KV key names. */
202
- const SLASH_PARAMS = ["object_key", "key_name"];
203
- function record(value) {
204
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
205
- }
206
- function text(value) {
207
- return typeof value === "string" && value.trim() !== "" ? value : undefined;
208
- }
209
- function invalid(message) {
210
- throw new ConnectorCallError("invalid_args", message);
211
- }
212
- function errorsOf(body) {
213
- const errors = record(body)["errors"];
214
- return Array.isArray(errors)
215
- ? errors.filter((entry) => typeof entry === "object" && entry !== null)
216
- : [];
217
- }
218
- /** Flatten Cloudflare's error array (and any nested chain) into one line. */
219
- function describeErrors(errors) {
220
- const parts = [];
221
- const walk = (list) => {
222
- for (const entry of list) {
223
- const code = typeof entry.code === "number" ? entry.code : undefined;
224
- const message = typeof entry.message === "string" ? entry.message.slice(0, 500) : "Unknown error";
225
- parts.push(code === undefined ? message : `${code}: ${message}`);
226
- if (Array.isArray(entry.error_chain))
227
- walk(entry.error_chain);
228
- }
229
- };
230
- walk(errors.slice(0, 10));
231
- return parts.length > 0 ? parts.join("; ") : "Cloudflare reported no detail.";
232
- }
233
- function errorCodes(errors) {
234
- const codes = new Set();
235
- const walk = (list) => {
236
- for (const entry of list) {
237
- if (typeof entry.code === "number")
238
- codes.add(entry.code);
239
- if (Array.isArray(entry.error_chain))
240
- walk(entry.error_chain);
241
- }
242
- };
243
- walk(errors);
244
- return codes;
245
- }
246
- /**
247
- * Credential-shaped Cloudflare error codes that are *not* already implied by a
248
- * 401 or 403: a missing or malformed `Authorization` header, and the legacy
249
- * key/email headers. These arrive on HTTP 400, so status alone would misfile
250
- * them as an argument problem the agent could repair.
251
- *
252
- * Deliberately excludes 10000. Cloudflare returns 10000 for "Authentication
253
- * error" but has also been observed reusing it as a generic validation code,
254
- * so routing on it would risk telling an agent its token was broken when its
255
- * arguments were. Genuine 10000 auth failures arrive with 401 or 403.
256
- *
257
- * Cloudflare publishes no table mapping codes to causes; these six come from
258
- * community reports and probing. Prefer `verify_credential` when a diagnosis
259
- * matters. Cloudflare also rate-limits authentication failures separately
260
- * (429 with code 10502), one more reason to verify once rather than retry.
261
- */
262
- const AUTH_ERROR_CODES = new Set([1001, 6003, 6111, 9103, 9106, 9107]);
263
- /**
264
- * The reviewed value-safety table over the pinned index: refusals, redaction
265
- * of every successful body, and which operations withhold error text (any
266
- * reviewed operation, or one whose request accepts a credential, header, or
267
- * environment value, since a vendor error may echo what was submitted).
268
- */
269
- const SAFETY = valueSafety(CLOUDFLARE_VALUE_SAFETY, () => cloudflareIndex());
270
- /**
271
- * Map a failed Cloudflare response by the caller's next move (H11). For an
272
- * operation in a reviewed secret family the vendor's error text is withheld
273
- * (it may echo a submitted secret); the status and numeric codes still route.
274
- */
275
- function failureFor(status, headers, body, call) {
276
- const errors = errorsOf(withholdEmbeddedUrlsIn(body));
277
- const ray = headers.get("cf-ray");
278
- const codes = errorCodes(errors);
279
- const withheld = call !== undefined && SAFETY.withholdsErrors(call.op);
280
- const described = withheld
281
- ? `${codes.size ? `Cloudflare error code ${[...codes].join(", ")}.` : "Cloudflare reported an error."} Its text is withheld because this operation handles credentials.`
282
- : describeErrors(errors);
283
- const detail = `${described}${ray ? ` Ray ${ray}.` : ""}`;
284
- // 429 before the authentication codes: Cloudflare reuses 10000 on throttled
285
- // responses, and reading a rate limit as an auth failure would tell an
286
- // agent to stop when it should wait.
287
- if (status === 429) {
288
- return new ConnectorCallError("rate_limited", `Cloudflare rate limit reached (HTTP 429). ${detail} The documented limit is 1,200 requests per five minutes per user, counted across the dashboard and every token.`,
289
- // Cloudflare blocks the rest of the five-minute window when the global
290
- // limit trips, so without Retry-After the honest wait is the window.
291
- { retryAfterMs: retryAfterMs(headers) ?? 300_000 });
292
- }
293
- if (status === 401 || [...codes].some((code) => AUTH_ERROR_CODES.has(code))) {
294
- return new ConnectorCallError("auth_required", `Cloudflare rejected the configured credential (HTTP ${status}). ${detail} An operator must check or replace it in this connection; call verify_credential to confirm.`);
295
- }
296
- if (status === 403) {
297
- return new ConnectorCallError("provider_permission_denied", `Cloudflare refused access to this resource (HTTP 403). ${detail} Verify the credential first; if it is valid, the token or user lacks this permission or resource.`);
298
- }
299
- if (status === 400 || status === 409 || status === 422 || status === 405 || status === 415) {
300
- return new ConnectorCallError("invalid_args", `Cloudflare rejected the request (HTTP ${status}). ${detail}`);
301
- }
302
- // Cloudflare refuses a credential that may not touch a resource with 401
303
- // or 403, so a 404 is a real absence an agent can act on.
304
- if (status === 404) {
305
- return new ConnectorCallError("not_found", `Cloudflare found no such resource (HTTP 404). ${detail} Confirm the id with list_zones, list_accounts, or a list read.`);
306
- }
307
- if (status >= 500) {
308
- return new ConnectorCallError("unavailable", `Cloudflare is unavailable (HTTP ${status}). ${detail}`);
309
- }
310
- return new ConnectorCallError("connector_call_failed", `Cloudflare request failed (HTTP ${status}). ${detail}`, {
311
- retryable: false,
312
- });
313
- }
314
- function isEnvelope(body) {
315
- const value = record(body);
316
- return typeof value["success"] === "boolean" && ("result" in value || "errors" in value);
317
- }
318
- // --- Pins --------------------------------------------------------------------
319
- const ACCOUNT_KEYS = new Set(["account_id", "accountId", "account.id", "account_tag", "accountTag"]);
320
- const ZONE_KEYS = new Set(["zone_id", "zoneId", "zone.id", "zone_tag", "zoneTag"]);
321
- /** Every account and zone id a call names in its path, query, or top-level body. */
322
- function scopeIds(call) {
323
- const template = call.op.path.slice(1).split("/");
324
- const accounts = [];
325
- const zones = [];
326
- template.forEach((part, index) => {
327
- if (!part.startsWith("{"))
328
- return;
329
- const value = call.params[part.slice(1, -1)];
330
- if (value === undefined)
331
- return;
332
- if (template[index - 1] === "accounts")
333
- accounts.push(decodeURIComponent(value));
334
- if (template[index - 1] === "zones")
335
- zones.push(decodeURIComponent(value));
336
- });
337
- const scan = (source) => {
338
- for (const [key, value] of Object.entries(source)) {
339
- const values = Array.isArray(value) ? value : [value];
340
- for (const item of values) {
341
- if (typeof item !== "string")
342
- continue;
343
- if (ACCOUNT_KEYS.has(key))
344
- accounts.push(item);
345
- if (ZONE_KEYS.has(key))
346
- zones.push(item);
347
- }
348
- }
349
- };
350
- scan(record(call.query));
351
- const body = record(call.body);
352
- scan(body);
353
- const account = text(record(body["account"])["id"]);
354
- if (account)
355
- accounts.push(account);
356
- const zone = text(record(body["zone"])["id"]);
357
- if (zone)
358
- zones.push(zone);
359
- return { accounts, zones, template };
360
- }
361
- // --- Framing -----------------------------------------------------------------
362
- /** A pre-framed upload body, carried through `RestCall.body` to `encode`. */
363
- class RawUpload {
364
- body;
365
- contentType;
366
- constructor(body, contentType) {
367
- this.body = body;
368
- this.contentType = contentType;
369
- }
370
- }
371
- function bytesFromBase64(value, field) {
372
- try {
373
- const binary = atob(value);
374
- return Uint8Array.from(binary, (character) => character.charCodeAt(0));
375
- }
376
- catch {
377
- return invalid(`${field} must be valid base64.`);
378
- }
379
- }
380
- function queryOf(call) {
381
- const query = {};
382
- const scalar = (item) => typeof item === "string" || typeof item === "number" || typeof item === "boolean";
383
- for (const [name, value] of Object.entries(call.query)) {
384
- if (value === undefined)
385
- continue;
386
- if (scalar(value) || (Array.isArray(value) && value.every(scalar)))
387
- query[name] = value;
388
- else
389
- invalid(`Cloudflare query parameter ${name} takes a string, number, boolean, or a list of them.`);
390
- }
391
- return query;
392
- }
393
- // --- The vendor ----------------------------------------------------------------
394
- /** The pinned index, shared by every Cloudflare REST connector in the deployment. */
395
- let index;
396
- function cloudflareIndex() {
397
- index ??= new OperationIndex(openapi, { vendor: "cloudflare", title: "Cloudflare", slashParams: SLASH_PARAMS });
398
- return index;
399
- }
400
- const PAGE_SCHEMA = {
401
- type: "object",
402
- description: "Pass next as the param query argument for the following page.",
403
- properties: {
404
- hasMore: { type: "boolean", description: "Whether another page exists." },
405
- next: { type: "string", description: "The next page number or cursor." },
406
- param: { type: "string", description: "The query parameter that takes next." },
407
- },
408
- required: ["hasMore"],
409
- };
410
- const PAGING_INPUT = {
411
- page: { type: "integer", minimum: 1, maximum: 10_000, description: "Page number; default 1." },
412
- perPage: {
413
- type: "integer",
414
- minimum: 5,
415
- maximum: 50,
416
- description: "Results per page; default 20, Cloudflare's max 50.",
417
- },
418
- };
419
- function closed(properties, required = []) {
420
- return { type: "object", properties, required, additionalProperties: false };
421
- }
422
- /** Cloudflare's REST vendor, its named tools, and its credential tests. */
423
- export function cloudflareRest(options) {
424
- const operations = cloudflareIndex();
425
- const { auth, pin } = options;
426
- const pinnedAccounts = pin?.accountIds ? new Set(pin.accountIds) : undefined;
427
- const pinnedZones = pin?.zoneIds ? new Set(pin.zoneIds) : undefined;
428
- const send = restTransport({
429
- provider: "Cloudflare",
430
- baseUrl: options.baseUrl,
431
- maxResponseBytes: MAX_RESPONSE_BYTES,
432
- timeoutMs: READ_CAP_MS,
433
- authenticate: async (ctx) => {
434
- if (auth === "globalApiKey") {
435
- const values = await ctx.credential?.getAll();
436
- const email = values?.["email"]?.trim();
437
- const apiKey = values?.["apiKey"]?.trim();
438
- if (!email || !apiKey) {
439
- throw new ConnectorCallError("auth_required", "No Cloudflare Global API Key and account email are configured for this connector. An operator must add both in this connection in the operator UI.");
440
- }
441
- return { "X-Auth-Email": email, "X-Auth-Key": apiKey };
442
- }
443
- const token = (await ctx.credential?.get())?.trim();
444
- if (!token) {
445
- throw new ConnectorCallError("auth_required", "No Cloudflare API token is configured for this connector. An operator must add one in this connection in the operator UI.");
446
- }
447
- return { Authorization: `Bearer ${token}` };
448
- },
449
- });
450
- // --- Pin enforcement (default deny) ---------------------------------------
451
- /** Zone id → owning account id, learned from a GET /zones/{id} read or a zone list. */
452
- const owners = new Map();
453
- /** Times of internal ownership reads, bounded per rolling window. */
454
- const lookups = [];
455
- const lookupsPerCall = new WeakMap();
456
- const pinRefusal = (what) => {
457
- throw new ConnectorCallError("provider_permission_denied", `${what} is outside this connector's pin. The deployment pins it to ${[
458
- pinnedAccounts ? `accounts ${[...pinnedAccounts].join(", ")}` : "",
459
- pinnedZones ? `zones ${[...pinnedZones].join(", ")}` : "",
460
- ]
461
- .filter(Boolean)
462
- .join(" and ")}; a different account or zone needs its own connector.`, { retryable: false });
463
- };
464
- const assertAccount = (account) => {
465
- if (!pinnedAccounts?.has(account))
466
- pinRefusal(`Account ${account}`);
467
- };
468
- /**
469
- * Charge one internal ownership read: at most `MAX_LOOKUPS_PER_CALL` per
470
- * tool call and `MAX_LOOKUPS_PER_WINDOW` per five minutes per connector, so
471
- * one call cannot spend the user's shared request limit on pin checks.
472
- */
473
- const chargeLookup = (ctx) => {
474
- const used = lookupsPerCall.get(ctx) ?? 0;
475
- if (used >= MAX_LOOKUPS_PER_CALL) {
476
- throw new ConnectorCallError("invalid_args", `This call names more than ${MAX_LOOKUPS_PER_CALL} zones whose account the pin must check. Name fewer zones per call, or list them in pin.zoneIds.`);
477
- }
478
- const now = Date.now();
479
- while (lookups.length > 0 && lookups[0] <= now - LOOKUP_WINDOW_MS)
480
- lookups.shift();
481
- if (lookups.length >= MAX_LOOKUPS_PER_WINDOW) {
482
- throw new ConnectorCallError("rate_limited", "This connector's zone-ownership checks reached their per-window budget.", { retryAfterMs: Math.max(1_000, lookups[0] + LOOKUP_WINDOW_MS - now) });
483
- }
484
- lookups.push(now);
485
- lookupsPerCall.set(ctx, used + 1);
486
- };
487
- const assertZone = async (zone, ctx) => {
488
- if (pinnedZones?.has(zone))
489
- return;
490
- if (!pinnedAccounts)
491
- pinRefusal(`Zone ${zone}`);
492
- let owner = owners.get(zone);
493
- if (owner === undefined) {
494
- chargeLookup(ctx);
495
- await chargeConnectorCall(ctx);
496
- owner = await send({ method: "GET", path: `/zones/${encodeURIComponent(zone)}` }, ctx, async (response) => {
497
- const body = await response.jsonResult().catch(() => ({ value: undefined }));
498
- const value = "value" in body ? body.value : undefined;
499
- if (!response.ok)
500
- throw failureFor(response.status, response.headers, value);
501
- return text(record(record(record(value)["result"])["account"])["id"]) ?? "";
502
- });
503
- if (owner)
504
- owners.set(zone, owner);
505
- }
506
- if (!owner || !pinnedAccounts?.has(owner))
507
- pinRefusal(`Zone ${zone}`);
508
- };
509
- /**
510
- * Under a pin, an operation is admitted only if every account and zone it
511
- * names is pinned (zones by verified ownership), and an operation that names
512
- * neither is admitted only from the reviewed `PIN_SAFE_UNSCOPED` set.
513
- */
514
- const admit = async (call, ctx) => {
515
- if (call.method !== "GET" &&
516
- call.method !== "HEAD" &&
517
- !READ_POSTS.some(([method, path]) => method === call.method && path === call.op.path)) {
518
- refuseWithheldLinks([call.query, call.path], "Cloudflare");
519
- if (call.body instanceof RawUpload)
520
- await refuseWithheldUpload(call.body.body, "Cloudflare");
521
- else
522
- refuseWithheldLinks(call.body, "Cloudflare");
523
- if (call.method === "PUT" &&
524
- call.op.path === "/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/bulk" &&
525
- Array.isArray(call.body)) {
526
- for (const entry of call.body) {
527
- // KV values support 25 MiB decoded; MAX_RESPONSE_BYTES only caps reads.
528
- if (record(entry)["base64"] === true)
529
- await refuseWithheldBase64(record(entry)["value"], "Cloudflare", 25 * 1024 * 1024);
530
- }
531
- }
532
- }
533
- if (!pin)
534
- return;
535
- const { accounts, zones, template } = scopeIds(call);
536
- const scoped = template.some((part, index) => part.startsWith("{") && (template[index - 1] === "accounts" || template[index - 1] === "zones"));
537
- if (!scoped) {
538
- const key = `${call.op.method} ${call.op.path}`;
539
- const radar = call.op.method === "GET" && template[0] === "radar";
540
- if (!PIN_SAFE_UNSCOPED.has(key) && !radar) {
541
- const reason = PIN_REFUSED_UNSCOPED[template[0] ?? ""] ?? "names no account or zone this pin can verify";
542
- throw new ConnectorCallError("provider_permission_denied", `${key} ${reason}, so a pinned connector refuses it.`, { retryable: false });
543
- }
544
- if (key === "POST /zones") {
545
- // Cloudflare places the zone by body.account.id alone; other spellings are ignored.
546
- const account = text(record(record(call.body)["account"])["id"]);
547
- if (!account)
548
- invalid("A pinned connector creates a zone only in a pinned account: set body.account.id.");
549
- assertAccount(account);
550
- }
551
- }
552
- for (const account of accounts)
553
- assertAccount(account);
554
- for (const zone of zones)
555
- await assertZone(zone, ctx);
556
- };
557
- /** A pinned connector's account and zone lists show only what the pin admits. */
558
- const filterListed = (op, result) => {
559
- if (!pin || op.method !== "GET" || !Array.isArray(result))
560
- return result;
561
- if (op.path === "/accounts")
562
- return result.filter((item) => pinnedAccounts?.has(String(record(item)["id"])));
563
- if (op.path === "/memberships") {
564
- return result.filter((item) => pinnedAccounts?.has(String(record(record(item)["account"])["id"])));
565
- }
566
- if (op.path === "/zones") {
567
- return result.filter((item) => {
568
- const zone = record(item);
569
- const id = String(zone["id"]);
570
- const account = String(record(zone["account"])["id"]);
571
- if (account && id)
572
- owners.set(id, account);
573
- return pinnedZones?.has(id) || pinnedAccounts?.has(account);
574
- });
575
- }
576
- return result;
577
- };
578
- // --- Framing ---------------------------------------------------------------
579
- const encode = (call) => {
580
- const query = queryOf(call);
581
- const headers = { ...call.headers };
582
- if (call.body instanceof RawUpload) {
583
- if (call.body.contentType)
584
- headers["Content-Type"] = call.body.contentType;
585
- return { query, headers, rawBody: call.body.body };
586
- }
587
- if (call.body === undefined)
588
- return { query, headers };
589
- const type = (operations.bodyType(call.op) ?? "application/json").toLowerCase();
590
- if (/^text\/|javascript/.test(type)) {
591
- if (typeof call.body !== "string")
592
- invalid(`${call.op.method} ${call.op.path} takes a ${type} body: send it as a string.`);
593
- return { query, headers: { ...headers, "Content-Type": type }, rawBody: call.body };
594
- }
595
- if (/ndjson|jsonl/.test(type)) {
596
- const lines = typeof call.body === "string"
597
- ? call.body
598
- : Array.isArray(call.body)
599
- ? call.body.map((item) => JSON.stringify(item)).join("\n")
600
- : invalid(`${call.op.method} ${call.op.path} takes newline-delimited JSON: send a list of objects.`);
601
- return { query, headers: { ...headers, "Content-Type": type }, rawBody: lines };
602
- }
603
- if (type === "application/x-www-form-urlencoded") {
604
- const form = new URLSearchParams();
605
- for (const [name, value] of Object.entries(record(call.body))) {
606
- if (value !== undefined && value !== null)
607
- form.append(name, typeof value === "string" ? value : JSON.stringify(value));
608
- }
609
- return { query, headers: { ...headers, "Content-Type": type }, rawBody: form.toString() };
610
- }
611
- if (typeof call.body === "string")
612
- invalid(`${call.op.method} ${call.op.path} takes a JSON body, not a string.`);
613
- return {
614
- query,
615
- headers: { ...headers, ...(type.endsWith("+json") ? { "Content-Type": type } : {}) },
616
- body: call.body,
617
- };
618
- };
619
- /** Fill configured default ids into `{account_id}` and `{zone_id}` placeholders. */
620
- const fillDefaults = (path) => {
621
- if (!options.accountId && !options.zoneId)
622
- return path;
623
- const parts = path.split("/");
624
- return parts
625
- .map((part, position) => {
626
- const previous = parts[position - 1];
627
- if (options.accountId && previous === "accounts" && /^\{(account_id|account_tag|accountId)\}$/.test(part)) {
628
- return encodeURIComponent(options.accountId);
629
- }
630
- if (options.zoneId && previous === "zones" && /^\{(zone_id|zone_identifier|zoneId)\}$/.test(part)) {
631
- return encodeURIComponent(options.zoneId);
632
- }
633
- return part;
634
- })
635
- .join("/");
636
- };
637
- const scope = (call) => {
638
- // NDJSON operations take a list of records; frame it before validation,
639
- // so the index checks the string the vendor receives.
640
- const type = (operations.bodyType(call.op) ?? "").toLowerCase();
641
- if (/ndjson|jsonl/.test(type) && Array.isArray(call.body)) {
642
- if (!call.body.every((item) => typeof item === "object" && item !== null && !Array.isArray(item))) {
643
- invalid(`${call.op.method} ${call.op.path} takes newline-delimited JSON: send a list of objects.`);
644
- }
645
- call = { ...call, body: call.body.map((item) => JSON.stringify(item)).join("\n") };
646
- }
647
- for (const [name, value] of Object.entries(call.headers ?? {})) {
648
- if (!/^\/accounts\/\{[^/]+\}\/r2\//.test(call.op.path)) {
649
- invalid(`${name} applies only to R2 operations under /accounts/{account_id}/r2/.`);
650
- }
651
- if (!HEADER_VALUES[name]?.includes(value)) {
652
- invalid(`${name} must be one of ${HEADER_VALUES[name]?.join(", ") ?? "its documented values"}.`);
653
- }
654
- }
655
- return call;
656
- };
657
- /** Argument- and auth-dependent refusals; the table's own run in the generic tools. */
658
- const refuse = (call) => {
659
- const { method, op } = call;
660
- if (method === "GET")
661
- return undefined;
662
- if (auth === "globalApiKey" && /^\/(?:user|memberships)(?:\/|$)/.test(op.path)) {
663
- return "A Global API Key connector never writes user-level settings or memberships: the key is the user's own identity. Make that change in the Cloudflare dashboard.";
664
- }
665
- return undefined;
666
- };
667
- /** Next page: page numbers from result_info, or a cursor. */
668
- const page = (body, call) => {
669
- if (!isEnvelope(body))
670
- return undefined;
671
- const info = record(body["result_info"]);
672
- if (Object.keys(info).length === 0)
673
- return undefined;
674
- const rawCursor = text(info["cursor"]) ?? text(record(info["cursors"])["after"]);
675
- // Never echo or offer a modified cursor: it would not address the next page.
676
- const cursor = rawCursor === undefined || withholdEmbeddedUrlsIn(rawCursor) === rawCursor ? rawCursor : undefined;
677
- if (cursor !== undefined || "cursor" in info || "cursors" in info) {
678
- const accepted = operations.contract(call.op).parameters.map((parameter) => parameter.name);
679
- const param = accepted.includes("cursor") ? "cursor" : accepted.includes("after") ? "after" : "cursor";
680
- const truncated = info["is_truncated"];
681
- const hasMore = cursor !== undefined && truncated !== false && truncated !== "false";
682
- return { hasMore, ...(hasMore ? { next: cursor, param } : {}) };
683
- }
684
- const current = typeof info["page"] === "number" ? info["page"] : undefined;
685
- if (current === undefined)
686
- return undefined;
687
- const perPage = typeof info["per_page"] === "number" ? info["per_page"] : 0;
688
- const totalPages = typeof info["total_pages"] === "number" ? info["total_pages"] : undefined;
689
- const totalCount = typeof info["total_count"] === "number" ? info["total_count"] : undefined;
690
- const count = typeof info["count"] === "number" ? info["count"] : 0;
691
- const hasMore = totalPages !== undefined
692
- ? current < totalPages
693
- : totalCount !== undefined
694
- ? current * perPage < totalCount
695
- : perPage > 0 && count >= perPage;
696
- return { hasMore, ...(hasMore ? { next: String(current + 1), param: "page" } : {}) };
697
- };
698
- // Unwrap the v4 envelope; the shared value-safety pass then redacts the
699
- // result on every method and tool.
700
- const result = (body, call, response) => {
701
- if (!isEnvelope(body))
702
- return body;
703
- if (body["success"] === false)
704
- throw failureFor(response.status === 200 ? 400 : response.status, response.headers, body, call);
705
- return filterListed(call.op, body["result"]);
706
- };
707
- const vendor = {
708
- vendor: "cloudflare",
709
- title: "Cloudflare",
710
- index: operations,
711
- transport(server) {
712
- if (server === undefined)
713
- return send;
714
- return `This operation is served from ${server}, which this connector does not reach.`;
715
- },
716
- failure: failureFor,
717
- readPosts: READ_POSTS,
718
- valueSafety: SAFETY,
719
- refuse,
720
- path: fillDefaults,
721
- scope,
722
- admit,
723
- result,
724
- encode,
725
- page,
726
- headers: HEADERS,
727
- textBodies: true,
728
- pathExample: "/zones/023e105f4ecef8ad9ca31a8372d0c353/dns_records",
729
- upload: "Send multipart, binary, and file bodies with cloudflare_api_upload.",
730
- bodyHint: "a string for text, SQL, or script operations, a list of objects for NDJSON",
731
- };
732
- const verify = async (ctx) => {
733
- if (auth === "globalApiKey") {
734
- const { data } = await callRest(vendor, restCall(vendor, "GET", "/user"), ctx);
735
- const user = record(data);
736
- return { auth, status: "active", id: user["id"] ?? null, email: user["email"] ?? null, scope: "user" };
737
- }
738
- try {
739
- const { data } = await callRest(vendor, restCall(vendor, "GET", "/user/tokens/verify"), ctx);
740
- const token = record(data);
741
- return {
742
- auth,
743
- status: token["status"] ?? null,
744
- id: token["id"] ?? null,
745
- expiresOn: token["expires_on"] ?? null,
746
- scope: "user",
747
- };
748
- }
749
- catch (error) {
750
- // An account-owned token is verified under its account, not /user.
751
- const account = options.accountId ?? (pin?.accountIds?.length === 1 ? pin.accountIds[0] : undefined);
752
- if (!account || !(error instanceof ConnectorCallError) || error.code !== "auth_required")
753
- throw error;
754
- const { data } = await callRest(vendor, restCall(vendor, "GET", `/accounts/${encodeURIComponent(account)}/tokens/verify`), ctx);
755
- const token = record(data);
756
- return {
757
- auth,
758
- status: token["status"] ?? null,
759
- id: token["id"] ?? null,
760
- expiresOn: token["expires_on"] ?? null,
761
- scope: "account",
762
- accountId: account,
763
- };
764
- }
765
- };
766
- const withValues = (ctx, values) => ({
767
- ...ctx,
768
- credential: {
769
- get: async (field) => (field ? (values[field] ?? null) : (values["value"] ?? null)),
770
- getAll: async () => values,
771
- },
772
- });
773
- const credentialTest = async (ctx) => {
774
- try {
775
- const verified = await verify(ctx);
776
- if (auth === "globalApiKey")
777
- return { ok: true, message: `Global API Key verified for ${String(verified["email"])}.` };
778
- return verified["status"] === "active"
779
- ? {
780
- ok: true,
781
- message: `Token verified: active${verified["scope"] === "account" ? ` (account token for ${String(verified["accountId"])})` : ""}.`,
782
- }
783
- : { ok: false, message: `Token status is "${String(verified["status"])}".` };
784
- }
785
- catch (error) {
786
- return { ok: false, message: error instanceof Error ? error.message : "Cloudflare rejected the credential." };
787
- }
788
- };
789
- const projectZone = (value) => {
790
- const zone = record(value);
791
- const account = record(zone["account"]);
792
- return {
793
- id: zone["id"],
794
- name: zone["name"],
795
- status: zone["status"] ?? null,
796
- paused: zone["paused"] ?? null,
797
- type: zone["type"] ?? null,
798
- accountId: account["id"] ?? null,
799
- accountName: account["name"] ?? null,
800
- nameServers: Array.isArray(zone["name_servers"]) ? zone["name_servers"] : [],
801
- plan: record(zone["plan"])["name"] ?? null,
802
- };
803
- };
804
- const tools = [
805
- {
806
- name: "verify_credential",
807
- description: "Verify the configured Cloudflare credential and report its status. Call this first when another call fails with auth_required.",
808
- annotations: { readOnlyHint: true },
809
- inputSchema: closed({}),
810
- outputSchema: {
811
- type: "object",
812
- properties: {
813
- auth: { type: "string", enum: ["apiToken", "globalApiKey"] },
814
- status: { type: ["string", "null"], description: '"active" for a usable credential.' },
815
- id: { type: ["string", "null"] },
816
- email: { type: ["string", "null"], description: "The Global API Key's user." },
817
- expiresOn: { type: ["string", "null"] },
818
- scope: { type: "string", enum: ["user", "account"], description: "Who owns the token." },
819
- accountId: { type: "string" },
820
- },
821
- required: ["auth", "status", "scope"],
822
- },
823
- handler: async (_args, ctx) => await verify(ctx),
824
- },
825
- {
826
- name: "list_accounts",
827
- description: "List the Cloudflare accounts this credential reaches (only pinned ones when the connector is pinned). Supplies account ids for /accounts/{account_id} paths.",
828
- annotations: { readOnlyHint: true },
829
- inputSchema: closed({
830
- name: { type: "string", minLength: 1, maxLength: 200, description: "Exact account name." },
831
- ...PAGING_INPUT,
832
- }),
833
- outputSchema: {
834
- type: "object",
835
- properties: {
836
- accounts: {
837
- type: "array",
838
- items: {
839
- type: "object",
840
- properties: { id: { type: "string" }, name: { type: "string" }, type: { type: "string" } },
841
- },
842
- },
843
- page: PAGE_SCHEMA,
844
- },
845
- required: ["accounts"],
846
- },
847
- handler: async (args, ctx) => {
848
- const query = { page: args["page"] ?? 1, per_page: args["perPage"] ?? 20 };
849
- if (typeof args["name"] === "string")
850
- query["name"] = args["name"];
851
- const { data, page: next } = await callRest(vendor, restCall(vendor, "GET", "/accounts", { query }), ctx);
852
- return {
853
- accounts: (Array.isArray(data) ? data : []).map((item) => {
854
- const account = record(item);
855
- return { id: account["id"], name: account["name"], type: account["type"] ?? null };
856
- }),
857
- ...(next ? { page: next } : {}),
858
- };
859
- },
860
- },
861
- {
862
- name: "list_zones",
863
- description: "List Cloudflare zones (domains) by name, status, or account; only pinned ones when the connector is pinned. Supplies zone ids for /zones/{zone_id} paths.",
864
- annotations: { readOnlyHint: true },
865
- inputSchema: closed({
866
- name: { type: "string", minLength: 1, maxLength: 253, description: "Exact domain name, e.g. example.com." },
867
- status: {
868
- type: "string",
869
- enum: ["initializing", "pending", "active", "moved"],
870
- description: "Only zones in this status.",
871
- },
872
- accountId: { type: "string", minLength: 1, maxLength: 64, description: "Only zones in this account." },
873
- ...PAGING_INPUT,
874
- }),
875
- outputSchema: {
876
- type: "object",
877
- properties: {
878
- zones: {
879
- type: "array",
880
- items: {
881
- type: "object",
882
- properties: {
883
- id: { type: "string" },
884
- name: { type: "string" },
885
- status: { type: ["string", "null"] },
886
- accountId: { type: ["string", "null"] },
887
- nameServers: { type: "array" },
888
- },
889
- },
890
- },
891
- page: PAGE_SCHEMA,
892
- },
893
- required: ["zones"],
894
- },
895
- handler: async (args, ctx) => {
896
- const query = { page: args["page"] ?? 1, per_page: args["perPage"] ?? 20 };
897
- if (typeof args["name"] === "string")
898
- query["name"] = args["name"];
899
- if (typeof args["status"] === "string")
900
- query["status"] = args["status"];
901
- if (typeof args["accountId"] === "string")
902
- query["account.id"] = args["accountId"];
903
- const { data, page: next } = await callRest(vendor, restCall(vendor, "GET", "/zones", { query }), ctx);
904
- return { zones: (Array.isArray(data) ? data : []).map(projectZone), ...(next ? { page: next } : {}) };
905
- },
906
- },
907
- {
908
- name: "graphql_query",
909
- description: "Run a read-only GraphQL Analytics API query (POST /graphql). Mutations and subscriptions are refused before sending; filter by zoneTag or accountTag.",
910
- annotations: { readOnlyHint: true },
911
- inputSchema: closed({
912
- query: { type: "string", minLength: 1, maxLength: 20_000, description: "A GraphQL query document." },
913
- variables: { type: "object", description: "Variables the query declares, as JSON." },
914
- operationName: {
915
- type: "string",
916
- minLength: 1,
917
- maxLength: 200,
918
- description: "Which query to run when the document has several.",
919
- },
920
- }, ["query"]),
921
- outputSchema: {
922
- type: "object",
923
- properties: {
924
- data: { description: "The query result." },
925
- errors: { type: "array", description: "GraphQL errors, when Cloudflare returned partial data." },
926
- },
927
- required: ["data"],
928
- },
929
- handler: async (args, ctx) => {
930
- const source = String(args["query"]);
931
- const variables = record(args["variables"]);
932
- const scoped = inspectGraphqlQuery(source, variables, {
933
- strict: pin !== undefined,
934
- ...(typeof args["operationName"] === "string" ? { operationName: args["operationName"] } : {}),
935
- });
936
- if (pin) {
937
- if (scoped.openTags.length > 0) {
938
- invalid(`A pinned connector filters GraphQL by zoneTag or accountTag equality or _in, not ${scoped.openTags.join(", ")}.`);
939
- }
940
- if (scoped.unscopedFields.length > 0 || scoped.accountTags.length + scoped.zoneTags.length === 0) {
941
- invalid("A pinned connector's GraphQL queries filter every accounts and zones field by a pinned accountTag or zoneTag.");
942
- }
943
- for (const account of scoped.accountTags)
944
- assertAccount(account);
945
- for (const zone of scoped.zoneTags)
946
- await assertZone(zone, ctx);
947
- }
948
- const graphql = Object.freeze({
949
- method: "POST",
950
- path: "/graphql",
951
- operationId: "graphql",
952
- summary: "GraphQL Analytics API",
953
- tag: "graphql",
954
- server: undefined,
955
- row: -1,
956
- });
957
- const body = { query: source };
958
- if (args["variables"] !== undefined)
959
- body["variables"] = variables;
960
- if (typeof args["operationName"] === "string")
961
- body["operationName"] = args["operationName"];
962
- const response = await callRest({ ...vendor, encode: (call) => ({ body: call.body }), page: () => undefined }, { method: "POST", path: "/graphql", op: graphql, params: {}, query: {}, body }, ctx);
963
- const document = record(response.data);
964
- const errors = Array.isArray(document["errors"]) ? document["errors"] : [];
965
- if ((document["data"] === undefined || document["data"] === null) && errors.length > 0) {
966
- invalid(`Cloudflare GraphQL rejected the query: ${describeErrors(errors)}`);
967
- }
968
- return { data: document["data"] ?? null, ...(errors.length > 0 ? { errors } : {}) };
969
- },
970
- },
971
- {
972
- name: "cloudflare_api_upload",
973
- description: "Upload text, base64 bytes, or multipart parts to a reviewed Cloudflare upload operation. Covers Workers scripts, KV values, R2 objects, Pages, Images, Stream, DNS import, and Snippets; never reads local files.",
974
- annotations: { readOnlyHint: false, destructiveHint: true },
975
- inputSchema: closed({
976
- method: { type: "string", enum: ["POST", "PUT", "PATCH"], description: "The operation's HTTP method." },
977
- path: {
978
- type: "string",
979
- minLength: 2,
980
- maxLength: 2048,
981
- description: "Concrete path from cloudflare_api_search; encode a / inside an R2 or KV key as %2F.",
982
- },
983
- query: { type: "object", description: "Query parameters as JSON, named as details lists them." },
984
- headers: {
985
- type: "object",
986
- description: "Optional R2 headers.",
987
- properties: Object.fromEntries(Object.entries(HEADERS).map(([name, description]) => [
988
- name,
989
- { type: "string", minLength: 1, maxLength: 40, description },
990
- ])),
991
- additionalProperties: false,
992
- },
993
- contentType: {
994
- type: "string",
995
- minLength: 1,
996
- maxLength: 200,
997
- description: "Content-Type of a text or base64 body.",
998
- },
999
- textBody: { type: "string", description: "A UTF-8 body; exclusive with base64Body and parts." },
1000
- base64Body: { type: "string", description: "Body bytes as base64; exclusive with textBody and parts." },
1001
- parts: {
1002
- type: "array",
1003
- maxItems: 100,
1004
- description: "multipart/form-data parts; a part with fileName is sent as a file.",
1005
- items: {
1006
- type: "object",
1007
- properties: {
1008
- name: { type: "string", minLength: 1, maxLength: 200, description: "Form field name, e.g. metadata." },
1009
- text: { type: "string", description: "Text value; exclusive with base64." },
1010
- base64: { type: "string", description: "Bytes as base64; exclusive with text." },
1011
- fileName: {
1012
- type: "string",
1013
- minLength: 1,
1014
- maxLength: 500,
1015
- description: "Send as a file with this name.",
1016
- },
1017
- contentType: { type: "string", minLength: 1, maxLength: 200, description: "The part's Content-Type." },
1018
- },
1019
- required: ["name"],
1020
- additionalProperties: false,
1021
- },
1022
- },
1023
- }, ["method", "path"]),
1024
- outputSchema: {
1025
- type: "object",
1026
- properties: {
1027
- status: { type: "integer", description: "HTTP status." },
1028
- data: { description: "Cloudflare's result." },
1029
- },
1030
- required: ["status", "data"],
1031
- },
1032
- handler: async (args, ctx) => {
1033
- const method = String(args["method"]);
1034
- const prepared = restCall(vendor, method, String(args["path"]), {
1035
- query: record(args["query"]),
1036
- ...(Object.keys(record(args["headers"])).length
1037
- ? { headers: record(args["headers"]) }
1038
- : {}),
1039
- });
1040
- const refusal = SAFETY.refusal(prepared.method, prepared.op.path) ?? refuse(prepared);
1041
- if (refusal)
1042
- invalid(refusal);
1043
- if (!UPLOADS.some(([verb, template]) => verb === prepared.op.method && template === prepared.op.path)) {
1044
- invalid(`${prepared.op.method} ${prepared.op.path} is not a reviewed upload operation; JSON writes go through cloudflare_api_write.`);
1045
- }
1046
- const accepted = operations.contract(prepared.op).parameters.filter((parameter) => parameter.in === "query");
1047
- for (const name of Object.keys(prepared.query)) {
1048
- if (!accepted.some((parameter) => parameter.name === name)) {
1049
- invalid(`${prepared.op.path} has no query parameter ${name}; it accepts ${accepted.map((parameter) => parameter.name).join(", ") || "none"}.`);
1050
- }
1051
- }
1052
- const parts = Array.isArray(args["parts"]) ? args["parts"].map(record) : [];
1053
- const textBody = typeof args["textBody"] === "string" ? args["textBody"] : undefined;
1054
- const base64Body = typeof args["base64Body"] === "string" ? args["base64Body"] : undefined;
1055
- if (Number(parts.length > 0) + Number(textBody !== undefined) + Number(base64Body !== undefined) !== 1) {
1056
- invalid("cloudflare_api_upload needs exactly one body: textBody, base64Body, or parts.");
1057
- }
1058
- let upload;
1059
- if (parts.length > 0) {
1060
- const form = new FormData();
1061
- for (const part of parts) {
1062
- const name = String(part["name"]);
1063
- const value = typeof part["text"] === "string" ? part["text"] : undefined;
1064
- const bytes = typeof part["base64"] === "string" ? part["base64"] : undefined;
1065
- if (Number(value !== undefined) + Number(bytes !== undefined) !== 1) {
1066
- invalid(`Multipart part ${name} needs exactly one of text or base64.`);
1067
- }
1068
- const fileName = text(part["fileName"]);
1069
- const type = text(part["contentType"]);
1070
- if (fileName || type || bytes !== undefined) {
1071
- const blob = new Blob([value ?? bytesFromBase64(bytes, `part ${name}`)], {
1072
- type: type ?? "application/octet-stream",
1073
- });
1074
- form.append(name, blob, fileName ?? name);
1075
- }
1076
- else {
1077
- form.append(name, value);
1078
- }
1079
- }
1080
- upload = new RawUpload(form, undefined);
1081
- }
1082
- else {
1083
- upload = new RawUpload(textBody ?? bytesFromBase64(base64Body, "base64Body"), text(args["contentType"]) ??
1084
- (textBody !== undefined ? "text/plain; charset=utf-8" : "application/octet-stream"));
1085
- }
1086
- const { status, data } = await callRest(vendor, { ...prepared, body: upload }, ctx);
1087
- return { status, data };
1088
- },
1089
- },
1090
- ];
1091
- return {
1092
- vendor,
1093
- tools,
1094
- testCredential: async (value, ctx) => await credentialTest(withValues(ctx, { value: value.trim() })),
1095
- testCredentials: async (values, ctx) => await credentialTest(withValues(ctx, values)),
1096
- };
1097
- }