@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
@@ -0,0 +1,801 @@
1
+ // Google Docs named projections and bounded writes over the provider kit.
2
+ import { Effect, Option } from "effect";
3
+ import { canonicalJson } from "../../canonical-json.js";
4
+ import { ConnectorCallError } from "../../errors.js";
5
+ import { Refusal } from "../../http/errors.js";
6
+ import { fieldsProblem, selectFields } from "../../http/fields.js";
7
+ import { Recovery } from "../../http/services.js";
8
+ import { requestSentSecrets, redactBeforeCut } from "../../sent-secrets.js";
9
+ import { providerCallFailure } from "../../write-outcome.js";
10
+ import { googleClient, googleFailureMessage, googleOutcomeOf } from "../_shared/google/kit.js";
11
+ import { RESULT_BUDGET_BYTES, clampText, jsonBytes } from "../_shared/google/result-size.js";
12
+ import { DOCS_INDEX } from "./lib/discovery.js";
13
+ import { Operation } from "../../runtime/call.js";
14
+ import { boundCursor } from "../../http/paging.js";
15
+ import { read, write } from "../../http/tools.js";
16
+ import * as schemas from "./lib/schemas.js";
17
+ /**
18
+ * Rendered characters kept across a document's tabs by default: at most about
19
+ * 60 KB of JSON even in a three-byte script, so a default read crosses the
20
+ * 256 KiB execute_code bridge with room for its index page and tab rows.
21
+ */
22
+ const DEFAULT_MAX_CHARS = 20_000;
23
+ /**
24
+ * Serialized bytes of index rows in one page. Rows are a few hundred bytes at
25
+ * most, so a page is hundreds of them; a long document pages by cursor rather
26
+ * than returning more than the execute_code bridge carries.
27
+ */
28
+ const ELEMENT_PAGE_BYTES = 64 * 1024;
29
+ /** Characters of each index row's text preview. */
30
+ const PREVIEW_CHARS = 80;
31
+ function asRecord(value) {
32
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
33
+ }
34
+ function asArray(value) {
35
+ return Array.isArray(value) ? value : [];
36
+ }
37
+ function compact(value) {
38
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
39
+ }
40
+ function text(value) {
41
+ return typeof value === "string" && value !== "" ? value : undefined;
42
+ }
43
+ function integer(value) {
44
+ return typeof value === "number" && Number.isInteger(value) ? value : undefined;
45
+ }
46
+ /**
47
+ * Ids Google sends back are copied into results only if a tool could take
48
+ * them as input again — the same pattern and length the input schemas
49
+ * enforce. Anything else is dropped whole and named in the result's
50
+ * `dropped`, never cut: a shortened id addresses something else.
51
+ */
52
+ const ID_RULES = {
53
+ documentId: { pattern: /^[A-Za-z0-9_-]+$/, max: 256 },
54
+ revisionId: { pattern: /^\S+$/, max: 512 },
55
+ tabId: { pattern: /^[A-Za-z0-9._-]+$/, max: 128 },
56
+ };
57
+ function keptId(value, kind, name, dropped) {
58
+ const id = text(value);
59
+ if (id === undefined)
60
+ return undefined;
61
+ const rule = ID_RULES[kind];
62
+ if (id.length <= rule.max && rule.pattern.test(id))
63
+ return id;
64
+ dropped.push(name);
65
+ return undefined;
66
+ }
67
+ /** Titles are prose: cut to this many JSON bytes with a marker, never dropped. */
68
+ const TITLE_BYTES = 4 * 1024;
69
+ function boundedTitle(ctx, value) {
70
+ const title = text(value);
71
+ return title === undefined ? undefined : clampText(ctx, title, TITLE_BYTES, (more) => `… [${more} more characters]`);
72
+ }
73
+ function droppedField(dropped) {
74
+ return dropped.length > 0 ? [...new Set(dropped)].slice(0, 20) : undefined;
75
+ }
76
+ function documentUrl(documentId) {
77
+ return `https://docs.google.com/document/d/${encodeURIComponent(documentId)}/edit`;
78
+ }
79
+ /**
80
+ * What a tab holds that its text does not show, named so a reader never
81
+ * mistakes the rendering for the whole document: headers, footers, floating
82
+ * (positioned) images, in the inline view which text is a suggestion, and any
83
+ * element of a kind Google added after this rendering was written. Every
84
+ * paragraph element kind Docs v1 defines renders as something.
85
+ */
86
+ const NOT_RENDERED = ["headers", "footers", "positioned_objects", "suggestion_marks", "unknown_elements"];
87
+ const AUTO_TEXT = {
88
+ PAGE_NUMBER: "[page number]",
89
+ PAGE_COUNT: "[page count]",
90
+ };
91
+ function suggested(part) {
92
+ return asArray(part["suggestedInsertionIds"]).length > 0 || asArray(part["suggestedDeletionIds"]).length > 0;
93
+ }
94
+ const HEADINGS = {
95
+ TITLE: "# ",
96
+ SUBTITLE: "## ",
97
+ HEADING_1: "# ",
98
+ HEADING_2: "## ",
99
+ HEADING_3: "### ",
100
+ HEADING_4: "#### ",
101
+ HEADING_5: "##### ",
102
+ HEADING_6: "###### ",
103
+ };
104
+ /** Whether a list level numbers its items, from the tab's list definitions. */
105
+ function ordered(sources, bullet) {
106
+ const list = asRecord(sources.lists[String(bullet["listId"] ?? "")]);
107
+ const levels = asArray(asRecord(list["listProperties"])["nestingLevels"]);
108
+ const level = asRecord(levels[integer(bullet["nestingLevel"]) ?? 0]);
109
+ const glyph = text(level["glyphType"]);
110
+ return glyph !== undefined && glyph !== "NONE" && glyph !== "GLYPH_TYPE_UNSPECIFIED";
111
+ }
112
+ /**
113
+ * One paragraph element as text: a run's content, a link's text and target,
114
+ * a person's name, a rich link's title, and a bracketed marker for what has
115
+ * no text of its own.
116
+ */
117
+ function elementText(element, state) {
118
+ for (const part of Object.values(element)) {
119
+ if (part && typeof part === "object" && suggested(asRecord(part)))
120
+ state.marks.suggested = true;
121
+ }
122
+ const run = asRecord(element["textRun"]);
123
+ if (typeof run["content"] === "string") {
124
+ const url = text(asRecord(asRecord(run["textStyle"])["link"])["url"]);
125
+ return url ? { text: run["content"], url } : { text: run["content"] };
126
+ }
127
+ if (element["person"]) {
128
+ const person = asRecord(asRecord(element["person"])["personProperties"]);
129
+ return { text: text(person["name"]) ?? text(person["email"]) ?? "[person]" };
130
+ }
131
+ if (element["richLink"]) {
132
+ const link = asRecord(asRecord(element["richLink"])["richLinkProperties"]);
133
+ const uri = text(link["uri"]);
134
+ const title = text(link["title"]) ?? uri ?? "link";
135
+ return uri ? { text: title, url: uri } : { text: title };
136
+ }
137
+ if (element["footnoteReference"]) {
138
+ const reference = asRecord(element["footnoteReference"]);
139
+ const id = text(reference["footnoteId"]);
140
+ const number = text(reference["footnoteNumber"]) ?? String(state.footnotes.length + 1);
141
+ if (id && !state.footnotes.some((entry) => entry.id === id))
142
+ state.footnotes.push({ id, number });
143
+ return { text: `[^${number}]` };
144
+ }
145
+ if (element["inlineObjectElement"]) {
146
+ const id = String(asRecord(element["inlineObjectElement"])["inlineObjectId"] ?? "");
147
+ const embedded = asRecord(asRecord(asRecord(state.sources.inlineObjects[id])["inlineObjectProperties"])["embeddedObject"]);
148
+ const label = text(embedded["title"]) ?? text(embedded["description"]);
149
+ return { text: label ? `[image: ${label}]` : "[image]" };
150
+ }
151
+ if (element["dateElement"]) {
152
+ const date = asRecord(asRecord(element["dateElement"])["dateElementProperties"]);
153
+ return { text: text(date["displayText"]) ?? "[date]" };
154
+ }
155
+ if (element["dropdown"]) {
156
+ const dropdown = asRecord(asRecord(element["dropdown"])["dropdownProperties"]);
157
+ return { text: text(dropdown["displayValue"]) ?? "[dropdown: no selection]" };
158
+ }
159
+ if (element["autoText"]) {
160
+ return { text: AUTO_TEXT[String(asRecord(element["autoText"])["type"] ?? "")] ?? "[auto text]" };
161
+ }
162
+ if (element["horizontalRule"])
163
+ return { text: "---" };
164
+ // The API exposes no equation content, only that one is there.
165
+ if (element["equation"])
166
+ return { text: "[equation]" };
167
+ if (element["pageBreak"])
168
+ return { text: "[page break]" };
169
+ if (element["columnBreak"])
170
+ return { text: "[column break]" };
171
+ if (Object.keys(element).some((key) => key !== "startIndex" && key !== "endIndex")) {
172
+ state.marks.unknown = true;
173
+ }
174
+ return { text: "" };
175
+ }
176
+ /** Docs' soft line break (Shift+Enter) inside one paragraph. */
177
+ const VERTICAL_TAB = String.fromCharCode(0x0b);
178
+ /**
179
+ * A paragraph's text with its links as `[text](url)`. Adjacent runs that
180
+ * share a link become one link, and the paragraph's own closing newline is
181
+ * dropped; a soft line break (U+000B) becomes a newline.
182
+ */
183
+ function paragraphText(paragraph, state) {
184
+ const pieces = [];
185
+ for (const element of asArray(paragraph["elements"])) {
186
+ const piece = elementText(asRecord(element), state);
187
+ const last = pieces[pieces.length - 1];
188
+ if (last && last.url === piece.url)
189
+ last.text += piece.text;
190
+ else
191
+ pieces.push(piece);
192
+ }
193
+ const safe = requestSentSecrets(state.ctx).parts(pieces.map((piece) => piece.text));
194
+ for (const [index, piece] of pieces.entries()) {
195
+ piece.text = safe[index];
196
+ if (piece.url)
197
+ piece.url = redactBeforeCut(state.ctx, piece.url);
198
+ }
199
+ const lastPiece = pieces[pieces.length - 1];
200
+ if (lastPiece)
201
+ lastPiece.text = lastPiece.text.replace(/\n$/, "");
202
+ return pieces
203
+ .map((piece) => (piece.url && piece.text.trim() ? `[${piece.text}](${piece.url})` : piece.text))
204
+ .join("")
205
+ .replaceAll(VERTICAL_TAB, "\n");
206
+ }
207
+ /** Where to cut `value` near `max` UTF-16 units without splitting a surrogate pair. */
208
+ function safeCut(value, max) {
209
+ const code = value.charCodeAt(max - 1);
210
+ return max > 0 && code >= 0xd800 && code <= 0xdbff ? max - 1 : max;
211
+ }
212
+ function preview(ctx, value) {
213
+ // Collapsing whitespace can join a value that spans lines, so the flat text is checked too.
214
+ const flat = redactBeforeCut(ctx, redactBeforeCut(ctx, value).replace(/\s+/g, " ").trim());
215
+ return {
216
+ text: flat.length > PREVIEW_CHARS ? `${flat.slice(0, safeCut(flat, PREVIEW_CHARS))}…` : flat,
217
+ cut: flat.length > PREVIEW_CHARS,
218
+ };
219
+ }
220
+ /** Render structural elements as markdown-ish lines, collecting index rows. */
221
+ function renderContent(content, state, cell) {
222
+ const lines = [];
223
+ for (const value of asArray(content)) {
224
+ const element = asRecord(value);
225
+ const start = integer(element["startIndex"]) ?? 0;
226
+ const end = integer(element["endIndex"]) ?? start;
227
+ if (element["paragraph"]) {
228
+ const paragraph = asRecord(element["paragraph"]);
229
+ const style = text(asRecord(paragraph["paragraphStyle"])["namedStyleType"]);
230
+ const body = paragraphText(paragraph, state);
231
+ const bullet = paragraph["bullet"] ? asRecord(paragraph["bullet"]) : undefined;
232
+ let prefix = "";
233
+ if (bullet) {
234
+ prefix = `${" ".repeat(integer(bullet["nestingLevel"]) ?? 0)}${ordered(state.sources, bullet) ? "1." : "-"} `;
235
+ }
236
+ else if (style && HEADINGS[style] && body.trim()) {
237
+ prefix = HEADINGS[style];
238
+ }
239
+ lines.push(`${prefix}${body}`);
240
+ const peek = preview(state.ctx, body);
241
+ state.rows?.push(compact({
242
+ type: "paragraph",
243
+ startIndex: start,
244
+ endIndex: end,
245
+ style: style && style !== "NORMAL_TEXT" ? style : undefined,
246
+ row: cell?.row,
247
+ column: cell?.column,
248
+ text: peek.text,
249
+ previewCut: peek.cut || undefined,
250
+ }));
251
+ }
252
+ else if (element["table"]) {
253
+ state.rows?.push(compact({ type: "table", startIndex: start, endIndex: end, row: cell?.row, column: cell?.column }));
254
+ const rows = asArray(asRecord(element["table"])["tableRows"]);
255
+ rows.forEach((rowValue, rowIndex) => {
256
+ const cells = asArray(asRecord(rowValue)["tableCells"]).map((cellValue, columnIndex) => renderContent(asRecord(cellValue)["content"], state, { row: rowIndex, column: columnIndex })
257
+ .join("<br>")
258
+ .replace(/\n/g, "<br>")
259
+ .replace(/\|/g, "\\|")
260
+ .trim());
261
+ lines.push(`| ${cells.join(" | ")} |`);
262
+ if (rowIndex === 0)
263
+ lines.push(`|${cells.map(() => " --- |").join("")}`);
264
+ });
265
+ }
266
+ else if (element["tableOfContents"]) {
267
+ state.rows?.push(compact({ type: "table_of_contents", startIndex: start, endIndex: end }));
268
+ // The headings it lists are rendered where they stand.
269
+ lines.push("[Table of contents]");
270
+ }
271
+ else if (!element["sectionBreak"] && Object.keys(element).some((key) => !key.endsWith("Index"))) {
272
+ state.marks.unknown = true;
273
+ }
274
+ // A section break has no text and is not an edit target.
275
+ }
276
+ return lines;
277
+ }
278
+ /** One tab's body as text, with its footnotes after it. */
279
+ function renderTab(ctx, tab, sources, rows) {
280
+ const marks = { suggested: false, unknown: false };
281
+ const state = { ctx, sources, footnotes: [], rows, marks };
282
+ const lines = renderContent(asRecord(tab["body"])["content"], state);
283
+ const notes = state.footnotes.map(({ id, number }) => {
284
+ const footnote = asRecord(sources.footnotes[id]);
285
+ // Footnote content is its own segment: rendered, never indexed.
286
+ const inner = { ctx, sources, footnotes: [], rows: undefined, marks };
287
+ return `[^${number}]: ${renderContent(footnote["content"], inner).join(" ").trim()}`;
288
+ });
289
+ const present = (key) => Object.keys(asRecord(tab[key])).length > 0;
290
+ const notRendered = NOT_RENDERED.filter((part) => part === "suggestion_marks"
291
+ ? marks.suggested
292
+ : part === "unknown_elements"
293
+ ? marks.unknown
294
+ : present(part === "positioned_objects" ? "positionedObjects" : part));
295
+ return {
296
+ text: [...lines, ...(notes.length > 0 ? ["", ...notes] : [])]
297
+ .join("\n")
298
+ .replace(/\n{3,}/g, "\n\n")
299
+ .trim(),
300
+ notRendered,
301
+ };
302
+ }
303
+ /**
304
+ * Every tab, depth first in the order the document shows them. A response
305
+ * without tabs — a document read without `includeTabsContent` — is one
306
+ * untitled tab holding the legacy top-level body.
307
+ */
308
+ function flattenTabs(document) {
309
+ const tabs = [];
310
+ const visit = (value, depth) => {
311
+ const tab = asRecord(value);
312
+ const properties = asRecord(tab["tabProperties"]);
313
+ tabs.push({
314
+ tabId: text(properties["tabId"]),
315
+ title: text(properties["title"]),
316
+ parentTabId: text(properties["parentTabId"]),
317
+ nestingLevel: integer(properties["nestingLevel"]),
318
+ documentTab: asRecord(tab["documentTab"]),
319
+ });
320
+ if (depth > 20)
321
+ return;
322
+ for (const child of asArray(tab["childTabs"]))
323
+ visit(child, depth + 1);
324
+ };
325
+ for (const tab of asArray(document["tabs"]))
326
+ visit(tab, 0);
327
+ if (tabs.length === 0) {
328
+ tabs.push({
329
+ tabId: undefined,
330
+ title: undefined,
331
+ parentTabId: undefined,
332
+ nestingLevel: undefined,
333
+ documentTab: document,
334
+ });
335
+ }
336
+ return tabs;
337
+ }
338
+ /** The index just past a body's last element: insert before `endIndex - 1`. */
339
+ function endIndexOf(body) {
340
+ const content = asArray(asRecord(body)["content"]);
341
+ return integer(asRecord(content[content.length - 1])["endIndex"]);
342
+ }
343
+ /** Cut text at `max` characters, never inside a surrogate pair, and say so. Every secret the
344
+ * request sent is replaced first: a cut through an echo leaves a fragment nothing redacts. */
345
+ function capped(ctx, whole, max) {
346
+ if (whole.length <= max)
347
+ return { text: whole, textTruncated: false };
348
+ const value = redactBeforeCut(ctx, whole);
349
+ if (value.length <= max)
350
+ return { text: value, textTruncated: false };
351
+ const cut = safeCut(value, max);
352
+ const rest = value.length - cut;
353
+ return {
354
+ text: `${value.slice(0, cut)}${cut > 0 ? "\n" : ""}[… ${rest} more characters truncated; raise maxChars or pass tabId to read them]`,
355
+ textTruncated: true,
356
+ };
357
+ }
358
+ const SUGGESTION_MODES = {
359
+ inline: "SUGGESTIONS_INLINE",
360
+ accepted: "PREVIEW_SUGGESTIONS_ACCEPTED",
361
+ rejected: "PREVIEW_WITHOUT_SUGGESTIONS",
362
+ };
363
+ // --- Editing -----------------------------------------------------------------------
364
+ function batchPath(documentId) {
365
+ return `/documents/${encodeURIComponent(documentId)}:batchUpdate`;
366
+ }
367
+ /** The revision a batchUpdate left the document at, when Google reports a usable one. */
368
+ function revisionAfter(reply, dropped) {
369
+ return keptId(asRecord(reply["writeControl"])["requiredRevisionId"], "revisionId", "revisionId", dropped);
370
+ }
371
+ // No cause: what it would carry is a transport, stream, or parser error that can quote the reply (#695).
372
+ function uncertain(message, ctx) {
373
+ return providerCallFailure(ctx, { title: "Google Docs" }, new ConnectorCallError("connector_call_failed", message, { retryable: false }), {
374
+ dispatched: true,
375
+ status: 200,
376
+ fallback: "write",
377
+ });
378
+ }
379
+ function writeOutcome(error) {
380
+ const outcome = googleOutcomeOf(error);
381
+ switch (outcome?.phase) {
382
+ case "before-send":
383
+ return "not-sent";
384
+ case "refused": {
385
+ const status = outcome.status;
386
+ return status !== undefined && status >= 400 && status < 500 ? "refused" : "unknown";
387
+ }
388
+ case "reading-body":
389
+ return "probably-applied";
390
+ case "awaiting-response":
391
+ case "server-error":
392
+ case "redirected":
393
+ case "unauthorized":
394
+ default:
395
+ return "unknown";
396
+ }
397
+ }
398
+ /**
399
+ * One `documents.batchUpdate`, atomic on Google's side: every request applies
400
+ * or none does. A write naming `requiredRevisionId` is sent revision-guarded,
401
+ * so the shared client reads Google's own reason (FAILED_PRECONDITION or
402
+ * ABORTED on a 400 or 409) and answers a stale revision with `conflict`.
403
+ * Nothing here infers one: a revision read afterwards would prove nothing,
404
+ * since a collaborator may have edited in between. See {@link writeOutcome}
405
+ * for what every other failure means.
406
+ */
407
+ function batchUpdate(client, documentId, requests, requiredRevisionId, step) {
408
+ return client
409
+ .json({
410
+ method: "POST",
411
+ path: batchPath(documentId),
412
+ template: "/documents/{documentId}:batchUpdate",
413
+ body: compact({ requests, writeControl: requiredRevisionId ? { requiredRevisionId } : undefined }),
414
+ }, { ...(requiredRevisionId !== undefined ? { revisionGuarded: true } : {}), ...(step ? { step } : {}) })
415
+ .pipe(Effect.map(asRecord));
416
+ }
417
+ /** Characters of one projected reply string: an id or a short value, never prose. */
418
+ const REPLY_STRING_CHARS = 512;
419
+ /**
420
+ * One batchUpdate reply kept to what a later request needs: its kind, and
421
+ * the ids and counts beneath it to two levels. A string too long to be an id
422
+ * is dropped whole, never cut into a different one; `truncated` names
423
+ * the projected replies. Most replies are `{}` or one id; this only
424
+ * bites on the rare kind that echoes more.
425
+ */
426
+ function projectReply(value, depth = 0) {
427
+ const kept = {};
428
+ for (const [key, entry] of Object.entries(value)) {
429
+ if (typeof entry === "string") {
430
+ if (entry.length <= REPLY_STRING_CHARS)
431
+ kept[key] = entry;
432
+ }
433
+ else if (typeof entry === "number" || typeof entry === "boolean") {
434
+ kept[key] = entry;
435
+ }
436
+ else if (entry && typeof entry === "object" && !Array.isArray(entry) && depth < 2) {
437
+ kept[key] = projectReply(entry, depth + 1);
438
+ }
439
+ }
440
+ return kept;
441
+ }
442
+ /**
443
+ * A successful batch's result, inside the delivery budget whatever Google
444
+ * echoed. Replies come back whole when they fit; otherwise projected to ids
445
+ * and counts; otherwise the longest prefix that fits, so `replies[i]` still
446
+ * answers `requests[i]`. A cut result still says the batch applied — it did.
447
+ */
448
+ function boundedReplies(saved, replies) {
449
+ const whole = { ...saved, replies };
450
+ if (jsonBytes(whole) <= RESULT_BUDGET_BYTES)
451
+ return whole;
452
+ const projected = replies.map((reply) => projectReply(reply));
453
+ const slim = { ...saved, replies: projected, truncated: ["replies[]"] };
454
+ const notice = (shown) => `The batch applied. ${shown === replies.length ? "Every" : `${shown} of ${replies.length}`} replies ${shown === replies.length ? "is" : "are"} shown, projected to ids and counts, to stay inside the result budget. Do not repeat it; re-read the document with get_document for anything else.`;
455
+ if (jsonBytes({ ...slim, notice: notice(replies.length) }) <= RESULT_BUDGET_BYTES) {
456
+ return { ...slim, notice: notice(replies.length) };
457
+ }
458
+ // The longest prefix whose complete result — its own notice included —
459
+ // fits. JSON adds each element's bytes plus one comma between elements,
460
+ // so a candidate's size is exact without re-serializing it.
461
+ const sizes = projected.map((reply) => jsonBytes(reply));
462
+ const sizeWith = (count) => jsonBytes({ ...slim, replies: [], notice: notice(count) }) +
463
+ sizes.slice(0, count).reduce((sum, size) => sum + size, 0) +
464
+ Math.max(0, count - 1);
465
+ let count = 0;
466
+ while (count < projected.length && sizeWith(count + 1) <= RESULT_BUDGET_BYTES)
467
+ count += 1;
468
+ return { ...slim, replies: projected.slice(0, count), notice: notice(count) };
469
+ }
470
+ /** An edit's result: the document it addressed, and the revision it left, if usable. */
471
+ function savedEdit(documentId, reply) {
472
+ const dropped = [];
473
+ return compact({ documentId, revisionId: revisionAfter(reply, dropped), dropped: droppedField(dropped) });
474
+ }
475
+ // --- Tools -------------------------------------------------------------------------
476
+ /**
477
+ * Connecta's ceiling on a rendered read: inside the 8 MiB stash a
478
+ * direct call pages from, with room for its envelope.
479
+ */
480
+ const MAX_READ_RESULT_BYTES = 4 * 1024 * 1024;
481
+ /**
482
+ * The one check every result passes on its way out, whatever built it. The
483
+ * tools bound what they copy, so this is a backstop, not a plan. A read
484
+ * stopped here refuses with its way out; a write was already applied, so it
485
+ * is acknowledged with only its smallest facts and told not to repeat.
486
+ */
487
+ function delivered(name, args, result) {
488
+ const read = name === "get_document";
489
+ // A default read must also cross the execute_code bridge; an explicit
490
+ // maxChars is for a direct call, which the stash pages.
491
+ const explicit = read && args["maxChars"] !== undefined;
492
+ const limit = explicit ? MAX_READ_RESULT_BYTES : RESULT_BUDGET_BYTES;
493
+ const bytes = jsonBytes(result);
494
+ if (bytes <= limit)
495
+ return result;
496
+ if (read) {
497
+ throw new ConnectorCallError("invalid_args", `This document's result is ${bytes} bytes, past connecta's ${limit}-byte ceiling for it. Pass tabId for one tab, or a smaller maxChars.`);
498
+ }
499
+ const full = asRecord(result);
500
+ const small = {};
501
+ for (const key of ["documentId", "url", "occurrencesChanged"]) {
502
+ const value = full[key];
503
+ if (typeof value === "number" || (typeof value === "string" && value.length <= 1024))
504
+ small[key] = value;
505
+ }
506
+ if (name === "batch_update_document") {
507
+ small["replies"] = [];
508
+ small["truncated"] = ["replies[]"];
509
+ }
510
+ small["notice"] =
511
+ `Google Docs applied this ${name === "create_document" ? "create" : "edit"}, but its full result (${bytes} bytes) is too large to return. Do not repeat it; re-read the document with get_document.`;
512
+ return small;
513
+ }
514
+ export function docsTools({ connection }) {
515
+ const client = googleClient(connection, DOCS_INDEX);
516
+ return toolDefinitions(connection, client).map((tool) => {
517
+ const isRead = tool.annotations.readOnlyHint === true;
518
+ const spec = {
519
+ name: tool.name,
520
+ description: tool.description,
521
+ input: tool.input,
522
+ output: tool.output,
523
+ ...(tool.writeSteps ? { writeSteps: tool.writeSteps } : {}),
524
+ run: (args) => Effect.gen(function* () {
525
+ const fields = isRead ? args["fields"] : undefined;
526
+ const problem = isRead
527
+ ? fieldsProblem(fields, new Set(Object.keys(schemas.GET_DOCUMENT_OUTPUT.fields)), "the result")
528
+ : undefined;
529
+ if (problem)
530
+ return yield* Effect.fail(new Refusal({ error: problem }));
531
+ const operation = yield* Effect.serviceOption(Operation);
532
+ if (Option.isNone(operation) || !operation.value)
533
+ return yield* Effect.die(new Error("Docs needs a call context."));
534
+ const result = delivered(tool.name, args, yield* tool.run(args, operation.value));
535
+ if (!isRead)
536
+ return result;
537
+ const record = asRecord(result);
538
+ return {
539
+ ...asRecord(selectFields(record, fields)),
540
+ ...(record["page"] ? { page: record["page"] } : {}),
541
+ ...(record["truncated"] ? { truncated: record["truncated"] } : {}),
542
+ };
543
+ }).pipe(Effect.catchDefect((defect) => defect instanceof ConnectorCallError ? Effect.fail(new Refusal({ error: defect })) : Effect.die(defect))),
544
+ };
545
+ return isRead
546
+ ? read(connection, spec)
547
+ : write(connection, { ...spec, destructive: tool.annotations.destructiveHint !== false });
548
+ });
549
+ }
550
+ function toolDefinitions(connection, client) {
551
+ const revision = (args) => typeof args["requiredRevisionId"] === "string" ? args["requiredRevisionId"] : undefined;
552
+ return [
553
+ {
554
+ name: "get_document",
555
+ description: "Get one Google Doc as readable markdown-ish text per tab, with its title and revisionId; withIndexes adds the start/end indexes edits target. Cannot search or list documents.",
556
+ annotations: { readOnlyHint: true },
557
+ input: schemas.GET_DOCUMENT_INPUT,
558
+ output: schemas.GET_DOCUMENT_OUTPUT,
559
+ run: (args, ctx) => Effect.gen(function* () {
560
+ const documentId = String(args["documentId"]);
561
+ if (args["cursor"] !== undefined && args["withIndexes"] !== true) {
562
+ throw new ConnectorCallError("invalid_args", "cursor pages index rows; send it with withIndexes: true.");
563
+ }
564
+ const binding = { ...args, fields: undefined };
565
+ let from = 0;
566
+ let expectedSnapshot;
567
+ if (typeof args["cursor"] === "string") {
568
+ const payload = yield* boundCursor.decode("get_document", binding, args["cursor"]);
569
+ let parsed;
570
+ try {
571
+ parsed = JSON.parse(payload);
572
+ }
573
+ catch {
574
+ throw new ConnectorCallError("invalid_args", "Invalid document cursor; start a fresh indexed read.");
575
+ }
576
+ const claim = asRecord(parsed);
577
+ if (typeof claim["snapshot"] !== "string" || !Number.isSafeInteger(claim["from"]) || claim["from"] <= 0)
578
+ throw new ConnectorCallError("invalid_args", "Invalid document cursor position.");
579
+ from = claim["from"];
580
+ expectedSnapshot = claim["snapshot"];
581
+ }
582
+ const suggestions = typeof args["suggestions"] === "string" ? SUGGESTION_MODES[args["suggestions"]] : undefined;
583
+ const document = asRecord(yield* client.json({
584
+ method: "GET",
585
+ path: `/documents/${encodeURIComponent(documentId)}`,
586
+ template: "/documents/{documentId}",
587
+ query: { includeTabsContent: true, suggestionsViewMode: suggestions },
588
+ }));
589
+ let tabs = flattenTabs(document);
590
+ const wanted = typeof args["tabId"] === "string" ? args["tabId"] : undefined;
591
+ if (wanted !== undefined) {
592
+ tabs = tabs.filter((tab) => tab.tabId === wanted);
593
+ if (tabs.length === 0) {
594
+ const known = flattenTabs(document)
595
+ .map((tab) => keptId(tab.tabId, "tabId", "tabId", []))
596
+ .filter((id) => id !== undefined);
597
+ throw new ConnectorCallError("not_found", `This document has no tab ${wanted}. Its tabs are: ${known.slice(0, 20).join(", ") || "none listed"}${known.length > 20 ? ", …" : ""}.`);
598
+ }
599
+ }
600
+ const withIndexes = args["withIndexes"] === true;
601
+ let budget = typeof args["maxChars"] === "number" ? args["maxChars"] : DEFAULT_MAX_CHARS;
602
+ // Index rows form one sequence across tabs; a page is the rows from
603
+ // the cursor's ordinal until the byte budget is spent.
604
+ const snapshot = withIndexes
605
+ ? yield* Effect.promise(async () => {
606
+ const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalJson(document)));
607
+ return Array.from(new Uint8Array(hash), (byte) => byte.toString(16).padStart(2, "0")).join("");
608
+ })
609
+ : undefined;
610
+ if (expectedSnapshot !== undefined && expectedSnapshot !== snapshot)
611
+ throw new ConnectorCallError("conflict", "This document changed since the indexed read. Start again without a cursor and plan edits at the new revision.");
612
+ let ordinal = 0;
613
+ let pageBytes = 0;
614
+ let next;
615
+ const dropped = [];
616
+ const truncated = [];
617
+ const title = boundedTitle(ctx, document["title"]);
618
+ if (title !== undefined && title !== document["title"])
619
+ truncated.push("title");
620
+ const rendered = tabs.map((tab, index) => {
621
+ const rows = withIndexes ? [] : undefined;
622
+ const sources = {
623
+ lists: asRecord(tab.documentTab["lists"]),
624
+ footnotes: asRecord(tab.documentTab["footnotes"]),
625
+ inlineObjects: asRecord(tab.documentTab["inlineObjects"]),
626
+ };
627
+ const { text: full, notRendered } = renderTab(ctx, tab.documentTab, sources, rows);
628
+ const cut = capped(ctx, full, budget);
629
+ const tabTitle = boundedTitle(ctx, tab.title);
630
+ if (tabTitle !== undefined && tabTitle !== tab.title)
631
+ truncated.push(`tabs[${index}].title`);
632
+ budget = Math.max(0, budget - full.length);
633
+ if (cut.textTruncated)
634
+ truncated.push(`tabs[${index}].text`);
635
+ let elements;
636
+ if (rows) {
637
+ elements = [];
638
+ for (const row of rows) {
639
+ const at = ordinal;
640
+ ordinal += 1;
641
+ if (at < from || next !== undefined)
642
+ continue;
643
+ const size = jsonBytes(row) + 1;
644
+ if (pageBytes + size > ELEMENT_PAGE_BYTES) {
645
+ next = at;
646
+ continue;
647
+ }
648
+ pageBytes += size;
649
+ const { previewCut, ...kept } = row;
650
+ if (previewCut)
651
+ truncated.push(`tabs[${index}].elements[].text`);
652
+ elements.push(kept);
653
+ }
654
+ }
655
+ return compact({
656
+ tabId: keptId(tab.tabId, "tabId", `tabs[${index}].tabId`, dropped),
657
+ title: tabTitle,
658
+ parentTabId: keptId(tab.parentTabId, "tabId", `tabs[${index}].parentTabId`, dropped),
659
+ nestingLevel: tab.nestingLevel,
660
+ endIndex: endIndexOf(tab.documentTab["body"]),
661
+ text: cut.text,
662
+ notRendered: notRendered.length > 0 ? notRendered : undefined,
663
+ elements,
664
+ });
665
+ });
666
+ if (from > ordinal)
667
+ throw new ConnectorCallError("invalid_args", "Invalid document cursor position.");
668
+ const nextCursor = next === undefined
669
+ ? undefined
670
+ : yield* boundCursor.encode("get_document", binding, JSON.stringify({ snapshot, from: next }));
671
+ return compact({
672
+ documentId,
673
+ title,
674
+ revisionId: keptId(document["revisionId"], "revisionId", "revisionId", dropped),
675
+ url: documentUrl(documentId),
676
+ dropped: droppedField(dropped),
677
+ tabs: rendered,
678
+ truncated: truncated.length ? [...new Set(truncated)] : undefined,
679
+ page: withIndexes ? compact({ hasMore: next !== undefined, nextCursor }) : undefined,
680
+ });
681
+ }),
682
+ },
683
+ {
684
+ name: "create_document",
685
+ writeSteps: { create: "once", initialText: "once" },
686
+ description: "Create a new Google Doc in the user's My Drive root with a title and optional plain-text body. Cannot choose a folder, apply a template, or copy a document.",
687
+ // Additive: a new document changes nothing that existed. Not read-only,
688
+ // so program calls require a writable endpoint; other writes use
689
+ // call_destructive_tool. The host controls approval.
690
+ annotations: { readOnlyHint: false, destructiveHint: false },
691
+ input: schemas.CREATE_DOCUMENT_INPUT,
692
+ output: schemas.CREATE_DOCUMENT_OUTPUT,
693
+ run: (args, ctx) => Effect.gen(function* () {
694
+ // Google's create takes a title and ignores any content, so a body is
695
+ // a second, separate edit. Creating is not idempotent: every failure
696
+ // that may have left a document behind says so and is not retryable.
697
+ const title = String(args["title"]);
698
+ const createdButUnknown = `Google Docs created the document (HTTP 2xx), but its reply could not be read, so its id is unknown. Do not create it again; find "${title}" in Drive (a Google Drive connection's search) and use that id.`;
699
+ const created = yield* client
700
+ .json({ method: "POST", path: "/documents", template: "/documents", body: { title } }, { step: "create" })
701
+ .pipe(Effect.map(asRecord), Effect.catch((failure) => !ctx.signal?.aborted && writeOutcome(failure) === "probably-applied"
702
+ ? googleFailureMessage(connection, failure, () => createdButUnknown)
703
+ : Effect.fail(failure)));
704
+ const dropped = [];
705
+ // An id that could not be passed back is no id at all.
706
+ const documentId = keptId(created["documentId"], "documentId", "documentId", dropped);
707
+ if (!documentId)
708
+ throw uncertain(createdButUnknown, ctx);
709
+ yield* (yield* Recovery).record({ documentId });
710
+ let revisionId = keptId(created["revisionId"], "revisionId", "revisionId", dropped);
711
+ if (typeof args["text"] === "string") {
712
+ const reply = yield* batchUpdate(client, documentId, [{ insertText: { text: args["text"], endOfSegmentLocation: {} } }], undefined, "initialText").pipe(Effect.catch((cause) => {
713
+ // The document exists now. A retry of this call would make a
714
+ // second one, so the failure names it and is not retryable. Only
715
+ // an explicit refusal proves the text is absent; anything else
716
+ // sends the caller to look before appending.
717
+ return googleFailureMessage(connection, cause, (detail) => {
718
+ const sentence = /[.!?]$/.test(detail.trim()) ? detail.trim() : `${detail.trim()}.`;
719
+ return ["refused", "not-sent"].includes(writeOutcome(cause))
720
+ ? `Created document ${documentId}, but Google refused its initial text: ${sentence} Do not create it again; add the text with append_text on ${documentId}.`
721
+ : `Created document ${documentId}, but its initial text was not confirmed: ${sentence} Do not create it again; read ${documentId} with get_document, and append the text with append_text only if it is missing.`;
722
+ });
723
+ }));
724
+ revisionId = revisionAfter(reply, dropped) ?? revisionId;
725
+ }
726
+ return compact({
727
+ documentId,
728
+ // The title this call sent, not Google's echo of it.
729
+ title,
730
+ revisionId,
731
+ url: documentUrl(documentId),
732
+ dropped: droppedField(dropped),
733
+ });
734
+ }),
735
+ },
736
+ {
737
+ name: "append_text",
738
+ description: "Append plain text to the end of a Google Doc's body, or of one tab. It joins the last paragraph; start with \\n for a new one. Never removes or restyles text.",
739
+ // Additive: the text lands after everything that was there.
740
+ annotations: { readOnlyHint: false, destructiveHint: false },
741
+ input: schemas.APPEND_TEXT_INPUT,
742
+ output: schemas.APPEND_TEXT_OUTPUT,
743
+ run: (args) => Effect.gen(function* () {
744
+ const reply = yield* batchUpdate(client, args["documentId"], [{ insertText: { text: args["text"], endOfSegmentLocation: compact({ tabId: args["tabId"] }) } }], revision(args));
745
+ return savedEdit(args["documentId"], reply);
746
+ }),
747
+ },
748
+ {
749
+ name: "insert_text",
750
+ description: "Insert plain text at one UTF-16 index of a Google Doc's body, from get_document withIndexes. Inserts only; nothing existing is removed or restyled.",
751
+ // Additive: text goes in at a point; nothing that was there is lost.
752
+ annotations: { readOnlyHint: false, destructiveHint: false },
753
+ input: schemas.INSERT_TEXT_INPUT,
754
+ output: schemas.INSERT_TEXT_OUTPUT,
755
+ run: (args) => Effect.gen(function* () {
756
+ const reply = yield* batchUpdate(client, args["documentId"], [{ insertText: { text: args["text"], location: compact({ index: args["index"], tabId: args["tabId"] }) } }], revision(args));
757
+ return savedEdit(args["documentId"], reply);
758
+ }),
759
+ },
760
+ {
761
+ name: "replace_all_text",
762
+ description: "Replace every occurrence of a string (or regex) in a Google Doc, in all tabs or the listed ones, and report how many changed. Overwrites text; no preview.",
763
+ // Destructive: the matched text is gone, and a broad match can rewrite
764
+ // far more of the document than was meant.
765
+ annotations: { readOnlyHint: false, destructiveHint: true },
766
+ input: schemas.REPLACE_ALL_TEXT_INPUT,
767
+ output: schemas.REPLACE_ALL_TEXT_OUTPUT,
768
+ run: (args) => Effect.gen(function* () {
769
+ const reply = yield* batchUpdate(client, args["documentId"], [
770
+ {
771
+ replaceAllText: compact({
772
+ containsText: compact({
773
+ text: args["find"],
774
+ matchCase: args["matchCase"] === true,
775
+ searchByRegex: args["regex"] === true ? true : undefined,
776
+ }),
777
+ replaceText: args["replaceWith"],
778
+ tabsCriteria: Array.isArray(args["tabIds"]) ? { tabIds: args["tabIds"] } : undefined,
779
+ }),
780
+ },
781
+ ], revision(args));
782
+ const changed = integer(asRecord(asRecord(asArray(reply["replies"])[0])["replaceAllText"])["occurrencesChanged"]);
783
+ // Google omits a zero count from the reply.
784
+ return { ...savedEdit(args["documentId"], reply), occurrencesChanged: changed ?? 0 };
785
+ }),
786
+ },
787
+ {
788
+ name: "batch_update_document",
789
+ description: "Apply raw Docs API documents.batchUpdate requests to one Google Doc at a required revision, atomically: all apply or none. Always destructive; prefer named edits.",
790
+ // Destructive by construction: a raw request can delete ranges, restyle
791
+ // the whole document, or drop tables, and no schema here can tell which.
792
+ annotations: { readOnlyHint: false, destructiveHint: true },
793
+ input: schemas.BATCH_UPDATE_DOCUMENT_INPUT,
794
+ output: schemas.BATCH_UPDATE_DOCUMENT_OUTPUT,
795
+ run: (args) => Effect.gen(function* () {
796
+ const reply = yield* batchUpdate(client, args["documentId"], args["requests"], revision(args));
797
+ return boundedReplies(savedEdit(args["documentId"], reply), asArray(reply["replies"]).map(asRecord));
798
+ }),
799
+ },
800
+ ];
801
+ }