blume 2.0.3 → 2.1.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 (455) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +48 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-1d7ve1dm.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-1d7ve1dm.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-2eytanqx.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-yfyb25rh.js → chunk-2f3ndkpw.js} +24 -8
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-r20tn01b.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-r20tn01b.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-35d4wj9f.js → chunk-2n4ps1b3.js} +7 -7
  12. package/dist/cli/{chunk-j85vccga.js → chunk-43zcf4gz.js} +97 -57
  13. package/dist/cli/{chunk-j85vccga.js.map → chunk-43zcf4gz.js.map} +6 -6
  14. package/dist/cli/{chunk-e04dxsz1.js → chunk-4ejxbzee.js} +1 -1
  15. package/dist/cli/{chunk-5m5nmvyq.js → chunk-4h8z9vv5.js} +233 -39
  16. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  17. package/dist/cli/{chunk-5f86nr5m.js → chunk-5cg2rzgf.js} +18 -16
  18. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-5cg2rzgf.js.map} +2 -2
  19. package/dist/cli/{chunk-tzmab476.js → chunk-5ffywsmb.js} +4 -4
  20. package/dist/cli/{chunk-2z928egk.js → chunk-5wj2qwn2.js} +5 -5
  21. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  22. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  23. package/dist/cli/{chunk-vg9r4eb9.js → chunk-713mngdq.js} +12 -5
  24. package/dist/cli/{chunk-vg9r4eb9.js.map → chunk-713mngdq.js.map} +3 -3
  25. package/dist/cli/{chunk-5xvm6tfj.js → chunk-9geymqns.js} +17 -15
  26. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-9geymqns.js.map} +2 -2
  27. package/dist/cli/{chunk-q58y5e6a.js → chunk-9xr4dgee.js} +26 -17
  28. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  29. package/dist/cli/{chunk-b07cmahc.js → chunk-amrb6hgh.js} +2 -2
  30. package/dist/cli/chunk-cm89scdv.js +443 -0
  31. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  32. package/dist/cli/{chunk-akbpwfxc.js → chunk-cmtcnh9f.js} +2 -2
  33. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  34. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  35. package/dist/cli/{chunk-crgn1q09.js → chunk-e7vvk5ya.js} +4 -4
  36. package/dist/cli/{chunk-pehfxfta.js → chunk-g2z9x01v.js} +6 -98
  37. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  38. package/dist/cli/{chunk-tkacnehg.js → chunk-gp84za0h.js} +2 -2
  39. package/dist/cli/{chunk-wrr3j9w9.js → chunk-h2a48yhd.js} +186 -63
  40. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  41. package/dist/cli/{chunk-hm3vjy5s.js → chunk-jnwj5kjj.js} +14 -9
  42. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-jnwj5kjj.js.map} +4 -4
  43. package/dist/cli/chunk-kwrg63tm.js +31 -0
  44. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  45. package/dist/cli/{chunk-2hsdwb9n.js → chunk-nd1821sk.js} +18 -16
  46. package/dist/cli/{chunk-2hsdwb9n.js.map → chunk-nd1821sk.js.map} +2 -2
  47. package/dist/cli/{chunk-6dsbexzp.js → chunk-ntv4qrt7.js} +17 -15
  48. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-ntv4qrt7.js.map} +2 -2
  49. package/dist/cli/{chunk-r9rcc4w7.js → chunk-p804f3pe.js} +7 -7
  50. package/dist/cli/{chunk-ey84smr6.js → chunk-py62fewh.js} +6 -6
  51. package/dist/cli/{chunk-ey84smr6.js.map → chunk-py62fewh.js.map} +1 -1
  52. package/dist/cli/chunk-q3wxxq39.js +126 -0
  53. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  54. package/dist/cli/{chunk-9t7a85s3.js → chunk-rekqz4qg.js} +23 -13
  55. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  56. package/dist/cli/{chunk-8ktnccpt.js → chunk-ta27feev.js} +1 -1
  57. package/dist/cli/{chunk-pv29h0wf.js → chunk-xkz5xbtv.js} +3476 -822
  58. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  59. package/dist/cli/{chunk-a58773jm.js → chunk-y4ykbark.js} +2 -2
  60. package/dist/cli/{chunk-p3v96n38.js → chunk-y5h4r2y0.js} +4 -4
  61. package/dist/cli/{chunk-acanzt5p.js → chunk-yj17vs39.js} +6 -6
  62. package/dist/cli/{chunk-p73c0m7w.js → chunk-yxmk4pec.js} +16 -14
  63. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-yxmk4pec.js.map} +2 -2
  64. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  65. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  66. package/dist/cli/{chunk-c8chx29p.js → chunk-zf748avv.js} +384 -207
  67. package/dist/cli/chunk-zf748avv.js.map +37 -0
  68. package/dist/cli/{chunk-g4hq16wv.js → chunk-zhvd6adm.js} +16 -14
  69. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-zhvd6adm.js.map} +2 -2
  70. package/dist/cli/index.js +26 -20
  71. package/dist/cli/index.js.map +5 -5
  72. package/dist/types/ai/agent-surface.d.ts +8 -7
  73. package/dist/types/ai/ai-catalog.d.ts +7 -0
  74. package/dist/types/ai/ask-context.d.ts +22 -0
  75. package/dist/types/ai/ask.d.ts +271 -58
  76. package/dist/types/ai/index.d.ts +2 -2
  77. package/dist/types/ai/site-skill.d.ts +26 -0
  78. package/dist/types/ai/skills.d.ts +16 -0
  79. package/dist/types/astro/integration.d.ts +8 -0
  80. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  81. package/dist/types/captcha/index.d.ts +28 -0
  82. package/dist/types/captcha/schema.d.ts +29 -0
  83. package/dist/types/captcha/turnstile.d.ts +28 -0
  84. package/dist/types/components/content/api-field.d.ts +59 -0
  85. package/dist/types/components/content/api-page.d.ts +95 -0
  86. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  87. package/dist/types/components/openapi/helpers.d.ts +137 -0
  88. package/dist/types/components/openapi/security.d.ts +85 -0
  89. package/dist/types/consent/ethyca.d.ts +48 -0
  90. package/dist/types/consent/index.d.ts +37 -0
  91. package/dist/types/consent/native.d.ts +28 -0
  92. package/dist/types/consent/osano.d.ts +42 -0
  93. package/dist/types/consent/schema.d.ts +110 -0
  94. package/dist/types/core/config-input.d.ts +187 -20
  95. package/dist/types/core/data.d.ts +49 -4
  96. package/dist/types/core/footer.d.ts +8 -0
  97. package/dist/types/core/i18n-ui.d.ts +86 -0
  98. package/dist/types/core/includes.d.ts +8 -2
  99. package/dist/types/core/localizable.d.ts +7 -0
  100. package/dist/types/core/page-modes.d.ts +29 -0
  101. package/dist/types/core/redirect-patterns.d.ts +144 -0
  102. package/dist/types/core/related.d.ts +40 -0
  103. package/dist/types/core/schema.d.ts +551 -56
  104. package/dist/types/core/sources/read.d.ts +5 -0
  105. package/dist/types/core/types.d.ts +1 -1
  106. package/dist/types/core/variables.d.ts +42 -0
  107. package/dist/types/deploy/node-headers.d.ts +13 -2
  108. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  109. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  110. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  111. package/dist/types/deploy/redirects.d.ts +39 -11
  112. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  113. package/dist/types/markdown/code-title.d.ts +17 -0
  114. package/dist/types/markdown/features.d.ts +3 -2
  115. package/dist/types/narration/build.d.ts +70 -0
  116. package/dist/types/narration/provider.d.ts +26 -0
  117. package/dist/types/narration/script.d.ts +154 -0
  118. package/dist/types/openapi/model.d.ts +12 -6
  119. package/dist/types/openapi/overlay.d.ts +54 -0
  120. package/dist/types/openapi/parse.d.ts +13 -0
  121. package/dist/types/openapi/references.d.ts +14 -4
  122. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  123. package/dist/types/ratelimit/index.d.ts +27 -0
  124. package/dist/types/ratelimit/memory.d.ts +36 -0
  125. package/dist/types/ratelimit/schema.d.ts +111 -0
  126. package/dist/types/ratelimit/upstash.d.ts +21 -0
  127. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  128. package/dist/types/reference/asyncapi.d.ts +10 -10
  129. package/dist/types/reference/graphql.d.ts +13 -10
  130. package/dist/types/reference/openapi.d.ts +33 -12
  131. package/dist/types/reference/options.d.ts +45 -5
  132. package/dist/types/reference/scalar.d.ts +13 -0
  133. package/dist/types/reference/schema.d.ts +60 -36
  134. package/dist/types/search/documents.d.ts +12 -1
  135. package/dist/types/search/orama-index.d.ts +4 -0
  136. package/dist/types/seo/metatags.d.ts +20 -0
  137. package/docs/02-deployment.mdx +41 -5
  138. package/docs/03-upgrading.mdx +4 -4
  139. package/docs/04-migrating.mdx +1 -1
  140. package/docs/advanced/changelog.mdx +2 -2
  141. package/docs/advanced/skills.mdx +14 -0
  142. package/docs/cli/audit.mdx +21 -1
  143. package/docs/cli/index.mdx +1 -0
  144. package/docs/configuration/analytics.mdx +3 -1
  145. package/docs/configuration/assistant.mdx +188 -36
  146. package/docs/configuration/consent.mdx +100 -0
  147. package/docs/configuration/customization.mdx +4 -4
  148. package/docs/configuration/index.mdx +63 -5
  149. package/docs/configuration/meta.ts +3 -0
  150. package/docs/configuration/narration.mdx +119 -0
  151. package/docs/configuration/rate-limiting.mdx +69 -0
  152. package/docs/configuration/search.mdx +29 -1
  153. package/docs/configuration/theming.mdx +3 -1
  154. package/docs/content/components.mdx +106 -0
  155. package/docs/content/frontmatter.mdx +76 -0
  156. package/docs/content/i18n.mdx +38 -3
  157. package/docs/content/includes.mdx +16 -0
  158. package/docs/content/meta.mdx +1 -0
  159. package/docs/content/meta.ts +1 -0
  160. package/docs/content/navigation.mdx +28 -6
  161. package/docs/content/sources/contentful.mdx +32 -0
  162. package/docs/content/sources/custom.mdx +39 -0
  163. package/docs/content/sources/github-releases.mdx +31 -0
  164. package/docs/content/sources/index.mdx +91 -0
  165. package/docs/content/sources/meta.ts +16 -0
  166. package/docs/content/sources/notion.mdx +32 -0
  167. package/docs/content/sources/obsidian.mdx +37 -0
  168. package/docs/content/sources/payload.mdx +30 -0
  169. package/docs/content/sources/remote-mdx.mdx +40 -0
  170. package/docs/content/sources/sanity.mdx +29 -0
  171. package/docs/content/sources/strapi.mdx +31 -0
  172. package/docs/content/syntax.mdx +67 -2
  173. package/docs/content/variables.mdx +41 -0
  174. package/docs/discoverability/agent-discovery.mdx +29 -2
  175. package/docs/discoverability/index.mdx +1 -1
  176. package/docs/discoverability/llms-txt.mdx +1 -1
  177. package/docs/discoverability/metadata.mdx +17 -0
  178. package/docs/references/api-pages.mdx +74 -0
  179. package/docs/references/asyncapi.mdx +1 -1
  180. package/docs/references/graphql.mdx +2 -2
  181. package/docs/references/meta.ts +1 -1
  182. package/docs/references/openapi.mdx +102 -3
  183. package/docs/references/scalar.mdx +3 -1
  184. package/package.json +36 -1
  185. package/skills/blume/SKILL.md +3 -0
  186. package/skills/blume-migrate/SKILL.md +5 -5
  187. package/skills/blume-migrate/references/docusaurus.md +3 -3
  188. package/skills/blume-migrate/references/mintlify.md +37 -19
  189. package/skills/blume-migrate/references/nextra.md +2 -2
  190. package/skills/blume-migrate/references/starlight.md +2 -1
  191. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  192. package/skills/blume-write-skill/SKILL.md +82 -0
  193. package/src/ai/agent-readability.ts +2 -1
  194. package/src/ai/agent-surface.ts +9 -7
  195. package/src/ai/ai-catalog.ts +6 -3
  196. package/src/ai/ask-context.ts +73 -20
  197. package/src/ai/ask-data.ts +6 -0
  198. package/src/ai/ask-tools.ts +142 -0
  199. package/src/ai/ask.ts +354 -76
  200. package/src/ai/component-markdown.ts +75 -0
  201. package/src/ai/cors.ts +2 -0
  202. package/src/ai/index.ts +12 -0
  203. package/src/ai/llms.ts +6 -2
  204. package/src/ai/markdown.ts +2 -1
  205. package/src/ai/mcp/data.ts +6 -0
  206. package/src/ai/openapi-components.ts +9 -3
  207. package/src/ai/site-skill.ts +274 -0
  208. package/src/ai/skills.ts +35 -2
  209. package/src/ai/write-skill.ts +97 -0
  210. package/src/astro/generate.ts +132 -18
  211. package/src/astro/index.ts +1 -0
  212. package/src/astro/integration.ts +35 -0
  213. package/src/astro/module-types.ts +6 -0
  214. package/src/astro/templates.ts +283 -30
  215. package/src/astro/variables.ts +31 -0
  216. package/src/audit/catalog.ts +19 -0
  217. package/src/audit/checks/contrast.ts +183 -0
  218. package/src/audit/checks/i18n.ts +2 -3
  219. package/src/audit/checks/indexability.ts +2 -3
  220. package/src/audit/checks/links.ts +5 -12
  221. package/src/audit/checks/sitemap.ts +2 -3
  222. package/src/audit/redirects.ts +46 -15
  223. package/src/audit/run.ts +2 -0
  224. package/src/audit/types.ts +2 -1
  225. package/src/blume-modules.d.ts +7 -0
  226. package/src/captcha/client.ts +152 -0
  227. package/src/captcha/hcaptcha.ts +36 -0
  228. package/src/captcha/index.ts +28 -0
  229. package/src/captcha/schema.ts +32 -0
  230. package/src/captcha/turnstile.ts +38 -0
  231. package/src/captcha/verify.ts +71 -0
  232. package/src/cli/command-meta.ts +5 -0
  233. package/src/cli/commands/doctor.ts +22 -16
  234. package/src/cli/commands/skill.ts +96 -0
  235. package/src/cli/index.ts +5 -0
  236. package/src/cli/required-secrets.ts +71 -29
  237. package/src/components/BlumePage.astro +2 -0
  238. package/src/components/content/ApiEndpoint.astro +18 -0
  239. package/src/components/content/ApiField.astro +79 -0
  240. package/src/components/content/ApiPlayground.astro +62 -0
  241. package/src/components/content/ApiRail.astro +19 -0
  242. package/src/components/content/Color.astro +1 -0
  243. package/src/components/content/FileTree.astro +1 -0
  244. package/src/components/content/GithubInfo.astro +1 -0
  245. package/src/components/content/Panel.astro +1 -0
  246. package/src/components/content/ParamField.astro +25 -0
  247. package/src/components/content/RequestExample.astro +17 -0
  248. package/src/components/content/ResponseExample.astro +17 -0
  249. package/src/components/content/ResponseField.astro +32 -0
  250. package/src/components/content/Step.astro +1 -0
  251. package/src/components/content/Tile.astro +1 -0
  252. package/src/components/content/Tooltip.astro +1 -0
  253. package/src/components/content/Tree.astro +1 -0
  254. package/src/components/content/TypeTable.astro +4 -1
  255. package/src/components/content/View.astro +21 -0
  256. package/src/components/content/ViewSwitcher.astro +168 -0
  257. package/src/components/content/api-field.ts +100 -0
  258. package/src/components/content/api-page.ts +224 -0
  259. package/src/components/content/view-switcher.ts +71 -0
  260. package/src/components/index.ts +2 -0
  261. package/src/components/islands/Assistant.astro +10 -1
  262. package/src/components/islands/assistant.tsx +104 -14
  263. package/src/components/islands/code-question.ts +62 -0
  264. package/src/components/islands/hooks.ts +110 -21
  265. package/src/components/islands/support-link.ts +68 -0
  266. package/src/components/layout/Analytics.astro +11 -2
  267. package/src/components/layout/Banner.astro +14 -10
  268. package/src/components/layout/Breadcrumbs.astro +6 -2
  269. package/src/components/layout/ConsentBanner.astro +62 -0
  270. package/src/components/layout/ConsentHead.astro +38 -0
  271. package/src/components/layout/Directory.astro +94 -0
  272. package/src/components/layout/DrawerSwitchers.astro +51 -0
  273. package/src/components/layout/Empty.astro +2 -2
  274. package/src/components/layout/Header.astro +122 -80
  275. package/src/components/layout/LanguageSwitcher.astro +48 -15
  276. package/src/components/layout/NarrationPlayer.astro +1085 -0
  277. package/src/components/layout/NavSelector.astro +33 -7
  278. package/src/components/layout/PageFeedback.astro +54 -49
  279. package/src/components/layout/PageLayout.astro +71 -3
  280. package/src/components/layout/ReferenceLayout.astro +17 -6
  281. package/src/components/layout/RelatedPages.astro +42 -0
  282. package/src/components/layout/RootLayout.astro +308 -45
  283. package/src/components/layout/Search.astro +31 -17
  284. package/src/components/layout/SiteFooter.astro +124 -0
  285. package/src/components/layout/SiteMetatags.astro +16 -0
  286. package/src/components/layout/consent/native.ts +79 -0
  287. package/src/components/layout/consent/types.ts +37 -0
  288. package/src/components/layout/footer-links.ts +53 -0
  289. package/src/components/layout/footer-socials.ts +87 -0
  290. package/src/components/layout/locale-redirect.ts +107 -0
  291. package/src/components/layout/nav-utils.ts +83 -0
  292. package/src/components/layout/page-feedback.ts +96 -0
  293. package/src/components/layout/search/analytics.ts +98 -0
  294. package/src/components/layout/search/flexsearch.ts +17 -5
  295. package/src/components/layout/search/orama-cloud.ts +26 -14
  296. package/src/components/layout/search/types.ts +20 -0
  297. package/src/components/layout/search/typesense.ts +5 -1
  298. package/src/components/openapi/Operation.astro +161 -13
  299. package/src/components/openapi/RequestBody.astro +6 -4
  300. package/src/components/openapi/RequestPanel.astro +59 -7
  301. package/src/components/openapi/Responses.astro +6 -4
  302. package/src/components/openapi/async-snippets.ts +4 -4
  303. package/src/components/openapi/code-samples.ts +97 -0
  304. package/src/components/openapi/security.ts +6 -3
  305. package/src/components/openapi/snippets.ts +869 -8
  306. package/src/components/props.ts +6 -0
  307. package/src/consent/client.ts +95 -0
  308. package/src/consent/clients.ts +19 -0
  309. package/src/consent/ethyca.ts +71 -0
  310. package/src/consent/head.ts +24 -0
  311. package/src/consent/index.ts +37 -0
  312. package/src/consent/init.ts +29 -0
  313. package/src/consent/native.ts +36 -0
  314. package/src/consent/osano.ts +59 -0
  315. package/src/consent/schema.ts +30 -0
  316. package/src/core/builtin-tags.ts +5 -0
  317. package/src/core/client-address.ts +19 -0
  318. package/src/core/config-input.ts +194 -20
  319. package/src/core/data.ts +44 -4
  320. package/src/core/diagnostics.ts +3 -2
  321. package/src/core/footer.ts +26 -0
  322. package/src/core/graph.ts +39 -36
  323. package/src/core/i18n-ui.ts +82 -2
  324. package/src/core/includes.ts +20 -3
  325. package/src/core/links.ts +16 -2
  326. package/src/core/localizable.ts +26 -0
  327. package/src/core/navigation.ts +43 -12
  328. package/src/core/page-modes.ts +56 -0
  329. package/src/core/project-graph.ts +111 -5
  330. package/src/core/redirect-patterns.ts +491 -0
  331. package/src/core/related.ts +131 -0
  332. package/src/core/schema.ts +318 -64
  333. package/src/core/sources/normalize.ts +2 -1
  334. package/src/core/sources/read.ts +8 -3
  335. package/src/core/types.ts +1 -1
  336. package/src/core/ui-packs/ar.ts +43 -0
  337. package/src/core/ui-packs/bg.ts +45 -0
  338. package/src/core/ui-packs/bn.ts +43 -0
  339. package/src/core/ui-packs/ca.ts +46 -0
  340. package/src/core/ui-packs/cs.ts +44 -0
  341. package/src/core/ui-packs/da.ts +45 -0
  342. package/src/core/ui-packs/de.ts +46 -0
  343. package/src/core/ui-packs/el.ts +45 -0
  344. package/src/core/ui-packs/es.ts +46 -0
  345. package/src/core/ui-packs/fa.ts +45 -0
  346. package/src/core/ui-packs/fi.ts +45 -0
  347. package/src/core/ui-packs/fr.ts +46 -0
  348. package/src/core/ui-packs/he.ts +43 -0
  349. package/src/core/ui-packs/hi.ts +44 -0
  350. package/src/core/ui-packs/hr.ts +45 -0
  351. package/src/core/ui-packs/hu.ts +44 -0
  352. package/src/core/ui-packs/id.ts +45 -0
  353. package/src/core/ui-packs/it.ts +45 -0
  354. package/src/core/ui-packs/ja.ts +45 -0
  355. package/src/core/ui-packs/ko.ts +44 -0
  356. package/src/core/ui-packs/nl.ts +46 -0
  357. package/src/core/ui-packs/no.ts +44 -0
  358. package/src/core/ui-packs/pl.ts +45 -0
  359. package/src/core/ui-packs/pt-br.ts +46 -0
  360. package/src/core/ui-packs/pt.ts +45 -0
  361. package/src/core/ui-packs/ro.ts +45 -0
  362. package/src/core/ui-packs/ru.ts +45 -0
  363. package/src/core/ui-packs/sk.ts +44 -0
  364. package/src/core/ui-packs/sr.ts +45 -0
  365. package/src/core/ui-packs/sv.ts +44 -0
  366. package/src/core/ui-packs/th.ts +43 -0
  367. package/src/core/ui-packs/tr.ts +45 -0
  368. package/src/core/ui-packs/uk.ts +45 -0
  369. package/src/core/ui-packs/vi.ts +45 -0
  370. package/src/core/ui-packs/zh-tw.ts +43 -0
  371. package/src/core/ui-packs/zh.ts +43 -0
  372. package/src/core/variables.ts +103 -0
  373. package/src/deploy/artifacts.ts +77 -8
  374. package/src/deploy/cloudflare-negotiation.ts +35 -5
  375. package/src/deploy/headers.ts +2 -1
  376. package/src/deploy/node-headers.ts +63 -9
  377. package/src/deploy/platforms/cloudflare.ts +32 -0
  378. package/src/deploy/platforms/netlify.ts +33 -1
  379. package/src/deploy/platforms/vercel.ts +37 -2
  380. package/src/deploy/redirects.ts +91 -24
  381. package/src/deploy/vercel-negotiation.ts +36 -0
  382. package/src/markdown/api-rail.ts +259 -0
  383. package/src/markdown/code-title.ts +35 -9
  384. package/src/markdown/features.ts +3 -2
  385. package/src/markdown/fence-meta.ts +2 -0
  386. package/src/markdown/include.ts +11 -1
  387. package/src/markdown/index.ts +28 -5
  388. package/src/markdown/math.ts +4 -4
  389. package/src/markdown/variables.ts +56 -0
  390. package/src/markdown/views.ts +82 -0
  391. package/src/narration/build.ts +412 -0
  392. package/src/narration/player.ts +249 -0
  393. package/src/narration/provider.ts +29 -0
  394. package/src/narration/script.ts +592 -0
  395. package/src/og/card.ts +7 -6
  396. package/src/openapi/model.ts +57 -24
  397. package/src/openapi/overlay.ts +256 -0
  398. package/src/openapi/parse.ts +55 -2
  399. package/src/openapi/references.ts +21 -5
  400. package/src/openapi/render-mdx.ts +5 -1
  401. package/src/openapi/scalar.ts +40 -6
  402. package/src/openapi/source.ts +1 -1
  403. package/src/ratelimit/cloudflare.ts +54 -0
  404. package/src/ratelimit/index.ts +30 -0
  405. package/src/ratelimit/memory.ts +45 -0
  406. package/src/ratelimit/runtime.ts +229 -0
  407. package/src/ratelimit/schema.ts +28 -0
  408. package/src/ratelimit/upstash.ts +30 -0
  409. package/src/ratelimit/wrangler.ts +70 -0
  410. package/src/reference/asyncapi.ts +2 -2
  411. package/src/reference/graphql.ts +5 -2
  412. package/src/reference/openapi.ts +21 -7
  413. package/src/reference/options.ts +51 -7
  414. package/src/reference/scalar.ts +33 -8
  415. package/src/registry/eject.ts +20 -4
  416. package/src/registry/registry.ts +7 -0
  417. package/src/search/documents.ts +26 -2
  418. package/src/search/orama-index.ts +28 -3
  419. package/src/search/pagefind-ranking.ts +39 -0
  420. package/src/search/sync/algolia.ts +15 -4
  421. package/src/search/sync/orama-cloud.ts +3 -0
  422. package/src/search/sync/typesense.ts +5 -0
  423. package/src/seo/metatags.ts +67 -0
  424. package/src/theme/chrome-icons.ts +1 -0
  425. package/src/theme/color.ts +500 -0
  426. package/src/theme/entry.ts +56 -3
  427. package/src/theme/palette.ts +121 -45
  428. package/src/translate/prompts.ts +1 -0
  429. package/src/translate/validate.ts +8 -2
  430. package/src/upgrade/upgrade.ts +4 -171
  431. package/dist/cli/chunk-364znk6q.js +0 -75
  432. package/dist/cli/chunk-364znk6q.js.map +0 -11
  433. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  434. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  435. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  436. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  437. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  438. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  439. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  440. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  441. package/docs/content/sources.mdx +0 -334
  442. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-1n228xqb.js.map} +0 -0
  443. /package/dist/cli/{chunk-35d4wj9f.js.map → chunk-2n4ps1b3.js.map} +0 -0
  444. /package/dist/cli/{chunk-e04dxsz1.js.map → chunk-4ejxbzee.js.map} +0 -0
  445. /package/dist/cli/{chunk-tzmab476.js.map → chunk-5ffywsmb.js.map} +0 -0
  446. /package/dist/cli/{chunk-2z928egk.js.map → chunk-5wj2qwn2.js.map} +0 -0
  447. /package/dist/cli/{chunk-b07cmahc.js.map → chunk-amrb6hgh.js.map} +0 -0
  448. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-cmtcnh9f.js.map} +0 -0
  449. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-e7vvk5ya.js.map} +0 -0
  450. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-gp84za0h.js.map} +0 -0
  451. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-p804f3pe.js.map} +0 -0
  452. /package/dist/cli/{chunk-8ktnccpt.js.map → chunk-ta27feev.js.map} +0 -0
  453. /package/dist/cli/{chunk-a58773jm.js.map → chunk-y4ykbark.js.map} +0 -0
  454. /package/dist/cli/{chunk-p3v96n38.js.map → chunk-y5h4r2y0.js.map} +0 -0
  455. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-yj17vs39.js.map} +0 -0
