@anchrd/intel 0.59.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 (364) hide show
  1. package/README.md +496 -0
  2. package/bin/intel.mjs +134 -0
  3. package/dist/adapters/cloudflare/cloudflare-flow-workflow.d.ts +42 -0
  4. package/dist/adapters/cloudflare/cloudflare-flow-workflow.js +89 -0
  5. package/dist/adapters/cloudflare/cloudflare.d.ts +29 -0
  6. package/dist/adapters/cloudflare/cloudflare.js +416 -0
  7. package/dist/adapters/cloudflare/cloudflare.types.d.ts +31 -0
  8. package/dist/adapters/cloudflare/cloudflare.types.js +1 -0
  9. package/dist/adapters/content/content.d.ts +3 -0
  10. package/dist/adapters/content/content.js +128 -0
  11. package/dist/adapters/content/content.types.d.ts +38 -0
  12. package/dist/adapters/content/content.types.js +1 -0
  13. package/dist/adapters/db/db-audit.d.ts +62 -0
  14. package/dist/adapters/db/db-audit.js +150 -0
  15. package/dist/adapters/db/db-boards.d.ts +6 -0
  16. package/dist/adapters/db/db-boards.js +324 -0
  17. package/dist/adapters/db/db-feed.d.ts +30 -0
  18. package/dist/adapters/db/db-feed.js +276 -0
  19. package/dist/adapters/db/db-flows.d.ts +42 -0
  20. package/dist/adapters/db/db-flows.js +1311 -0
  21. package/dist/adapters/db/db-grants.d.ts +133 -0
  22. package/dist/adapters/db/db-grants.js +269 -0
  23. package/dist/adapters/db/db-indexing.d.ts +3 -0
  24. package/dist/adapters/db/db-indexing.js +200 -0
  25. package/dist/adapters/db/db-oauth.d.ts +3 -0
  26. package/dist/adapters/db/db-oauth.js +26 -0
  27. package/dist/adapters/db/db-prompts.d.ts +46 -0
  28. package/dist/adapters/db/db-prompts.js +144 -0
  29. package/dist/adapters/db/db.d.ts +6 -0
  30. package/dist/adapters/db/db.js +1687 -0
  31. package/dist/adapters/db/db.types.d.ts +16 -0
  32. package/dist/adapters/db/db.types.js +1 -0
  33. package/dist/adapters/document-converter/document-converter.d.ts +2 -0
  34. package/dist/adapters/document-converter/document-converter.js +26 -0
  35. package/dist/adapters/document-converter/document-converter.types.d.ts +17 -0
  36. package/dist/adapters/document-converter/document-converter.types.js +1 -0
  37. package/dist/adapters/flow-runtime/flow-runtime.d.ts +3 -0
  38. package/dist/adapters/flow-runtime/flow-runtime.js +22 -0
  39. package/dist/adapters/flow-runtime/flow-runtime.types.d.ts +19 -0
  40. package/dist/adapters/flow-runtime/flow-runtime.types.js +1 -0
  41. package/dist/adapters/index-queue/index-queue.d.ts +10 -0
  42. package/dist/adapters/index-queue/index-queue.js +12 -0
  43. package/dist/adapters/index-queue/index-queue.types.d.ts +11 -0
  44. package/dist/adapters/index-queue/index-queue.types.js +1 -0
  45. package/dist/adapters/json-schema/json-schema.d.ts +2 -0
  46. package/dist/adapters/json-schema/json-schema.js +77 -0
  47. package/dist/adapters/openid/openid.d.ts +5 -0
  48. package/dist/adapters/openid/openid.js +301 -0
  49. package/dist/adapters/portal-tokens/portal-tokens.d.ts +8 -0
  50. package/dist/adapters/portal-tokens/portal-tokens.js +79 -0
  51. package/dist/adapters/remote-tools/remote-tools.d.ts +5 -0
  52. package/dist/adapters/remote-tools/remote-tools.js +129 -0
  53. package/dist/adapters/semantic-index/semantic-index.d.ts +2 -0
  54. package/dist/adapters/semantic-index/semantic-index.js +139 -0
  55. package/dist/adapters/semantic-index/semantic-index.types.d.ts +56 -0
  56. package/dist/adapters/semantic-index/semantic-index.types.js +1 -0
  57. package/dist/adapters/session-cookie/session-cookie.d.ts +5 -0
  58. package/dist/adapters/session-cookie/session-cookie.js +87 -0
  59. package/dist/adapters/tool-source-policy/tool-source-policy.d.ts +1 -0
  60. package/dist/adapters/tool-source-policy/tool-source-policy.js +22 -0
  61. package/dist/audit/audit.d.ts +2 -0
  62. package/dist/audit/audit.js +87 -0
  63. package/dist/audit/audit.types.d.ts +23 -0
  64. package/dist/audit/audit.types.js +1 -0
  65. package/dist/auth/auth.d.ts +2 -0
  66. package/dist/auth/auth.js +284 -0
  67. package/dist/auth/auth.types.d.ts +108 -0
  68. package/dist/auth/auth.types.js +1 -0
  69. package/dist/boards/boards.d.ts +44 -0
  70. package/dist/boards/boards.js +447 -0
  71. package/dist/boards/boards.types.d.ts +117 -0
  72. package/dist/boards/boards.types.js +1 -0
  73. package/dist/build/build.d.ts +2 -0
  74. package/dist/build/build.js +192 -0
  75. package/dist/build/build.types.d.ts +17 -0
  76. package/dist/build/build.types.js +1 -0
  77. package/dist/bundle/bundle.d.ts +4 -0
  78. package/dist/bundle/bundle.js +1194 -0
  79. package/dist/bundle/bundle.types.d.ts +33 -0
  80. package/dist/bundle/bundle.types.js +1 -0
  81. package/dist/cli/cli.d.ts +4 -0
  82. package/dist/cli/cli.js +262 -0
  83. package/dist/cli/cli.types.d.ts +14 -0
  84. package/dist/cli/cli.types.js +1 -0
  85. package/dist/contract/audit.d.ts +50 -0
  86. package/dist/contract/audit.js +72 -0
  87. package/dist/contract/board.d.ts +150 -0
  88. package/dist/contract/board.js +262 -0
  89. package/dist/contract/bundle.d.ts +147 -0
  90. package/dist/contract/bundle.js +139 -0
  91. package/dist/contract/contract.d.ts +112 -0
  92. package/dist/contract/contract.js +143 -0
  93. package/dist/contract/feed.d.ts +126 -0
  94. package/dist/contract/feed.js +149 -0
  95. package/dist/contract/flow-run.d.ts +346 -0
  96. package/dist/contract/flow-run.js +194 -0
  97. package/dist/contract/flow.d.ts +1057 -0
  98. package/dist/contract/flow.js +552 -0
  99. package/dist/contract/node.d.ts +551 -0
  100. package/dist/contract/node.js +456 -0
  101. package/dist/contract/share.d.ts +216 -0
  102. package/dist/contract/share.js +134 -0
  103. package/dist/contract/table.d.ts +171 -0
  104. package/dist/contract/table.js +117 -0
  105. package/dist/contract/tool.d.ts +122 -0
  106. package/dist/contract/tool.js +172 -0
  107. package/dist/feed/feed.d.ts +2 -0
  108. package/dist/feed/feed.js +77 -0
  109. package/dist/feed/feed.types.d.ts +23 -0
  110. package/dist/feed/feed.types.js +1 -0
  111. package/dist/flows/flows.d.ts +41 -0
  112. package/dist/flows/flows.js +2216 -0
  113. package/dist/flows/flows.types.d.ts +339 -0
  114. package/dist/flows/flows.types.js +1 -0
  115. package/dist/http/http.d.ts +9 -0
  116. package/dist/http/http.js +874 -0
  117. package/dist/http/http.types.d.ts +22 -0
  118. package/dist/http/http.types.js +1 -0
  119. package/dist/indexing/indexing.d.ts +6 -0
  120. package/dist/indexing/indexing.js +239 -0
  121. package/dist/indexing/indexing.types.d.ts +11 -0
  122. package/dist/indexing/indexing.types.js +1 -0
  123. package/dist/intel/intel.d.ts +3 -0
  124. package/dist/intel/intel.js +194 -0
  125. package/dist/intel/intel.types.d.ts +24 -0
  126. package/dist/intel/intel.types.js +1 -0
  127. package/dist/mcp/mcp.d.ts +2 -0
  128. package/dist/mcp/mcp.js +1292 -0
  129. package/dist/mcp/mcp.types.d.ts +25 -0
  130. package/dist/mcp/mcp.types.js +1 -0
  131. package/dist/nodes/document-links/document-links.d.ts +20 -0
  132. package/dist/nodes/document-links/document-links.js +57 -0
  133. package/dist/nodes/nodes.d.ts +2 -0
  134. package/dist/nodes/nodes.js +1667 -0
  135. package/dist/nodes/nodes.types.d.ts +511 -0
  136. package/dist/nodes/nodes.types.js +1 -0
  137. package/dist/prepare/prepare.d.ts +4 -0
  138. package/dist/prepare/prepare.js +16 -0
  139. package/dist/prepare/prepare.types.d.ts +9 -0
  140. package/dist/prepare/prepare.types.js +1 -0
  141. package/dist/prompts/prompts.d.ts +2 -0
  142. package/dist/prompts/prompts.js +65 -0
  143. package/dist/prompts/prompts.types.d.ts +71 -0
  144. package/dist/prompts/prompts.types.js +1 -0
  145. package/dist/shared/callers/callers.d.ts +28 -0
  146. package/dist/shared/callers/callers.js +30 -0
  147. package/dist/shared/csv/csv.d.ts +13 -0
  148. package/dist/shared/csv/csv.js +85 -0
  149. package/dist/shared/document-text/document-text.d.ts +21 -0
  150. package/dist/shared/document-text/document-text.js +31 -0
  151. package/dist/shared/gate-authorization/gate-authorization.d.ts +5 -0
  152. package/dist/shared/gate-authorization/gate-authorization.js +26 -0
  153. package/dist/shared/grant-expiry/grant-expiry.d.ts +19 -0
  154. package/dist/shared/grant-expiry/grant-expiry.js +26 -0
  155. package/dist/shared/intel-error/intel-error.d.ts +22 -0
  156. package/dist/shared/intel-error/intel-error.js +27 -0
  157. package/dist/shared/plain-title/plain-title.d.ts +16 -0
  158. package/dist/shared/plain-title/plain-title.js +45 -0
  159. package/dist/shared/problem-details/problem-details.d.ts +30 -0
  160. package/dist/shared/problem-details/problem-details.js +18 -0
  161. package/dist/shared/report-unexpected-error/report-unexpected-error.d.ts +1 -0
  162. package/dist/shared/report-unexpected-error/report-unexpected-error.js +37 -0
  163. package/dist/shared/safe-return-path/safe-return-path.d.ts +2 -0
  164. package/dist/shared/safe-return-path/safe-return-path.js +11 -0
  165. package/dist/shared/sha256/sha256.d.ts +2 -0
  166. package/dist/shared/sha256/sha256.js +14 -0
  167. package/dist/tools/tool-servers/tool-servers.d.ts +46 -0
  168. package/dist/tools/tool-servers/tool-servers.js +177 -0
  169. package/dist/tools/tools.d.ts +2 -0
  170. package/dist/tools/tools.js +207 -0
  171. package/dist/tools/tools.types.d.ts +56 -0
  172. package/dist/tools/tools.types.js +1 -0
  173. package/examples/branding/favicon.svg +5 -0
  174. package/examples/branding/logo.svg +5 -0
  175. package/examples/branding/theme.css +16 -0
  176. package/examples/dev.vars.example +7 -0
  177. package/examples/intel.json +9 -0
  178. package/migrations/0000_intel_foundation.sql +203 -0
  179. package/migrations/0001_portal_native_tools.sql +21 -0
  180. package/migrations/0002_flows_in_the_knowledge_tree.sql +34 -0
  181. package/migrations/0003_folder_permissions.sql +211 -0
  182. package/migrations/0004_subflow_runs.sql +14 -0
  183. package/migrations/0005_flow_node_cleanup.sql +28 -0
  184. package/migrations/0005_tables_in_the_knowledge_tree.sql +68 -0
  185. package/migrations/0006_links_are_written_in_the_text.sql +20 -0
  186. package/migrations/0007_no_node_waits.sql +69 -0
  187. package/migrations/0008_three_layers.sql +130 -0
  188. package/migrations/0009_no_context_policy.sql +48 -0
  189. package/migrations/0010_no_run_waits_a_year.sql +27 -0
  190. package/migrations/0011_one_name_for_the_tree.sql +53 -0
  191. package/migrations/0012_table_snapshots.sql +29 -0
  192. package/migrations/0017_a_vector_per_card.sql +38 -0
  193. package/migrations/0018_no_context_policy_at_last.sql +97 -0
  194. package/migrations/0019_one_name_for_the_grants.sql +52 -0
  195. package/migrations/0020_cascade_purge_replay.sql +11 -0
  196. package/migrations/0021_a_flow_carries_its_own_grant.sql +44 -0
  197. package/migrations/0022_a_cursor_over_the_journal.sql +14 -0
  198. package/migrations/0023_a_board_and_its_tasks.sql +116 -0
  199. package/migrations/0024_the_archive_is_a_column.sql +33 -0
  200. package/migrations/0025_archiving_takes_the_subtree.sql +29 -0
  201. package/migrations/0026_one_feed_over_two_kinds.sql +23 -0
  202. package/migrations/0027_the_runs_of_every_flow.sql +27 -0
  203. package/migrations/0028_a_prompt_name_over_two_kinds.sql +70 -0
  204. package/package.json +118 -0
  205. package/src/contract/CLAUDE.md +152 -0
  206. package/src/contract/README.md +102 -0
  207. package/src/contract/audit.ts +85 -0
  208. package/src/contract/board.ts +306 -0
  209. package/src/contract/bundle.ts +157 -0
  210. package/src/contract/contract.ts +161 -0
  211. package/src/contract/feed.ts +165 -0
  212. package/src/contract/flow-run.ts +241 -0
  213. package/src/contract/flow.ts +675 -0
  214. package/src/contract/node.ts +583 -0
  215. package/src/contract/share.ts +196 -0
  216. package/src/contract/table.ts +178 -0
  217. package/src/contract/tool.ts +202 -0
  218. package/ui/README.md +110 -0
  219. package/ui/components.json +21 -0
  220. package/ui/index.html +13 -0
  221. package/ui/package.json +11 -0
  222. package/ui/scripts/lint-tokens.mjs +187 -0
  223. package/ui/src/access-summary/access-summary.tsx +385 -0
  224. package/ui/src/app/action-slot/action-slot.tsx +27 -0
  225. package/ui/src/app/app-sidebar/app-sidebar.tsx +71 -0
  226. package/ui/src/app/app-tree/app-tree.tsx +1175 -0
  227. package/ui/src/app/app.tsx +87 -0
  228. package/ui/src/app/header-search/header-search.tsx +294 -0
  229. package/ui/src/app/reindex-dialog/reindex-dialog.tsx +79 -0
  230. package/ui/src/app/settings-dialog/settings-dialog.tsx +135 -0
  231. package/ui/src/app/sidebar-preferences/sidebar-preferences.ts +68 -0
  232. package/ui/src/app/sidebar-preferences/sidebar-preferences.types.ts +15 -0
  233. package/ui/src/app/sidebar-resize-handle/sidebar-resize-handle.tsx +86 -0
  234. package/ui/src/app/tree-expansion/tree-expansion.ts +107 -0
  235. package/ui/src/app/tree-expansion/tree-expansion.types.ts +31 -0
  236. package/ui/src/app/tree-move/tree-move.tsx +427 -0
  237. package/ui/src/app/tree-reveal/tree-reveal.ts +64 -0
  238. package/ui/src/app/tree-reveal/tree-reveal.types.ts +16 -0
  239. package/ui/src/app/user-footer/user-footer.tsx +188 -0
  240. package/ui/src/app/view-menu/view-menu.tsx +115 -0
  241. package/ui/src/app-root/app-root.tsx +109 -0
  242. package/ui/src/app-root/app-root.types.ts +27 -0
  243. package/ui/src/archive/archive.tsx +417 -0
  244. package/ui/src/attachment-detail/attachment-detail.ts +52 -0
  245. package/ui/src/attachment-viewer/attachment-viewer.tsx +166 -0
  246. package/ui/src/blocknote-view/blocknote-view.tsx +30 -0
  247. package/ui/src/board/board-assignee/board-assignee-picker.tsx +132 -0
  248. package/ui/src/board/board-assignee/board-assignee.ts +48 -0
  249. package/ui/src/board/board-assignee/board-assignee.tsx +75 -0
  250. package/ui/src/board/board-chip/board-chip.tsx +46 -0
  251. package/ui/src/board/board-crumbs/board-crumbs.ts +56 -0
  252. package/ui/src/board/board-crumbs/board-crumbs.tsx +111 -0
  253. package/ui/src/board/board-data/board-data.ts +195 -0
  254. package/ui/src/board/board-data/board-data.types.ts +38 -0
  255. package/ui/src/board/board-dates/board-dates.ts +40 -0
  256. package/ui/src/board/board-dates/board-dates.tsx +105 -0
  257. package/ui/src/board/board-kanban/board-kanban.ts +162 -0
  258. package/ui/src/board/board-kanban/board-kanban.tsx +680 -0
  259. package/ui/src/board/board-open-task/board-open-task.ts +32 -0
  260. package/ui/src/board/board-panel/board-panel.tsx +259 -0
  261. package/ui/src/board/board-settings/board-settings.tsx +226 -0
  262. package/ui/src/board/board-status/board-status.tsx +60 -0
  263. package/ui/src/board/board-stripes/board-stripes.ts +128 -0
  264. package/ui/src/board/board-table/board-table.ts +53 -0
  265. package/ui/src/board/board-table/board-table.tsx +610 -0
  266. package/ui/src/board/board-task/board-task.ts +108 -0
  267. package/ui/src/board/board-task/board-task.tsx +335 -0
  268. package/ui/src/board/board-title-row/board-title-row.tsx +66 -0
  269. package/ui/src/branding/branding.tsx +17 -0
  270. package/ui/src/branding/custom-logo.ts +1 -0
  271. package/ui/src/branding/favicon.default.svg +5 -0
  272. package/ui/src/branding/favicon.svg +5 -0
  273. package/ui/src/components/ui/avatar.tsx +39 -0
  274. package/ui/src/components/ui/button.tsx +64 -0
  275. package/ui/src/components/ui/collapsible.tsx +20 -0
  276. package/ui/src/components/ui/command.tsx +160 -0
  277. package/ui/src/components/ui/dialog.tsx +143 -0
  278. package/ui/src/components/ui/dropdown-menu.tsx +187 -0
  279. package/ui/src/components/ui/input.tsx +21 -0
  280. package/ui/src/components/ui/popover.tsx +41 -0
  281. package/ui/src/components/ui/select.tsx +163 -0
  282. package/ui/src/components/ui/separator.tsx +26 -0
  283. package/ui/src/components/ui/sheet.tsx +136 -0
  284. package/ui/src/components/ui/sidebar.tsx +693 -0
  285. package/ui/src/components/ui/skeleton.tsx +13 -0
  286. package/ui/src/components/ui/switch.tsx +25 -0
  287. package/ui/src/components/ui/table.tsx +82 -0
  288. package/ui/src/components/ui/tabs.tsx +52 -0
  289. package/ui/src/components/ui/tooltip.tsx +51 -0
  290. package/ui/src/data/intel-data-provider/intel-data-provider.ts +685 -0
  291. package/ui/src/data/intel-data-provider/intel-data-provider.types.ts +266 -0
  292. package/ui/src/data/query-client.ts +18 -0
  293. package/ui/src/data/request-refusal/refusal-notice.tsx +36 -0
  294. package/ui/src/data/request-refusal/request-refusal.ts +51 -0
  295. package/ui/src/data/sign-in/sign-in.ts +97 -0
  296. package/ui/src/data/sign-in/sign-in.types.ts +15 -0
  297. package/ui/src/document-link/document-link.tsx +114 -0
  298. package/ui/src/document-markdown/document-markdown.ts +195 -0
  299. package/ui/src/editor-schema/editor-schema.ts +23 -0
  300. package/ui/src/entry-picker/entry-picker.tsx +238 -0
  301. package/ui/src/feed/feed.tsx +344 -0
  302. package/ui/src/file-preview/file-preview-view.tsx +496 -0
  303. package/ui/src/file-preview/file-preview.tsx +28 -0
  304. package/ui/src/file-preview/pdf-file-preview.tsx +5 -0
  305. package/ui/src/file-preview/presentation-file-preview.tsx +21 -0
  306. package/ui/src/file-preview/spreadsheet-file-preview.tsx +5 -0
  307. package/ui/src/file-preview/word-file-preview.tsx +5 -0
  308. package/ui/src/flow-runs/flow-runs.tsx +238 -0
  309. package/ui/src/flows/flows.tsx +1646 -0
  310. package/ui/src/flows/node-icon/node-icon.ts +34 -0
  311. package/ui/src/flows/node-palette/node-palette.tsx +214 -0
  312. package/ui/src/flows/node-palette/node-palette.types.ts +15 -0
  313. package/ui/src/folder-contents/folder-contents.tsx +321 -0
  314. package/ui/src/folder-index/folder-index.ts +55 -0
  315. package/ui/src/frontmatter/frontmatter.tsx +193 -0
  316. package/ui/src/graph-pane/graph-pane.tsx +53 -0
  317. package/ui/src/hooks/use-mobile.ts +19 -0
  318. package/ui/src/i18n/custom.json +4 -0
  319. package/ui/src/i18n/de.json +609 -0
  320. package/ui/src/i18n/en.json +609 -0
  321. package/ui/src/i18n/es.json +609 -0
  322. package/ui/src/i18n/i18n-context.tsx +65 -0
  323. package/ui/src/i18n/i18n-languages/i18n-languages.ts +83 -0
  324. package/ui/src/i18n/i18n.ts +32 -0
  325. package/ui/src/i18n/i18n.types.ts +49 -0
  326. package/ui/src/kind-icon.ts +97 -0
  327. package/ui/src/lib/utils.ts +6 -0
  328. package/ui/src/main.tsx +68 -0
  329. package/ui/src/modal/modal.tsx +53 -0
  330. package/ui/src/node-details/node-details.tsx +545 -0
  331. package/ui/src/node-editor/node-editor.tsx +304 -0
  332. package/ui/src/node-graph/graph-notice.tsx +21 -0
  333. package/ui/src/node-graph/node-graph.ts +214 -0
  334. package/ui/src/node-graph/node-graph.tsx +295 -0
  335. package/ui/src/node-import/node-import.tsx +418 -0
  336. package/ui/src/node-save-error.ts +46 -0
  337. package/ui/src/node-source/node-source.tsx +128 -0
  338. package/ui/src/node-table/node-table.tsx +107 -0
  339. package/ui/src/nodes/nodes.tsx +240 -0
  340. package/ui/src/rename-resource/rename-resource.ts +80 -0
  341. package/ui/src/resolved-names/resolved-names.tsx +67 -0
  342. package/ui/src/resource-error.ts +33 -0
  343. package/ui/src/resource-menu/resource-menu.tsx +1156 -0
  344. package/ui/src/router/router-context.ts +6 -0
  345. package/ui/src/router/router.tsx +67 -0
  346. package/ui/src/router/router.types.ts +8 -0
  347. package/ui/src/router/selection-search.ts +88 -0
  348. package/ui/src/save-button/save-button.tsx +94 -0
  349. package/ui/src/section-hint/section-hint.tsx +40 -0
  350. package/ui/src/sign-in-refused/sign-in-refused.tsx +35 -0
  351. package/ui/src/styles.css +189 -0
  352. package/ui/src/table-columns/table-columns.tsx +379 -0
  353. package/ui/src/theme/custom.css +1 -0
  354. package/ui/src/theme/theme-context.tsx +89 -0
  355. package/ui/src/theme/theme.ts +70 -0
  356. package/ui/src/time/relative-time.tsx +41 -0
  357. package/ui/src/time/time-context.tsx +78 -0
  358. package/ui/src/time/time.ts +165 -0
  359. package/ui/src/title-row/title-row.tsx +185 -0
  360. package/ui/src/tools/tools.tsx +546 -0
  361. package/ui/src/user-name/user-name.ts +61 -0
  362. package/ui/src/verbatim/verbatim.tsx +68 -0
  363. package/ui/tsconfig.json +17 -0
  364. package/ui/vite.config.ts +129 -0