package/src/ai/ask.ts CHANGED
@@ -34,6 +34,15 @@ export type AssistantProviderOptions = Record<
34
34
  /** The AI SDK provider package the OpenAI-compatible adapters install. */
35
35
  const OPENAI_COMPATIBLE_DEP = "@ai-sdk/openai-compatible";
36
36
 
37
+ /**
38
+ * The AI SDK packages for the providers Blume calls directly: the package and
39
+ * the provider factory the route imports from it.
40
+ */
41
+ const OPENAI_SDK = { factory: "createOpenAI", pkg: "@ai-sdk/openai" };
42
+ const ANTHROPIC_SDK = { factory: "createAnthropic", pkg: "@ai-sdk/anthropic" };
43
+ const GEMINI_SDK = { factory: "createGoogle", pkg: "@ai-sdk/google" };
44
+ const GROK_SDK = { factory: "createXai", pkg: "@ai-sdk/xai" };
45
+
37
46
  // ---------------------------------------------------------------------------
38
47
  // Shared options
39
48
  // ---------------------------------------------------------------------------
@@ -69,7 +78,7 @@ const providerOptionsSchema = z.record(
69
78
  );
70
79
 
71
80
  /** The shared option schema, with the adapter's own key env var default. */
72
- const sharedOptions = (apiKeyEnv: string) => ({
81
+ export const sharedOptions = (apiKeyEnv: string) => ({
73
82
  apiKeyEnv: z.string().min(1).default(apiKeyEnv),
74
83
  headers: z.record(z.string(), z.string()).optional(),
75
84
  providerOptions: providerOptionsSchema.optional(),
@@ -81,13 +90,21 @@ const reasoningOption = z.enum(assistantReasoningLevels).optional();
81
90
  // gateway()
82
91
  // ---------------------------------------------------------------------------
83
92
 
84
- const GATEWAY_API_KEY_ENV = "AI_GATEWAY_API_KEY";
93
+ export const GATEWAY_API_KEY_ENV = "AI_GATEWAY_API_KEY";
85
94
  /** The model the gateway adapter uses when none is configured. */
86
95
  const DEFAULT_GATEWAY_MODEL = "openai/gpt-5.5";
87
96
 
88
97
  /** Options for {@link gateway}. */
89
98
  export interface AssistantGatewayOptions extends AssistantAdapterOptions {
90
- /** A `provider/model` id routed by the gateway. Defaults to `openai/gpt-5.5`. */
99
+ /**
100
+ * Narration only: how the voice should sound, for speech models that take
101
+ * instructions ("Read calmly, like a teacher").
102
+ */
103
+ instructions?: string;
104
+ /**
105
+ * A `provider/model` id routed by the gateway. Defaults to `openai/gpt-5.5`
106
+ * for the assistant and `openai/tts-1-hd` for narration.
107
+ */
91
108
  model?: string;
92
109
  /**
93
110
  * How much the model reasons before answering. Sent as the AI SDK's
@@ -96,6 +113,8 @@ export interface AssistantGatewayOptions extends AssistantAdapterOptions {
96
113
  * offer the level you pick. Omitted keeps the model's default.
97
114
  */
98
115
  reasoning?: AssistantReasoning;
116
+ /** Narration only: the speech model's voice. Defaults to `alloy`. */
117
+ voice?: string;
99
118
  }
100
119
 
101
120
  const gatewayOptionsSchema = z.strictObject({
@@ -115,7 +134,8 @@ export const gatewayAdapterSchema = adapterDescriptorSchema(
115
134
  );
116
135
 
117
136
  /**
118
- * Route the assistant through the Vercel AI Gateway (the default). `model` is a
137
+ * Route the assistant through the Vercel AI Gateway (the default), or generate
138
+ * narration audio with one of its speech models. `model` is a
119
139
  * `provider/model` string; the key is `AI_GATEWAY_API_KEY`, or Vercel's OIDC
120
140
  * token when deployed there. Needs no provider SDK beyond the `ai` package
121
141
  * Blume ships.
@@ -129,6 +149,233 @@ export const gateway = (
129
149
  runtimeDeps: [],
130
150
  });
131
151
 
152
+ // ---------------------------------------------------------------------------
153
+ // openai()
154
+ // ---------------------------------------------------------------------------
155
+
156
+ const OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
157
+
158
+ /** Options for {@link openai}. */
159
+ export interface AssistantOpenAIOptions extends AssistantAdapterOptions {
160
+ /**
161
+ * An OpenAI-compatible endpoint to call instead of OpenAI
162
+ * (`https://llm.internal.example.com/v1`): a self-hosted model or an
163
+ * internal gateway. The route then talks Chat Completions through
164
+ * `@ai-sdk/openai-compatible`, and the docs tools are off unless
165
+ * `ai.assistant.tools` turns them on, since the model may not call tools.
166
+ */
167
+ baseUrl?: string;
168
+ /** The model id (`gpt-5.5`). */
169
+ model: string;
170
+ /**
171
+ * The provider name the AI SDK reports, which a custom endpoint also reads
172
+ * its `providerOptions` under. Defaults to `openai`, or `openai-compatible`
173
+ * with a `baseUrl`.
174
+ */
175
+ name?: string;
176
+ /**
177
+ * How much the model reasons before answering, sent as `reasoning_effort`;
178
+ * with a `baseUrl`, the endpoint has to accept that parameter. Omitted
179
+ * keeps the model's default.
180
+ */
181
+ reasoning?: AssistantReasoning;
182
+ }
183
+
184
+ const openaiOptionsSchema = z.strictObject({
185
+ ...sharedOptions(OPENAI_API_KEY_ENV),
186
+ baseUrl: z.url().optional(),
187
+ model: z.string().min(1),
188
+ name: z.string().min(1).optional(),
189
+ reasoning: reasoningOption,
190
+ });
191
+
192
+ export type AssistantOpenAIAdapter = AdapterDescriptor<
193
+ "openai",
194
+ AssistantOpenAIOptions
195
+ >;
196
+
197
+ export const openaiAdapterSchema = adapterDescriptorSchema(
198
+ "openai",
199
+ openaiOptionsSchema
200
+ );
201
+
202
+ /**
203
+ * Route the assistant to OpenAI, or to any OpenAI-compatible endpoint with
204
+ * `baseUrl`. Reads `OPENAI_API_KEY` and needs `@ai-sdk/openai` installed; a
205
+ * custom endpoint needs `@ai-sdk/openai-compatible` instead, since most
206
+ * don't serve OpenAI's Responses API.
207
+ */
208
+ export const openai = (
209
+ options: AssistantOpenAIOptions
210
+ ): AssistantOpenAIAdapter => ({
211
+ kind: "openai",
212
+ options,
213
+ requiredSecrets: [options.apiKeyEnv ?? OPENAI_API_KEY_ENV],
214
+ runtimeDeps: [options.baseUrl ? OPENAI_COMPATIBLE_DEP : OPENAI_SDK.pkg],
215
+ });
216
+
217
+ /**
218
+ * Options for the deprecated {@link openaiCompatible}: {@link openai}'s, with
219
+ * the endpoint and its key env var required.
220
+ *
221
+ * @deprecated Use {@link AssistantOpenAIOptions}.
222
+ */
223
+ export interface AssistantOpenAICompatibleOptions extends AssistantOpenAIOptions {
224
+ apiKeyEnv: string;
225
+ baseUrl: string;
226
+ }
227
+
228
+ /** @deprecated Use {@link AssistantOpenAIAdapter}. */
229
+ export type AssistantOpenAICompatibleAdapter = AssistantOpenAIAdapter;
230
+
231
+ /**
232
+ * Route the assistant through an OpenAI-compatible endpoint.
233
+ *
234
+ * @deprecated Use `openai({ baseUrl, model, apiKeyEnv })`, which this calls.
235
+ */
236
+ export const openaiCompatible = (
237
+ options: AssistantOpenAICompatibleOptions
238
+ ): AssistantOpenAIAdapter => openai(options);
239
+
240
+ // ---------------------------------------------------------------------------
241
+ // anthropic()
242
+ // ---------------------------------------------------------------------------
243
+
244
+ const ANTHROPIC_API_KEY_ENV = "ANTHROPIC_API_KEY";
245
+
246
+ /** Options for {@link anthropic}. */
247
+ export interface AssistantAnthropicOptions extends AssistantAdapterOptions {
248
+ /** The Claude model id (`claude-sonnet-5`). */
249
+ model: string;
250
+ /**
251
+ * How much the model reasons before answering. The Anthropic provider
252
+ * maps the level to the model's thinking: an effort on models with
253
+ * adaptive thinking, a thinking budget on older ones, and thinking off for
254
+ * `"none"`. Omitted keeps the model's default.
255
+ */
256
+ reasoning?: AssistantReasoning;
257
+ }
258
+
259
+ const anthropicOptionsSchema = z.strictObject({
260
+ ...sharedOptions(ANTHROPIC_API_KEY_ENV),
261
+ model: z.string().min(1),
262
+ reasoning: reasoningOption,
263
+ });
264
+
265
+ export type AssistantAnthropicAdapter = AdapterDescriptor<
266
+ "anthropic",
267
+ AssistantAnthropicOptions
268
+ >;
269
+
270
+ export const anthropicAdapterSchema = adapterDescriptorSchema(
271
+ "anthropic",
272
+ anthropicOptionsSchema
273
+ );
274
+
275
+ /**
276
+ * Route the assistant to Anthropic's Claude API. Reads `ANTHROPIC_API_KEY`
277
+ * and needs `@ai-sdk/anthropic` installed.
278
+ */
279
+ export const anthropic = (
280
+ options: AssistantAnthropicOptions
281
+ ): AssistantAnthropicAdapter => ({
282
+ kind: "anthropic",
283
+ options,
284
+ requiredSecrets: [options.apiKeyEnv ?? ANTHROPIC_API_KEY_ENV],
285
+ runtimeDeps: [ANTHROPIC_SDK.pkg],
286
+ });
287
+
288
+ // ---------------------------------------------------------------------------
289
+ // gemini()
290
+ // ---------------------------------------------------------------------------
291
+
292
+ const GEMINI_API_KEY_ENV = "GEMINI_API_KEY";
293
+
294
+ /** Options for {@link gemini}. */
295
+ export interface AssistantGeminiOptions extends AssistantAdapterOptions {
296
+ /** The Gemini model id (`gemini-3.5-flash`). */
297
+ model: string;
298
+ /**
299
+ * How much the model reasons before answering. The Google provider maps
300
+ * the level to the model's thinking level, or to a thinking budget on
301
+ * models that take one. Omitted keeps the model's default.
302
+ */
303
+ reasoning?: AssistantReasoning;
304
+ }
305
+
306
+ const geminiOptionsSchema = z.strictObject({
307
+ ...sharedOptions(GEMINI_API_KEY_ENV),
308
+ model: z.string().min(1),
309
+ reasoning: reasoningOption,
310
+ });
311
+
312
+ export type AssistantGeminiAdapter = AdapterDescriptor<
313
+ "gemini",
314
+ AssistantGeminiOptions
315
+ >;
316
+
317
+ export const geminiAdapterSchema = adapterDescriptorSchema(
318
+ "gemini",
319
+ geminiOptionsSchema
320
+ );
321
+
322
+ /**
323
+ * Route the assistant to Google's Gemini API. Reads `GEMINI_API_KEY` and
324
+ * needs `@ai-sdk/google` installed.
325
+ */
326
+ export const gemini = (
327
+ options: AssistantGeminiOptions
328
+ ): AssistantGeminiAdapter => ({
329
+ kind: "gemini",
330
+ options,
331
+ requiredSecrets: [options.apiKeyEnv ?? GEMINI_API_KEY_ENV],
332
+ runtimeDeps: [GEMINI_SDK.pkg],
333
+ });
334
+
335
+ // ---------------------------------------------------------------------------
336
+ // grok()
337
+ // ---------------------------------------------------------------------------
338
+
339
+ const GROK_API_KEY_ENV = "XAI_API_KEY";
340
+
341
+ /** Options for {@link grok}. */
342
+ export interface AssistantGrokOptions extends AssistantAdapterOptions {
343
+ /** The Grok model id (`grok-4.7`). */
344
+ model: string;
345
+ /**
346
+ * How much the model reasons before answering, sent as xAI's reasoning
347
+ * effort on the models that take one. Omitted keeps the model's default.
348
+ */
349
+ reasoning?: AssistantReasoning;
350
+ }
351
+
352
+ const grokOptionsSchema = z.strictObject({
353
+ ...sharedOptions(GROK_API_KEY_ENV),
354
+ model: z.string().min(1),
355
+ reasoning: reasoningOption,
356
+ });
357
+
358
+ export type AssistantGrokAdapter = AdapterDescriptor<
359
+ "grok",
360
+ AssistantGrokOptions
361
+ >;
362
+
363
+ export const grokAdapterSchema = adapterDescriptorSchema(
364
+ "grok",
365
+ grokOptionsSchema
366
+ );
367
+
368
+ /**
369
+ * Route the assistant to xAI's Grok models. Reads `XAI_API_KEY` and needs
370
+ * `@ai-sdk/xai` installed.
371
+ */
372
+ export const grok = (options: AssistantGrokOptions): AssistantGrokAdapter => ({
373
+ kind: "grok",
374
+ options,
375
+ requiredSecrets: [options.apiKeyEnv ?? GROK_API_KEY_ENV],
376
+ runtimeDeps: [GROK_SDK.pkg],
377
+ });
378
+
132
379
  // ---------------------------------------------------------------------------
133
380
  // openrouter()
134
381
  // ---------------------------------------------------------------------------
@@ -272,87 +519,39 @@ export const inkeep = (
272
519
  runtimeDeps: [OPENAI_COMPATIBLE_DEP],
273
520
  });
274
521
 
275
- // ---------------------------------------------------------------------------
276
- // openaiCompatible()
277
- // ---------------------------------------------------------------------------
278
-
279
- /** Options for {@link openaiCompatible}. */
280
- export interface AssistantOpenAICompatibleOptions extends AssistantAdapterOptions {
281
- /** Name of the env var holding the endpoint's API key. */
282
- apiKeyEnv: string;
283
- /** The endpoint's base URL (`https://my-gateway.example.com/v1`). */
284
- baseUrl: string;
285
- /** The model id the endpoint serves. */
286
- model: string;
287
- /** The provider name the AI SDK reports. Defaults to `openai-compatible`. */
288
- name?: string;
289
- /**
290
- * How much the model reasons before answering, sent in the request as
291
- * `reasoning_effort`, so the endpoint has to accept that parameter.
292
- * Omitted keeps the model's default.
293
- */
294
- reasoning?: AssistantReasoning;
295
- }
296
-
297
- const openaiCompatibleOptionsSchema = z.strictObject({
298
- ...sharedOptions("API_KEY"),
299
- // A generic endpoint has no preset, so the caller names the key env var.
300
- apiKeyEnv: z.string().min(1),
301
- baseUrl: z.url(),
302
- model: z.string().min(1),
303
- name: z.string().min(1).default("openai-compatible"),
304
- reasoning: reasoningOption,
305
- });
306
-
307
- export type AssistantOpenAICompatibleAdapter = AdapterDescriptor<
308
- "openai-compatible",
309
- AssistantOpenAICompatibleOptions
310
- >;
311
-
312
- export const openaiCompatibleAdapterSchema = adapterDescriptorSchema(
313
- "openai-compatible",
314
- openaiCompatibleOptionsSchema
315
- );
316
-
317
- /**
318
- * Route the assistant through any OpenAI-compatible endpoint: supply its `baseUrl`,
319
- * the `model` it serves, and the env var holding its key. Needs
320
- * `@ai-sdk/openai-compatible` installed.
321
- */
322
- export const openaiCompatible = (
323
- options: AssistantOpenAICompatibleOptions
324
- ): AssistantOpenAICompatibleAdapter => ({
325
- kind: "openai-compatible",
326
- options,
327
- requiredSecrets: [options.apiKeyEnv],
328
- runtimeDeps: [OPENAI_COMPATIBLE_DEP],
329
- });
330
-
331
522
  // ---------------------------------------------------------------------------
332
523
  // The `ai.assistant.provider` schema
333
524
  // ---------------------------------------------------------------------------
334
525
 
335
- /** Which backend answers the assistant: the value of `gateway()`, `openrouter()`, … */
526
+ /** Which backend answers the assistant: the value of `gateway()`, `openai()`, … */
336
527
  export type AssistantAdapter =
337
528
  | AssistantGatewayAdapter
529
+ | AssistantOpenAIAdapter
530
+ | AssistantAnthropicAdapter
531
+ | AssistantGeminiAdapter
532
+ | AssistantGrokAdapter
338
533
  | AssistantOpenRouterAdapter
339
534
  | AssistantLlmGatewayAdapter
340
- | AssistantInkeepAdapter
341
- | AssistantOpenAICompatibleAdapter;
535
+ | AssistantInkeepAdapter;
342
536
 
343
537
  // ---------------------------------------------------------------------------
344
538
  // Blume 1 hints
345
539
  // ---------------------------------------------------------------------------
346
540
 
347
541
  /**
348
- * The 1.x `ai.ask.provider` names — also the adapters' `kind`s — each with the
349
- * `blume/ai` factory that replaced it.
542
+ * The adapters' `kind`s and the 1.x `ai.ask.provider` names, each with the
543
+ * `blume/ai` factory that takes its place.
350
544
  */
351
545
  const FACTORY_BY_PROVIDER = new Map([
546
+ ["anthropic", "anthropic"],
352
547
  ["gateway", "gateway"],
548
+ ["gemini", "gemini"],
549
+ ["grok", "grok"],
353
550
  ["inkeep", "inkeep"],
354
551
  ["llmgateway", "llmgateway"],
355
- ["openai-compatible", "openaiCompatible"],
552
+ ["openai", "openai"],
553
+ // 1.x only: an OpenAI-compatible endpoint is `openai()` with a `baseUrl`.
554
+ ["openai-compatible", "openai"],
356
555
  ["openrouter", "openrouter"],
357
556
  ]);
358
557
 
@@ -417,7 +616,7 @@ const providerNotAdapterMessage = (issue: z.core.$ZodRawIssue): string => {
417
616
  const factory = name.success ? FACTORY_BY_PROVIDER.get(name.data) : undefined;
418
617
  return factory
419
618
  ? `ai.assistant.provider takes an adapter from "blume/ai", not a provider name: \`provider: ${factory}({ model })\`. The 1.x model, apiKeyEnv, baseUrl, headers, and reasoning fields move into the call.`
420
- : 'ai.assistant.provider takes an adapter from "blume/ai": gateway(), openrouter(), llmgateway(), inkeep(), or openaiCompatible().';
619
+ : 'ai.assistant.provider takes an adapter from "blume/ai": openai(), anthropic(), gemini(), grok(), gateway(), openrouter(), llmgateway(), or inkeep().';
421
620
  };
422
621
 
423
622
  /**
@@ -429,10 +628,13 @@ export const assistantAdapterSchema = z.discriminatedUnion(
429
628
  "kind",
430
629
  [
431
630
  gatewayAdapterSchema,
631
+ openaiAdapterSchema,
632
+ anthropicAdapterSchema,
633
+ geminiAdapterSchema,
634
+ grokAdapterSchema,
432
635
  openrouterAdapterSchema,
433
636
  llmgatewayAdapterSchema,
434
637
  inkeepAdapterSchema,
435
- openaiCompatibleAdapterSchema,
436
638
  ],
437
639
  {
438
640
  // A non-object (a 1.x provider name) fails before any discriminator is
@@ -485,6 +687,12 @@ export interface AskBackendTemplate {
485
687
  export interface AskBackend {
486
688
  /** Whether Blume grounds answers in the docs (Inkeep retrieves itself). */
487
689
  grounded: boolean;
690
+ /**
691
+ * Whether the docs tools are on when `ai.assistant.tools` is unset: yes for
692
+ * the hosted model catalogs, whose models call tools, and no for an
693
+ * OpenAI-compatible backend, which may be a local model that can't.
694
+ */
695
+ toolsByDefault: boolean;
488
696
  kind: AssistantAdapterKind;
489
697
  /** Human-readable adapter name for diagnostics ("AI Gateway"). */
490
698
  label: string;
@@ -562,6 +770,7 @@ const gatewayBackend = (
562
770
  apiKey: ${secretExpr(options.apiKeyEnv)},${headersLine(options.headers)}
563
771
  });\n`,
564
772
  },
773
+ toolsByDefault: true,
565
774
  });
566
775
 
567
776
  const openrouterBackend = (
@@ -589,6 +798,7 @@ const openrouterBackend = (
589
798
  apiKey: ${secretExpr(options.apiKeyEnv)},${headersLine(options.headers)}
590
799
  });\n`,
591
800
  },
801
+ toolsByDefault: true,
592
802
  };
593
803
  };
594
804
 
@@ -601,6 +811,7 @@ const openaiCompatibleBackend = (
601
811
  kind: AssistantAdapterKind,
602
812
  label: string,
603
813
  grounded: boolean,
814
+ toolsByDefault: boolean,
604
815
  options: {
605
816
  apiKeyEnv: string;
606
817
  baseUrl: string;
@@ -630,8 +841,66 @@ const openaiCompatibleBackend = (
630
841
  name: ${JSON.stringify(options.name)},
631
842
  });\n`,
632
843
  },
844
+ toolsByDefault,
845
+ });
846
+
847
+ /**
848
+ * The backend for a provider Blume calls directly, through that provider's
849
+ * own AI SDK package, which maps the top-level `reasoning` call option to the
850
+ * model's control itself. Their models all call tools.
851
+ */
852
+ const directBackend = (
853
+ kind: AssistantAdapterKind,
854
+ label: string,
855
+ sdk: { factory: string; pkg: string },
856
+ options: {
857
+ apiKeyEnv: string;
858
+ headers?: Record<string, string>;
859
+ model: string;
860
+ name?: string;
861
+ providerOptions?: z.output<typeof providerOptionsSchema>;
862
+ reasoning?: AssistantReasoning;
863
+ }
864
+ ): AskBackend => ({
865
+ grounded: true,
866
+ kind,
867
+ label,
868
+ template: {
869
+ fields: callFields(options),
870
+ imports: [
871
+ 'import { createTextStreamResponse, streamText, toTextStream } from "ai";',
872
+ `import { ${sdk.factory} } from ${JSON.stringify(sdk.pkg)};`,
873
+ ],
874
+ keyCheck: keyCheck(options.apiKeyEnv),
875
+ model: `provider(${JSON.stringify(options.model)})`,
876
+ setup: `\nconst provider = ${sdk.factory}({
877
+ apiKey: ${secretExpr(options.apiKeyEnv)},${headersLine(options.headers)}${
878
+ options.name ? `\n name: ${JSON.stringify(options.name)},` : ""
879
+ }
880
+ });\n`,
881
+ },
882
+ toolsByDefault: true,
633
883
  });
634
884
 
885
+ /**
886
+ * `openai()`: OpenAI itself through its own SDK, or, with a `baseUrl`, a
887
+ * generic endpoint through the OpenAI-compatible provider — Chat Completions,
888
+ * which most such endpoints serve where the Responses API isn't — with the
889
+ * tools off by default, since a self-hosted model may not call them.
890
+ */
891
+ const openaiBackend = (
892
+ options: z.output<typeof openaiOptionsSchema>
893
+ ): AskBackend => {
894
+ const { baseUrl } = options;
895
+ return baseUrl
896
+ ? openaiCompatibleBackend("openai", "OpenAI-compatible", true, false, {
897
+ ...options,
898
+ baseUrl,
899
+ name: options.name ?? "openai-compatible",
900
+ })
901
+ : directBackend("openai", "OpenAI", OPENAI_SDK, options);
902
+ };
903
+
635
904
  /** Resolve a parsed `ai.assistant.provider` descriptor into its backend. */
636
905
  export const resolveAskBackend = (
637
906
  provider: AssistantAdapterConfig = assistantAdapterSchema.parse(
@@ -642,11 +911,25 @@ export const resolveAskBackend = (
642
911
  case "gateway": {
643
912
  return gatewayBackend(provider.options);
644
913
  }
914
+ case "anthropic": {
915
+ return directBackend(
916
+ "anthropic",
917
+ "Anthropic",
918
+ ANTHROPIC_SDK,
919
+ provider.options
920
+ );
921
+ }
922
+ case "gemini": {
923
+ return directBackend("gemini", "Gemini", GEMINI_SDK, provider.options);
924
+ }
925
+ case "grok": {
926
+ return directBackend("grok", "Grok", GROK_SDK, provider.options);
927
+ }
645
928
  case "openrouter": {
646
929
  return openrouterBackend(provider.options);
647
930
  }
648
931
  case "llmgateway": {
649
- return openaiCompatibleBackend("llmgateway", "LLMGateway", true, {
932
+ return openaiCompatibleBackend("llmgateway", "LLMGateway", true, true, {
650
933
  ...provider.options,
651
934
  name: "llmgateway",
652
935
  });
@@ -654,18 +937,13 @@ export const resolveAskBackend = (
654
937
  case "inkeep": {
655
938
  // Inkeep answers from the content indexed in its dashboard, so injected
656
939
  // excerpts would conflict with its own retrieval.
657
- return openaiCompatibleBackend("inkeep", "Inkeep", false, {
940
+ return openaiCompatibleBackend("inkeep", "Inkeep", false, false, {
658
941
  ...provider.options,
659
942
  name: "inkeep",
660
943
  });
661
944
  }
662
945
  default: {
663
- return openaiCompatibleBackend(
664
- "openai-compatible",
665
- "OpenAI-compatible",
666
- true,
667
- provider.options
668
- );
946
+ return openaiBackend(provider.options);
669
947
  }
670
948
  }
671
949
  };
@@ -1,6 +1,7 @@
1
1
  import { markdownTable } from "markdown-table";
2
2
  import { mdxToMdast } from "satteri";
3
3
 
4
+ import { PARAM_LOCATIONS } from "../components/content/api-field.ts";
4
5
  import { parseYouTubeId } from "../components/content/youtube.ts";
5
6
  import type { ExampleLookup } from "../core/types.ts";
6
7
  import { MDX_FEATURES } from "../markdown/features.ts";
@@ -398,6 +399,18 @@ const tabs: ComponentMarkdown = ({ childComponents, children }) => {
398
399
  .join("\n\n");
399
400
  };
400
401
 
402
+ /**
403
+ * A `<View>` block: its content under its view's name, so an agent reading
404
+ * the Markdown sees every view and which one each part belongs to.
405
+ */
406
+ const view: ComponentMarkdown = ({ children, props }) => {
407
+ const title = isString(props.title) && props.title !== "" ? props.title : "";
408
+ if (!title) {
409
+ return children;
410
+ }
411
+ return children ? `**${title}**\n\n${children}` : `**${title}**`;
412
+ };
413
+
401
414
  /** Escape the brackets that would end a link's text early. */
402
415
  const linkText = (value: string): string =>
403
416
  value.replaceAll(/[[\]]/gu, String.raw`\$&`);
@@ -420,6 +433,63 @@ const textProp = (value: EvaluatedValue): string => {
420
433
  return isNumber(value) ? String(value) : "";
421
434
  };
422
435
 
436
+ /** Whether a flag prop is on: shorthand `true`, or the string `"true"`. */
437
+ const flagOn = (value: EvaluatedValue): boolean =>
438
+ value === true || value === "true";
439
+
440
+ /** A prop that holds one label or several. */
441
+ const labelList = (value: EvaluatedValue): string[] =>
442
+ (Array.isArray(value) ? value : [value]).map(textProp).filter(Boolean);
443
+
444
+ /**
445
+ * One `<ParamField>` or `<ResponseField>` as a list item: its labels, name,
446
+ * location, type, flags, and default on the first line, joined with middle
447
+ * dots, and its description indented under it, nested fields included. A
448
+ * field with no name, or a prop that wouldn't evaluate, declines.
449
+ */
450
+ const apiField = (
451
+ { children, lossy, props }: ComponentMarkdownContext,
452
+ name: string,
453
+ location?: string
454
+ ): string | null => {
455
+ if (lossy || name === "") {
456
+ return null;
457
+ }
458
+ const value = props.default;
459
+ const head = [
460
+ ...labelList(props.pre),
461
+ `**${cellCode(name)}**`,
462
+ ...labelList(props.post),
463
+ location,
464
+ textProp(props.type) && cellCode(props.type),
465
+ flagOn(props.required) ? "required" : "",
466
+ flagOn(props.deprecated) ? "deprecated" : "",
467
+ value === undefined || value === null || value === ""
468
+ ? ""
469
+ : `default ${cellCode(isString(value) ? value : JSON.stringify(value))}`,
470
+ ].filter(Boolean);
471
+ const item = `- ${head.join(" · ")}`;
472
+ if (!children) {
473
+ return item;
474
+ }
475
+ const body = children
476
+ .split("\n")
477
+ .map((line) => (line === "" ? "" : ` ${line}`))
478
+ .join("\n");
479
+ return `${item}\n\n${body}`;
480
+ };
481
+
482
+ /** `<ParamField query="limit">`: named by its location attribute, else `name`. */
483
+ const paramFieldMarkdown: ComponentMarkdown = (context) => {
484
+ const location = PARAM_LOCATIONS.find((key) => isString(context.props[key]));
485
+ return location
486
+ ? apiField(context, textProp(context.props[location]), location)
487
+ : apiField(context, textProp(context.props.name));
488
+ };
489
+
490
+ const responseFieldMarkdown: ComponentMarkdown = (context) =>
491
+ apiField(context, textProp(context.props.name));
492
+
423
493
  /**
424
494
  * A card is a link with a blurb, so that is what it becomes: the title as the
425
495
  * link text, the body under it, and the call to action last — the order the
@@ -822,7 +892,11 @@ const SERIALIZERS = {
822
892
  Icon: icon,
823
893
  Math: math,
824
894
  Panel: panel,
895
+ ParamField: paramFieldMarkdown,
825
896
  Prompt: prompt,
897
+ RequestExample: cardGroup,
898
+ ResponseExample: cardGroup,
899
+ ResponseField: responseFieldMarkdown,
826
900
  Steps: steps,
827
901
  Tabs: tabs,
828
902
  Tile: tile,
@@ -832,6 +906,7 @@ const SERIALIZERS = {
832
906
  "Tree.Folder": treeFolder,
833
907
  TypeTable: typeTable,
834
908
  Update: update,
909
+ View: view,
835
910
  YouTube: youtube,
836
911
  } satisfies Record<string, ComponentMarkdown>;
837
912
 
package/src/ai/cors.ts CHANGED
@@ -61,6 +61,8 @@ export const preflightResponse = (
61
61
 
62
62
  /** The slice of Astro's `APIContext` the wrapped handler reads. */
63
63
  interface RequestContext {
64
+ /** The reader's address, which the route's rate limit keys on. */
65
+ readonly clientAddress?: string;
64
66
  request: Request;
65
67
  }
66
68