@@ -0,0 +1,16 @@
1
+ export interface D1Result<T = unknown> {
2
+ results?: T[];
3
+ meta?: {
4
+ changes?: number;
5
+ };
6
+ }
7
+ export interface D1Statement {
8
+ bind(...values: unknown[]): D1Statement;
9
+ first<T = Record<string, unknown>>(): Promise<T | null>;
10
+ all<T = Record<string, unknown>>(): Promise<D1Result<T>>;
11
+ run<T = Record<string, unknown>>(): Promise<D1Result<T>>;
12
+ }
13
+ export interface D1Database {
14
+ prepare(query: string): D1Statement;
15
+ batch<T = unknown>(statements: D1Statement[]): Promise<D1Result<T>[]>;
16
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { DocumentConverter, DocumentConverterDeps } from "./document-converter.types.js";
2
+ export declare function createDocumentConverter(deps: DocumentConverterDeps): DocumentConverter;
@@ -0,0 +1,26 @@
1
+ function converted(result) {
2
+ const item = Array.isArray(result) ? result[0] : result;
3
+ if (typeof item !== "object" ||
4
+ item === null ||
5
+ !("format" in item) ||
6
+ (item.format !== "markdown" && item.format !== "text") ||
7
+ !("data" in item) ||
8
+ typeof item.data !== "string") {
9
+ const detail = typeof item === "object" && item !== null && "error" in item && typeof item.error === "string"
10
+ ? item.error
11
+ : "Markdown conversion returned an invalid response";
12
+ throw new Error(detail);
13
+ }
14
+ return item.data;
15
+ }
16
+ export function createDocumentConverter(deps) {
17
+ const gateway = { id: deps.gateway };
18
+ return {
19
+ async convert(name, mediaType, content) {
20
+ return converted(await deps.ai.toMarkdown({
21
+ name,
22
+ blob: new Blob([content], { type: mediaType }),
23
+ }, { gateway }));
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,17 @@
1
+ export interface MarkdownAiBinding {
2
+ toMarkdown(input: {
3
+ name: string;
4
+ blob: Blob;
5
+ }, options: {
6
+ gateway: {
7
+ id: string;
8
+ };
9
+ }): Promise<unknown>;
10
+ }
11
+ export interface DocumentConverterDeps {
12
+ ai: MarkdownAiBinding;
13
+ gateway: string;
14
+ }
15
+ export interface DocumentConverter {
16
+ convert(name: string, mediaType: string, content: ArrayBuffer): Promise<string>;
17
+ }
@@ -0,0 +1,3 @@
1
+ import type { FlowRuntime } from "../../flows/flows.types.js";
2
+ import type { WorkflowBinding } from "./flow-runtime.types.js";
3
+ export declare function createFlowRuntime(binding: WorkflowBinding): FlowRuntime;
@@ -0,0 +1,22 @@
1
+ export function createFlowRuntime(binding) {
2
+ return {
3
+ async start(runId) {
4
+ try {
5
+ await binding.create({ id: runId, params: { runId } });
6
+ }
7
+ catch (error) {
8
+ try {
9
+ const existing = await binding.get(runId);
10
+ await existing.status();
11
+ }
12
+ catch {
13
+ throw error;
14
+ }
15
+ }
16
+ },
17
+ async signal(runId) {
18
+ const instance = await binding.get(runId);
19
+ await instance.sendEvent({ type: "advance", payload: { runId } });
20
+ },
21
+ };
22
+ }
@@ -0,0 +1,19 @@
1
+ export interface WorkflowBinding {
2
+ create(options: {
3
+ id: string;
4
+ params: {
5
+ runId: string;
6
+ };
7
+ }): Promise<{
8
+ id: string;
9
+ }>;
10
+ get(id: string): Promise<{
11
+ status(): Promise<{
12
+ status: string;
13
+ }>;
14
+ sendEvent(event: {
15
+ type: string;
16
+ payload: unknown;
17
+ }): Promise<void>;
18
+ }>;
19
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { z } from "zod";
2
+ import type { QueueProducer } from "./index-queue.types.js";
3
+ export declare const IndexMessage: z.ZodObject<{
4
+ type: z.ZodLiteral<"node.version">;
5
+ versionId: z.ZodString;
6
+ }, z.core.$strict>;
7
+ export type IndexMessage = z.infer<typeof IndexMessage>;
8
+ export declare function createIndexQueue(queue: QueueProducer<IndexMessage>): {
9
+ enqueue(versionId: string): Promise<void>;
10
+ };
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ export const IndexMessage = z.strictObject({
3
+ type: z.literal("node.version"),
4
+ versionId: z.string().min(1),
5
+ });
6
+ export function createIndexQueue(queue) {
7
+ return {
8
+ async enqueue(versionId) {
9
+ await queue.send({ type: "node.version", versionId });
10
+ },
11
+ };
12
+ }
@@ -0,0 +1,11 @@
1
+ export interface QueueProducer<T> {
2
+ send(message: T): Promise<void>;
3
+ }
4
+ export interface QueueMessage<T> {
5
+ body: T;
6
+ ack(): void;
7
+ retry(): void;
8
+ }
9
+ export interface QueueBatch<T> {
10
+ messages: QueueMessage<T>[];
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { ToolDeps } from "../../tools/tools.types.js";
2
+ export declare function createJsonSchemaValidator(): ToolDeps["validate"];
@@ -0,0 +1,77 @@
1
+ import { Validator } from "@cfworker/json-schema";
2
+ import { reportUnexpectedError } from "../../shared/report-unexpected-error/report-unexpected-error.js";
3
+ // ⚠️ This validator must not generate code, and Ajv must never come back. Ajv turns a schema into
4
+ // JavaScript with `new Function`, and workerd answers that with `EvalError: Code generation from
5
+ // strings disallowed for this context` — so `compile` threw for EVERY schema in production, the
6
+ // catch called it a broken schema, and each tool call died with 400 "The cached tool schema is
7
+ // invalid" while nothing was wrong with any schema (#229). Ajv is usable only with schemas
8
+ // precompiled at build time; these arrive from a remote catalog and are unknown until the call.
9
+ // `@cfworker/json-schema` interprets the schema instead, which is what it was written for.
10
+ //
11
+ // ⚠️ The `workerd` test pool does not prove this on its own: it replaces `globalThis.Function` with
12
+ // a proxy onto an unsafe-eval binding, so Ajv compiled happily in every test while failing in every
13
+ // deployment. `json-schema.int.ts` therefore takes that patch away for the length of the test — it
14
+ // is the only thing that turns red when a code-generating validator returns.
15
+ // Tool schemas come from remote catalogs and are therefore untrusted: `ShortCircuit` stops at the
16
+ // first error so a crafted schema cannot amplify the work of one call, and the prepared validators
17
+ // are bounded because each one holds its dereferenced schema for the life of the isolate.
18
+ const MaxPreparedSchemas = 200;
19
+ const ShortCircuit = true;
20
+ const MaxDetailLength = 2_000;
21
+ // A tool schema that names its draft gets that draft: draft-07 ignores keywords sitting beside a
22
+ // `$ref` and the later drafts apply them, so guessing here would validate a foreign catalog against
23
+ // rules its author never wrote. Unnamed means 2020-12, the draft the MCP generators emit when they
24
+ // stay silent.
25
+ const DefaultDraft = "2020-12";
26
+ const DraftsByMetaSchema = [
27
+ ["draft-04", "4"],
28
+ ["draft-07", "7"],
29
+ ["2019-09", "2019-09"],
30
+ ["2020-12", "2020-12"],
31
+ ];
32
+ function draftOf(schema) {
33
+ const declared = schema.$schema;
34
+ if (typeof declared !== "string")
35
+ return DefaultDraft;
36
+ return DraftsByMetaSchema.find(([marker]) => declared.includes(marker))?.[1] ?? DefaultDraft;
37
+ }
38
+ export function createJsonSchemaValidator() {
39
+ const prepared = new Map();
40
+ return (schema, value) => {
41
+ const key = JSON.stringify(schema);
42
+ let validator = prepared.get(key);
43
+ if (!validator) {
44
+ try {
45
+ validator = new Validator(schema, draftOf(schema), ShortCircuit);
46
+ }
47
+ catch (error) {
48
+ // A schema this validator cannot even read is still refused by name — but the caller hears
49
+ // that it was the schema, not their arguments, and the reason itself goes to the log. The
50
+ // old wording claimed a broken schema for every failure, which is what hid #229 for weeks.
51
+ reportUnexpectedError(error);
52
+ return { valid: false, detail: "The tool schema could not be read; the reason was logged" };
53
+ }
54
+ if (prepared.size >= MaxPreparedSchemas)
55
+ prepared.clear();
56
+ prepared.set(key, validator);
57
+ }
58
+ try {
59
+ const result = validator.validate(value);
60
+ if (result.valid)
61
+ return { valid: true };
62
+ return { valid: false, detail: describe(result) };
63
+ }
64
+ catch (error) {
65
+ // An unresolvable `$ref` only shows up while walking the instance, so this is the same
66
+ // unreadable schema arriving one step later — and never a statement about the arguments.
67
+ reportUnexpectedError(error);
68
+ return { valid: false, detail: "The tool schema could not be read; the reason was logged" };
69
+ }
70
+ };
71
+ }
72
+ function describe(result) {
73
+ return result.errors
74
+ .map((unit) => `${unit.instanceLocation}: ${unit.error}`)
75
+ .join("; ")
76
+ .slice(0, MaxDetailLength);
77
+ }
@@ -0,0 +1,5 @@
1
+ import type { OAuthPort } from "../../auth/auth.types.js";
2
+ export declare function createOpenId(deps: {
3
+ fetch: typeof fetch;
4
+ allowInsecure?: boolean;
5
+ }): OAuthPort;
@@ -0,0 +1,301 @@
1
+ import * as client from "openid-client";
2
+ import { z } from "zod";
3
+ import { ToolSourceUrl } from "../../contract/tool.js";
4
+ const ProtectedResource = z.looseObject({
5
+ resource: ToolSourceUrl,
6
+ authorization_servers: z.array(ToolSourceUrl).min(1),
7
+ });
8
+ function metadataCandidates(resourceUrl, challenge) {
9
+ const resource = new URL(resourceUrl);
10
+ const path = resource.pathname === "/" ? "" : resource.pathname.replace(/\/+$/, "");
11
+ let challengeUrl = null;
12
+ try {
13
+ challengeUrl = challenge ? new URL(challenge, resource) : null;
14
+ }
15
+ catch {
16
+ challengeUrl = null;
17
+ }
18
+ return [
19
+ ...(challengeUrl?.origin === resource.origin ? [challengeUrl.href] : []),
20
+ `${resource.origin}/.well-known/oauth-protected-resource${path}`,
21
+ `${resource.origin}/.well-known/oauth-protected-resource`,
22
+ ].filter((value, index, values) => values.indexOf(value) === index);
23
+ }
24
+ function resourceMetadataChallenge(header) {
25
+ return header?.match(/resource_metadata="([^"]+)"/i)?.[1] ?? null;
26
+ }
27
+ /**
28
+ * ⚠️ `issuer` is REQUIRED by RFC 8414 §2 and by OpenID Connect Discovery, and it is read here for one
29
+ * reason: it is the only thing that says WHO a metadata document describes. Two of the URLs below are
30
+ * bare-root guesses, and a bare root under a multi-tenant authorization server answers for a
31
+ * different tenant — a perfectly valid document about somebody else.
32
+ */
33
+ const AuthorizationServerMetadata = z.looseObject({
34
+ issuer: z.string().nullish(),
35
+ scopes_supported: z.array(z.string()).nullish(),
36
+ });
37
+ /**
38
+ * Where an authorization server may publish its metadata, in the order worth asking.
39
+ *
40
+ * ⚠️ One candidate was never a decision, it was an omission — `metadataCandidates` above tries three
41
+ * and `withAlgorithmFallback` tries two. A server that publishes only OIDC discovery read as "names
42
+ * no scopes", which is #97 at a different server class and with the same silent symptom (#303).
43
+ *
44
+ * ⚠️ The three spellings are not interchangeable, and the third is the one that is easy to miss.
45
+ * RFC 8414 §3.1 INSERTS the issuer's path between the well-known prefix and nothing else; OpenID
46
+ * Connect Discovery 1.0 APPENDS its suffix to the issuer instead. A realm-style issuer
47
+ * (`https://as.example/realms/x`) publishes at `…/realms/x/.well-known/openid-configuration` and at
48
+ * neither of the other two — which is exactly the form `client.discovery` asks for further down this
49
+ * file, so the same server was reachable for registration and invisible here.
50
+ */
51
+ function authorizationServerCandidates(issuer) {
52
+ const url = new URL(issuer);
53
+ const path = url.pathname === "/" ? "" : url.pathname.replace(/\/+$/, "");
54
+ return [
55
+ // RFC 8414 with the path inserted. First, because it is the shape the MCP portals publish.
56
+ `${url.origin}/.well-known/oauth-authorization-server${path}`,
57
+ // The same document at the bare root, for an issuer WITH a path that publishes it there anyway.
58
+ `${url.origin}/.well-known/oauth-authorization-server`,
59
+ // OpenID Connect Discovery 1.0, appended to the issuer — the realm-style form.
60
+ `${url.origin}${path}/.well-known/openid-configuration`,
61
+ // The same document read the RFC 8414 way, and at the bare root.
62
+ `${url.origin}/.well-known/openid-configuration${path}`,
63
+ `${url.origin}/.well-known/openid-configuration`,
64
+ ].filter((value, index, values) => values.indexOf(value) === index);
65
+ }
66
+ /** Two issuer identifiers are the same server, compared the way a URL says so and not as strings. */
67
+ function sameIssuer(published, wanted) {
68
+ if (!published)
69
+ return false;
70
+ try {
71
+ return new URL(published).href.replace(/\/$/, "") === new URL(wanted).href.replace(/\/$/, "");
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ }
77
+ /**
78
+ * The scopes this authorization server says it understands, or `null` when it names none.
79
+ *
80
+ * ⚠️ `null` is not "no scopes" — RFC 8414 makes `scopes_supported` OPTIONAL, and Cloudflare Access
81
+ * omits it. It means "this server never said", and the only safe reading of that is to ask for
82
+ * nothing: a scope the server does not know is at best ignored and at worst turns the whole
83
+ * authorization down, and neither failure names the scope as its cause.
84
+ *
85
+ * This replaces a hostname check (`issuer.endsWith(".cloudflareaccess.com")`) that decided the same
86
+ * question by guessing who the server was. A Cloudflare Access portal on its own domain — the
87
+ * normal case for anyone who cares what their portal is called — was read as some other vendor, and
88
+ * Intel then asked it for `offline_access`, a scope it never published (#97).
89
+ */
90
+ async function publishedScopes(fetcher, issuer) {
91
+ for (const candidate of authorizationServerCandidates(issuer)) {
92
+ const response = await fetcher(candidate, {
93
+ signal: AbortSignal.timeout(RequestTimeoutMs),
94
+ headers: { accept: "application/json" },
95
+ }).catch(() => null);
96
+ if (!response?.ok)
97
+ continue;
98
+ const metadata = AuthorizationServerMetadata.safeParse(await response.json().catch(() => null));
99
+ if (!metadata.success)
100
+ continue;
101
+ // ⚠️ The document has to be ABOUT this issuer, and that check is what makes the bare-root
102
+ // candidates safe to ask at all. Under a multi-tenant server the root answers for a different
103
+ // tenant, and taking its `scopes_supported` would ask THIS server for a scope it never published
104
+ // — the very rule this function exists to keep, broken by the fallback added to protect it.
105
+ // The resource loop further down does the same thing for the same reason.
106
+ if (!sameIssuer(metadata.data.issuer, issuer))
107
+ continue;
108
+ // ⚠️ A document that answered ABOUT THIS SERVER and named no scopes ends the search. Reading on
109
+ // would let the next candidate answer a question this server has already answered — "I publish
110
+ // none" — and the documents of one issuer may disagree about a field RFC 8414 makes optional.
111
+ return metadata.data.scopes_supported ?? null;
112
+ }
113
+ return null;
114
+ }
115
+ /**
116
+ * Of the scopes Intel would like, the ones this server published — joined, or absent.
117
+ *
118
+ * `offline_access` is the one that matters: it is where a refresh token comes from on a server that
119
+ * gates it behind a scope. Asking for it unconditionally is what a hostname check amounted to.
120
+ */
121
+ function requestableScope(published) {
122
+ if (!published)
123
+ return undefined;
124
+ const wanted = ["openid", "profile", "email", "offline_access"].filter((scope) => published.includes(scope));
125
+ return wanted.length > 0 ? wanted.join(" ") : undefined;
126
+ }
127
+ function tokenResponse(tokens) {
128
+ return {
129
+ accessToken: tokens.access_token,
130
+ expiresIn: tokens.expires_in ?? 3_600,
131
+ ...(tokens.refresh_token ? { refreshToken: tokens.refresh_token } : {}),
132
+ };
133
+ }
134
+ // Discovery, token and probe requests run inside the login and connect paths. Without a deadline a
135
+ // hanging issuer would hold the request open until the platform kills it.
136
+ const RequestTimeoutMs = 10_000;
137
+ export function createOpenId(deps) {
138
+ const customFetch = async (url, options) => {
139
+ const init = options;
140
+ const deadline = AbortSignal.timeout(RequestTimeoutMs);
141
+ // The library may pass its own signal. Spreading init last would drop the deadline, spreading it
142
+ // first would drop the caller's cancellation, so both have to abort the request.
143
+ return await deps.fetch(url, {
144
+ ...init,
145
+ signal: init.signal ? AbortSignal.any([init.signal, deadline]) : deadline,
146
+ });
147
+ };
148
+ const configurations = new Map();
149
+ function options(issuer) {
150
+ const insecure = new URL(issuer).protocol === "http:";
151
+ if (insecure && !deps.allowInsecure) {
152
+ throw new Error("Insecure OAuth issuers require ALLOW_INSECURE_OAUTH=true");
153
+ }
154
+ return {
155
+ [client.customFetch]: customFetch,
156
+ ...(insecure ? { execute: [client.allowInsecureRequests] } : {}),
157
+ };
158
+ }
159
+ // openid-client resolves issuer metadata via OIDC discovery unless told otherwise. Gate serves
160
+ // that document, but a plain OAuth 2.0 authorization server — the MCP portal on its custom
161
+ // domain is one — publishes only RFC 8414's oauth-authorization-server path and answers the OIDC
162
+ // one with a 404, which surfaced as a bare 500 on /auth/connect (#93). The fallback repeats the
163
+ // operation once with the OAuth document; when both fail, the second error is thrown because it
164
+ // belongs to the attempt that got further for the issuer that needed the fallback at all.
165
+ async function withAlgorithmFallback(run, usable = () => true) {
166
+ try {
167
+ const first = await run();
168
+ // ⚠️ A THROW is not the only way the OIDC document can be the wrong one (#425). Cloudflare
169
+ // Access answers `/.well-known/openid-configuration` with **200** and a two-field stub —
170
+ // `issuer` and `jwks_uri`, nothing else — while the endpoints live only under RFC 8414's
171
+ // `oauth-authorization-server`. Discovery therefore SUCCEEDS, the fallback never runs, and
172
+ // the missing endpoint surfaces minutes later inside `buildAuthorizationUrl` as
173
+ // `authorization server metadata does not contain a valid "as.authorization_endpoint"` — a
174
+ // 500 on `/auth/connect` with nothing in it pointing at discovery.
175
+ //
176
+ // So the question is not "did it fail" but "is what came back usable". An answer that parses
177
+ // and carries nothing is the same situation as a 404, and it gets the same second attempt.
178
+ return usable(first) ? first : await run("oauth2");
179
+ }
180
+ catch {
181
+ return await run("oauth2");
182
+ }
183
+ }
184
+ async function discover(issuer, clientId) {
185
+ const key = `${issuer}#${clientId}`;
186
+ let configuration = configurations.get(key);
187
+ if (!configuration) {
188
+ configuration = withAlgorithmFallback((algorithm) => client.discovery(new URL(issuer), clientId, { token_endpoint_auth_method: "none" }, client.None(), { ...options(issuer), ...(algorithm ? { algorithm } : {}) }),
189
+ // The two endpoints every flow here needs. Asking for exactly them rather than for a long
190
+ // list keeps the check honest: a server that publishes these two is usable, and one that
191
+ // publishes neither is the stub case above, whatever else it carries.
192
+ (resolved) => {
193
+ const metadata = resolved.serverMetadata();
194
+ return Boolean(metadata.authorization_endpoint && metadata.token_endpoint);
195
+ });
196
+ configurations.set(key, configuration);
197
+ void configuration.catch(() => configurations.delete(key));
198
+ }
199
+ return await configuration;
200
+ }
201
+ return {
202
+ async discoverResource(resourceUrl) {
203
+ const probe = await deps
204
+ .fetch(resourceUrl, {
205
+ method: "POST",
206
+ signal: AbortSignal.timeout(RequestTimeoutMs),
207
+ headers: {
208
+ accept: "application/json, text/event-stream",
209
+ "content-type": "application/json",
210
+ },
211
+ body: JSON.stringify({
212
+ jsonrpc: "2.0",
213
+ id: "intel-oauth-discovery",
214
+ method: "initialize",
215
+ params: {
216
+ protocolVersion: "2025-11-25",
217
+ capabilities: {},
218
+ clientInfo: { name: "intel", version: "0.1.0" },
219
+ },
220
+ }),
221
+ })
222
+ .catch(() => null);
223
+ const candidates = metadataCandidates(resourceUrl, resourceMetadataChallenge(probe?.headers.get("www-authenticate") ?? null));
224
+ for (const candidate of candidates) {
225
+ const response = await deps
226
+ .fetch(candidate, {
227
+ signal: AbortSignal.timeout(RequestTimeoutMs),
228
+ headers: { accept: "application/json" },
229
+ })
230
+ .catch(() => null);
231
+ if (!response?.ok)
232
+ continue;
233
+ const metadata = ProtectedResource.safeParse(await response.json().catch(() => null));
234
+ if (!metadata.success)
235
+ continue;
236
+ if (new URL(metadata.data.resource).href !== new URL(resourceUrl).href)
237
+ continue;
238
+ const issuer = metadata.data.authorization_servers[0];
239
+ if (issuer) {
240
+ const scope = requestableScope(await publishedScopes(deps.fetch, issuer));
241
+ return {
242
+ resource: metadata.data.resource,
243
+ issuer,
244
+ ...(scope ? { scope } : {}),
245
+ };
246
+ }
247
+ }
248
+ throw new Error("MCP resource did not publish valid OAuth metadata");
249
+ },
250
+ async register(input) {
251
+ const configuration = await withAlgorithmFallback((algorithm) => client.dynamicClientRegistration(new URL(input.issuer), {
252
+ client_name: input.clientName,
253
+ redirect_uris: [input.redirectUri],
254
+ response_types: ["code"],
255
+ // ⚠️ `refresh_token` belongs here, not only in the scope. RFC 7591 §2: `grant_types`
256
+ // declares which grants this client may USE, and a client registered without it may
257
+ // not refresh — whatever scope it was granted. That is why the portal connection did
258
+ // not survive its first access token (#97): `offline_access` was being requested and
259
+ // the right to act on it never was.
260
+ grant_types: ["authorization_code", "refresh_token"],
261
+ ...(input.scope ? { scope: input.scope } : {}),
262
+ token_endpoint_auth_method: "none",
263
+ // ⚠️ Unconditional, and that is the protocol rather than a vendor accommodation. The
264
+ // MCP authorization spec: "MCP clients MUST send this parameter regardless of whether
265
+ // authorization servers support it" (RFC 8707 Resource Indicators). Sending it only to
266
+ // hosts whose name ended in `.cloudflareaccess.com` meant every portal on its own
267
+ // domain registered as a client that never named the resource it wanted a token for —
268
+ // and Cloudflare's Managed OAuth requires exactly that conformance before it will hand
269
+ // out a refresh token. The connection then died at the first token expiry, and the log
270
+ // said "no refresh token was ever issued" (#97).
271
+ resource: input.resource,
272
+ }, client.None(), { ...options(input.issuer), ...(algorithm ? { algorithm } : {}) }));
273
+ return configuration.clientMetadata().client_id;
274
+ },
275
+ async authorizationUrl(input) {
276
+ const configuration = await discover(input.issuer, input.clientId);
277
+ return client.buildAuthorizationUrl(configuration, {
278
+ response_type: "code",
279
+ redirect_uri: input.redirectUri,
280
+ ...(input.scope ? { scope: input.scope } : {}),
281
+ resource: input.resource,
282
+ code_challenge: input.codeChallenge,
283
+ code_challenge_method: "S256",
284
+ state: input.state,
285
+ ...(input.prompt ? { prompt: input.prompt } : {}),
286
+ });
287
+ },
288
+ async exchange(input) {
289
+ const configuration = await discover(input.issuer, input.clientId);
290
+ const tokens = await client.authorizationCodeGrant(configuration, input.callbackUrl, { pkceCodeVerifier: input.codeVerifier, expectedState: input.state }, { resource: input.resource });
291
+ return tokenResponse(tokens);
292
+ },
293
+ async refresh(input) {
294
+ const configuration = await discover(input.issuer, input.clientId);
295
+ const tokens = await client.refreshTokenGrant(configuration, input.refreshToken, {
296
+ resource: input.resource,
297
+ });
298
+ return tokenResponse(tokens);
299
+ },
300
+ };
301
+ }
@@ -0,0 +1,8 @@
1
+ import type { PortalTokenStore } from "../../tools/tools.types.js";
2
+ import type { D1Database } from "../db/db.types.js";
3
+ export declare function createPortalTokenStore(deps: {
4
+ db: D1Database;
5
+ secret: string;
6
+ crypto: Crypto;
7
+ now(): Date;
8
+ }): PortalTokenStore;
@@ -0,0 +1,79 @@
1
+ import { z } from "zod";
2
+ import { reportUnexpectedError } from "../../shared/report-unexpected-error/report-unexpected-error.js";
3
+ const StoredToken = z.strictObject({
4
+ version: z.literal(1),
5
+ accessToken: z.string().min(1),
6
+ refreshToken: z.string().min(1).nullable(),
7
+ expiresAt: z.number().int().positive(),
8
+ issuer: z.url(),
9
+ clientId: z.string().min(1),
10
+ resource: z.url(),
11
+ });
12
+ // Bound to this purpose so a sealed value cannot be replayed as a session cookie and vice versa.
13
+ const additionalData = new TextEncoder().encode("anchrd-intel-portal-token-v1");
14
+ function encode(bytes) {
15
+ let value = "";
16
+ for (const byte of bytes)
17
+ value += String.fromCharCode(byte);
18
+ return btoa(value);
19
+ }
20
+ function decode(value) {
21
+ return Uint8Array.from(atob(value), (character) => character.charCodeAt(0));
22
+ }
23
+ export function createPortalTokenStore(deps) {
24
+ if (new TextEncoder().encode(deps.secret).byteLength < 32) {
25
+ throw new Error("INTEL_SESSION_SECRET must contain at least 32 bytes");
26
+ }
27
+ const key = deps.crypto.subtle
28
+ .digest("SHA-256", new TextEncoder().encode(deps.secret))
29
+ .then((hash) => deps.crypto.subtle.importKey("raw", hash, "AES-GCM", false, ["encrypt", "decrypt"]));
30
+ return {
31
+ async read(userId) {
32
+ const row = await deps.db
33
+ .prepare("SELECT sealed FROM portal_tokens WHERE user_id = ?")
34
+ .bind(userId)
35
+ .first();
36
+ if (!row)
37
+ return null;
38
+ try {
39
+ const raw = decode(row.sealed);
40
+ const plaintext = await deps.crypto.subtle.decrypt({ name: "AES-GCM", iv: raw.subarray(0, 12), additionalData }, await key, raw.subarray(12));
41
+ const parsed = StoredToken.parse(JSON.parse(new TextDecoder().decode(plaintext)));
42
+ const { version: _version, ...token } = parsed;
43
+ return token;
44
+ }
45
+ catch (error) {
46
+ // A row that no longer decrypts (rotated secret, tampering) is treated as absent so the
47
+ // user is asked to reconnect instead of hitting an opaque failure on every call.
48
+ // ⚠️ Absent and unreadable look identical from the outside — both end in the sign-in screen
49
+ // — but only one of them is a defect. Without this line the difference is unobservable, and
50
+ // a connection that disappears has no explanation anywhere (#97). The row is named by its
51
+ // length alone; the sealed value never goes near a log.
52
+ reportUnexpectedError(new Error(`portal token for a user did not decrypt (sealed length ${row.sealed.length})`, {
53
+ cause: error,
54
+ }));
55
+ return null;
56
+ }
57
+ },
58
+ async write(userId, token) {
59
+ const iv = deps.crypto.getRandomValues(new Uint8Array(12));
60
+ const encrypted = new Uint8Array(await deps.crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData }, await key, new TextEncoder().encode(JSON.stringify({ version: 1, ...token }))));
61
+ const sealed = new Uint8Array(iv.byteLength + encrypted.byteLength);
62
+ sealed.set(iv);
63
+ sealed.set(encrypted, iv.byteLength);
64
+ const timestamp = deps.now().toISOString();
65
+ await deps.db
66
+ .prepare(`INSERT INTO portal_tokens (user_id, sealed, expires_at, updated_at)
67
+ VALUES (?, ?, ?, ?)
68
+ ON CONFLICT(user_id) DO UPDATE SET
69
+ sealed = excluded.sealed,
70
+ expires_at = excluded.expires_at,
71
+ updated_at = excluded.updated_at`)
72
+ .bind(userId, encode(sealed), new Date(token.expiresAt).toISOString(), timestamp)
73
+ .run();
74
+ },
75
+ async clear(userId) {
76
+ await deps.db.prepare("DELETE FROM portal_tokens WHERE user_id = ?").bind(userId).run();
77
+ },
78
+ };
79
+ }
@@ -0,0 +1,5 @@
1
+ import type { RemoteTools } from "../../tools/tools.types.js";
2
+ export interface RemoteToolsDeps {
3
+ fetch: (url: string | URL, init?: RequestInit) => Promise<Response>;
4
+ }
5
+ export declare function createRemoteTools(deps: RemoteToolsDeps): RemoteTools;