@ecsplico/editor 3.6.0 → 4.0.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 (578) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/dist/adapters/direct-ai.d.ts +4 -4
  3. package/dist/adapters/direct-ai.js +2 -2
  4. package/dist/adapters/direct-ai.js.map +1 -1
  5. package/dist/adapters/document-state.d.ts +1 -1
  6. package/dist/adapters/document-state.js.map +1 -1
  7. package/dist/adapters/forge.d.ts +2 -2
  8. package/dist/adapters/forge.js.map +1 -1
  9. package/dist/adapters/nitro.d.ts +11 -3
  10. package/dist/adapters/nitro.js +3 -1
  11. package/dist/adapters/nitro.js.map +1 -1
  12. package/dist/adapters/shared.d.ts +6 -6
  13. package/dist/adapters/shared.js.map +1 -1
  14. package/dist/config/index.d.ts +14 -14
  15. package/dist/config/index.js +8 -8
  16. package/dist/config/index.js.map +1 -1
  17. package/dist/core/backend-types.d.ts +3 -3
  18. package/dist/core/backend-types.js.map +1 -1
  19. package/dist/core/backend.d.ts +4 -4
  20. package/dist/core/backend.js.map +1 -1
  21. package/dist/core/block-convert.d.ts +1 -1
  22. package/dist/core/block-convert.js +5 -5
  23. package/dist/core/block-convert.js.map +1 -1
  24. package/dist/core/block-transfer.d.ts +8 -4
  25. package/dist/core/block-transfer.js +23 -14
  26. package/dist/core/block-transfer.js.map +1 -1
  27. package/dist/core/capabilities.d.ts +113 -0
  28. package/dist/core/capabilities.js +155 -0
  29. package/dist/core/capabilities.js.map +1 -0
  30. package/dist/core/command-types.d.ts +1 -1
  31. package/dist/core/context.js.map +1 -1
  32. package/dist/core/convert.d.ts +5 -5
  33. package/dist/core/convert.js +5 -5
  34. package/dist/core/convert.js.map +1 -1
  35. package/dist/core/core-feature.js +1 -1
  36. package/dist/core/define-editor.d.ts +2 -2
  37. package/dist/core/define-editor.js +34 -6
  38. package/dist/core/define-editor.js.map +1 -1
  39. package/dist/core/derivation.d.ts +119 -0
  40. package/dist/core/derivation.js +187 -0
  41. package/dist/core/derivation.js.map +1 -0
  42. package/dist/core/derive.d.ts +61 -0
  43. package/dist/core/derive.js +69 -0
  44. package/dist/core/derive.js.map +1 -0
  45. package/dist/core/extension-points.d.ts +16 -0
  46. package/dist/core/feature-context.d.ts +5 -1
  47. package/dist/core/feature-context.js +10 -3
  48. package/dist/core/feature-context.js.map +1 -1
  49. package/dist/core/feature.d.ts +41 -19
  50. package/dist/core/feature.js.map +1 -1
  51. package/dist/core/fences.js +1 -1
  52. package/dist/core/fences.js.map +1 -1
  53. package/dist/core/index.d.ts +6 -2
  54. package/dist/core/index.js +5 -1
  55. package/dist/core/pipeline.d.ts +14 -5
  56. package/dist/core/pipeline.js +19 -7
  57. package/dist/core/pipeline.js.map +1 -1
  58. package/dist/core/properties.d.ts +22 -0
  59. package/dist/core/properties.js +17 -0
  60. package/dist/core/properties.js.map +1 -0
  61. package/dist/core/schema/block-ids.js +5 -5
  62. package/dist/core/schema/block-ids.js.map +1 -1
  63. package/dist/core/schema/index.d.ts +2 -1
  64. package/dist/core/schema/index.js +2 -1
  65. package/dist/core/schema/index.js.map +1 -1
  66. package/dist/core/schema/opaque-fence.d.ts +4 -4
  67. package/dist/core/schema/opaque-fence.js +7 -7
  68. package/dist/core/schema/opaque-fence.js.map +1 -1
  69. package/dist/core/schema/persisted-names.d.ts +58 -0
  70. package/dist/core/schema/persisted-names.js +96 -0
  71. package/dist/core/schema/persisted-names.js.map +1 -0
  72. package/dist/core/toolbar-items.js +1 -1
  73. package/dist/core/toolbar-items.js.map +1 -1
  74. package/dist/core/util.js +5 -1
  75. package/dist/core/util.js.map +1 -1
  76. package/dist/features/ai/agent-ops.js +1 -1
  77. package/dist/features/ai/ai-review.js +1 -1
  78. package/dist/features/ai/assistant.d.ts +4 -4
  79. package/dist/features/ai/assistant.js +1 -1
  80. package/dist/features/ai/assistant.js.map +1 -1
  81. package/dist/features/ai/components/AiSelectionMenu.js +1 -1
  82. package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
  83. package/dist/features/ai/components/ModelQuickSwitch.js +7 -7
  84. package/dist/features/ai/components/ModelQuickSwitch.js.map +1 -1
  85. package/dist/features/ai/components/ModelQuickSwitch.vue.d.ts +4 -4
  86. package/dist/features/ai/components/SettingsAiTab.js +8 -8
  87. package/dist/features/ai/components/SettingsAiTab.js.map +1 -1
  88. package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
  89. package/dist/features/ai/components/SettingsModelProfiles.js.map +1 -1
  90. package/dist/features/ai/document-chat-model.d.ts +2 -2
  91. package/dist/features/ai/document-chat-model.js +5 -5
  92. package/dist/features/ai/document-chat-model.js.map +1 -1
  93. package/dist/features/ai/document-chat.js +2 -2
  94. package/dist/features/ai/document-chat.js.map +1 -1
  95. package/dist/features/ai/feature.d.ts +2 -2
  96. package/dist/features/ai/feature.js +2 -2
  97. package/dist/features/ai/feature.js.map +1 -1
  98. package/dist/features/ai/ghost-autocomplete.d.ts +4 -4
  99. package/dist/features/ai/ghost-autocomplete.js +1 -1
  100. package/dist/features/ai/ghost-autocomplete.js.map +1 -1
  101. package/dist/features/ai/index.d.ts +3 -3
  102. package/dist/features/ai/index.js +2 -2
  103. package/dist/features/ai/model-profiles.d.ts +9 -9
  104. package/dist/features/ai/model-profiles.js +13 -13
  105. package/dist/features/ai/model-profiles.js.map +1 -1
  106. package/dist/features/ai/protocol/agent-ops.d.ts +2 -2
  107. package/dist/features/ai/protocol/agent-ops.js +1 -1
  108. package/dist/features/ai/protocol/agent-ops.js.map +1 -1
  109. package/dist/features/ai/protocol/block-lines.d.ts +7 -7
  110. package/dist/features/ai/protocol/block-lines.js +10 -10
  111. package/dist/features/ai/protocol/block-lines.js.map +1 -1
  112. package/dist/features/ai/protocol/chat.d.ts +5 -5
  113. package/dist/features/ai/protocol/chat.js +12 -12
  114. package/dist/features/ai/protocol/chat.js.map +1 -1
  115. package/dist/features/ai/protocol/generate.d.ts +1 -1
  116. package/dist/features/ai/protocol/generate.js +1 -1
  117. package/dist/features/ai/protocol/generate.js.map +1 -1
  118. package/dist/features/ai/protocol/index.d.ts +3 -3
  119. package/dist/features/ai/protocol/index.js +2 -2
  120. package/dist/features/ai/protocol/models.d.ts +3 -3
  121. package/dist/features/ai/protocol/models.js +3 -3
  122. package/dist/features/ai/protocol/models.js.map +1 -1
  123. package/dist/features/ai/protocol/schemas.d.ts +1 -1
  124. package/dist/features/ai/protocol/schemas.js +1 -1
  125. package/dist/features/ai/protocol/schemas.js.map +1 -1
  126. package/dist/features/ai/protocol/tasks.d.ts +5 -5
  127. package/dist/features/ai/protocol/tasks.js +12 -12
  128. package/dist/features/ai/protocol/tasks.js.map +1 -1
  129. package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
  130. package/dist/features/ai/react/ModelQuickSwitch.d.ts +4 -4
  131. package/dist/features/ai/react/ModelQuickSwitch.js +6 -6
  132. package/dist/features/ai/react/ModelQuickSwitch.js.map +1 -1
  133. package/dist/features/ai/react/SettingsAiTab.d.ts +1 -1
  134. package/dist/features/ai/react/SettingsAiTab.js +8 -8
  135. package/dist/features/ai/react/SettingsAiTab.js.map +1 -1
  136. package/dist/features/ai/react/SettingsModelProfiles.d.ts +1 -1
  137. package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
  138. package/dist/features/ai/react/SettingsModelProfiles.js.map +1 -1
  139. package/dist/features/ai/react/use-ai-assistant.d.ts +1 -1
  140. package/dist/features/ai/react/use-ai-assistant.js +1 -1
  141. package/dist/features/ai/react/use-ai-assistant.js.map +1 -1
  142. package/dist/features/ai/react/use-model-profiles.d.ts +5 -5
  143. package/dist/features/ai/react/use-model-profiles.js +2 -2
  144. package/dist/features/ai/react/use-model-profiles.js.map +1 -1
  145. package/dist/features/ai/use-ai-assistant.d.ts +1 -1
  146. package/dist/features/ai/use-ai-assistant.js +1 -1
  147. package/dist/features/ai/use-ai-assistant.js.map +1 -1
  148. package/dist/features/ai/use-model-profiles.d.ts +4 -4
  149. package/dist/features/ai/use-model-profiles.js +2 -2
  150. package/dist/features/ai/use-model-profiles.js.map +1 -1
  151. package/dist/features/block-ui/block-actions.d.ts +8 -4
  152. package/dist/features/block-ui/block-actions.js +38 -22
  153. package/dist/features/block-ui/block-actions.js.map +1 -1
  154. package/dist/features/block-ui/block-target.d.ts +29 -0
  155. package/dist/features/block-ui/block-target.js +63 -0
  156. package/dist/features/block-ui/block-target.js.map +1 -0
  157. package/dist/features/block-ui/components/BlockHandles.js +3 -3
  158. package/dist/features/block-ui/components/BlockHandles.js.map +1 -1
  159. package/dist/features/block-ui/components/BlockUi.js +11 -23
  160. package/dist/features/block-ui/components/BlockUi.js.map +1 -1
  161. package/dist/features/block-ui/feature.d.ts +3 -2
  162. package/dist/features/block-ui/feature.js +3 -2
  163. package/dist/features/block-ui/feature.js.map +1 -1
  164. package/dist/features/block-ui/handle-geometry.js +12 -23
  165. package/dist/features/block-ui/handle-geometry.js.map +1 -1
  166. package/dist/features/block-ui/index.d.ts +2 -1
  167. package/dist/features/block-ui/index.js +2 -1
  168. package/dist/features/block-ui/react/BlockHandles.d.ts +1 -1
  169. package/dist/features/block-ui/react/BlockHandles.js +4 -4
  170. package/dist/features/block-ui/react/BlockHandles.js.map +1 -1
  171. package/dist/features/block-ui/react/BlockUi.js +13 -25
  172. package/dist/features/block-ui/react/BlockUi.js.map +1 -1
  173. package/dist/features/comments/model.js +1 -1
  174. package/dist/features/composites/accepted-children.d.ts +5 -4
  175. package/dist/features/composites/accepted-children.js +26 -26
  176. package/dist/features/composites/accepted-children.js.map +1 -1
  177. package/dist/features/composites/ai-brief.d.ts +4 -4
  178. package/dist/features/composites/ai-brief.js +14 -12
  179. package/dist/features/composites/ai-brief.js.map +1 -1
  180. package/dist/features/composites/anchors.d.ts +1 -1
  181. package/dist/features/composites/anchors.js.map +1 -1
  182. package/dist/features/composites/baskets.d.ts +1 -1
  183. package/dist/features/composites/baskets.js.map +1 -1
  184. package/dist/features/composites/bibliography-command.d.ts +1 -1
  185. package/dist/features/composites/bibliography-form.js +1 -1
  186. package/dist/features/composites/bibliography-form.js.map +1 -1
  187. package/dist/features/composites/bibliography.js.map +1 -1
  188. package/dist/features/composites/block-types-json.d.ts +38 -0
  189. package/dist/features/composites/block-types-json.js +122 -0
  190. package/dist/features/composites/block-types-json.js.map +1 -0
  191. package/dist/features/composites/block-types.d.ts +119 -0
  192. package/dist/features/composites/block-types.js +343 -0
  193. package/dist/features/composites/block-types.js.map +1 -0
  194. package/dist/features/composites/capabilities.js +109 -0
  195. package/dist/features/composites/capabilities.js.map +1 -0
  196. package/dist/features/composites/components/BibliographyEditor.js +5 -5
  197. package/dist/features/composites/components/BibliographyEditor.js.map +1 -1
  198. package/dist/features/composites/components/CompositeBasketMenu.js +2 -2
  199. package/dist/features/composites/components/CompositeBasketMenu.js.map +1 -1
  200. package/dist/features/composites/components/CompositeChrome.js +18 -8
  201. package/dist/features/composites/components/CompositeChrome.js.map +1 -1
  202. package/dist/features/composites/components/CompositeChrome.vue.d.ts +1 -1
  203. package/dist/features/composites/components/CompositeDetails.js +11 -10
  204. package/dist/features/composites/components/CompositeDetails.js.map +1 -1
  205. package/dist/features/composites/components/CompositeDetails.vue.d.ts +1 -1
  206. package/dist/features/composites/components/CompositeFieldEditor.js +7 -7
  207. package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
  208. package/dist/features/composites/components/CompositeFieldEditor.vue.d.ts +5 -5
  209. package/dist/features/composites/components/CompositeInspector.js +136 -113
  210. package/dist/features/composites/components/CompositeInspector.js.map +1 -1
  211. package/dist/features/composites/components/CompositeInspector.vue.d.ts +3 -3
  212. package/dist/features/composites/components/CompositeStatusMenu.js +2 -2
  213. package/dist/features/composites/components/CompositeStatusMenu.js.map +1 -1
  214. package/dist/features/composites/components/CompositeSummaryEditor.js +1 -1
  215. package/dist/features/composites/components/CompositeSummaryEditor.js.map +1 -1
  216. package/dist/features/composites/components/CompositeSummaryEditor.vue.d.ts +1 -1
  217. package/dist/features/composites/components/CompositeTable.js +2 -2
  218. package/dist/features/composites/components/CompositeTable.js.map +1 -1
  219. package/dist/features/composites/components/CompositeTagsEditor.js +1 -1
  220. package/dist/features/composites/components/CompositeTagsEditor.js.map +1 -1
  221. package/dist/features/composites/components/CompositeTagsEditor.vue.d.ts +1 -1
  222. package/dist/features/composites/components/CompositeView.js +19 -17
  223. package/dist/features/composites/components/CompositeView.js.map +1 -1
  224. package/dist/features/composites/components/DerivationBadge.js +15 -6
  225. package/dist/features/composites/components/DerivationBadge.js.map +1 -1
  226. package/dist/features/composites/components/FocusChip.js +1 -1
  227. package/dist/features/composites/components/FocusChip.js.map +1 -1
  228. package/dist/features/composites/components/FocusChip.vue.d.ts +1 -1
  229. package/dist/features/composites/components/ReferenceFieldSelect.js +3 -3
  230. package/dist/features/composites/components/ReferenceFieldSelect.js.map +1 -1
  231. package/dist/features/composites/components/ReferenceView.js +1 -1
  232. package/dist/features/composites/components/ReferenceView.js.map +1 -1
  233. package/dist/features/composites/components/SettingsEditingTab.js +18 -18
  234. package/dist/features/composites/components/SettingsEditingTab.js.map +1 -1
  235. package/dist/features/composites/components/SettingsEditingTab.vue.d.ts +3 -3
  236. package/dist/features/composites/convert.d.ts +4 -4
  237. package/dist/features/composites/convert.js +27 -28
  238. package/dist/features/composites/convert.js.map +1 -1
  239. package/dist/features/composites/derived-chrome.js +34 -28
  240. package/dist/features/composites/derived-chrome.js.map +1 -1
  241. package/dist/features/composites/editing-settings-keys.js +6 -6
  242. package/dist/features/composites/editing-settings-keys.js.map +1 -1
  243. package/dist/features/composites/feature.d.ts +10 -10
  244. package/dist/features/composites/feature.js +58 -46
  245. package/dist/features/composites/feature.js.map +1 -1
  246. package/dist/features/composites/fences.d.ts +3 -3
  247. package/dist/features/composites/fences.js +18 -13
  248. package/dist/features/composites/fences.js.map +1 -1
  249. package/dist/features/composites/focus.d.ts +10 -10
  250. package/dist/features/composites/focus.js +11 -11
  251. package/dist/features/composites/focus.js.map +1 -1
  252. package/dist/features/composites/heading-composites.d.ts +5 -5
  253. package/dist/features/composites/heading-composites.js +13 -16
  254. package/dist/features/composites/heading-composites.js.map +1 -1
  255. package/dist/features/composites/index.d.ts +20 -18
  256. package/dist/features/composites/index.js +9 -7
  257. package/dist/features/composites/inspector-model.d.ts +45 -22
  258. package/dist/features/composites/inspector-model.js +53 -14
  259. package/dist/features/composites/inspector-model.js.map +1 -1
  260. package/dist/features/composites/legacy.d.ts +5 -4
  261. package/dist/features/composites/legacy.js +16 -23
  262. package/dist/features/composites/legacy.js.map +1 -1
  263. package/dist/features/composites/markdown-export.js +3 -3
  264. package/dist/features/composites/markdown-export.js.map +1 -1
  265. package/dist/features/composites/merge.d.ts +8 -8
  266. package/dist/features/composites/merge.js +23 -22
  267. package/dist/features/composites/merge.js.map +1 -1
  268. package/dist/features/composites/meta-display.js +5 -5
  269. package/dist/features/composites/meta-display.js.map +1 -1
  270. package/dist/features/composites/meta-keys.d.ts +5 -5
  271. package/dist/features/composites/meta-keys.js +8 -8
  272. package/dist/features/composites/meta-keys.js.map +1 -1
  273. package/dist/features/composites/node-views.d.ts +3 -3
  274. package/dist/features/composites/node-views.js +2 -2
  275. package/dist/features/composites/node-views.js.map +1 -1
  276. package/dist/features/composites/nodes.d.ts +13 -13
  277. package/dist/features/composites/nodes.js +100 -77
  278. package/dist/features/composites/nodes.js.map +1 -1
  279. package/dist/features/composites/outline.d.ts +15 -12
  280. package/dist/features/composites/outline.js +24 -21
  281. package/dist/features/composites/outline.js.map +1 -1
  282. package/dist/features/composites/properties.d.ts +51 -0
  283. package/dist/features/composites/properties.js +89 -0
  284. package/dist/features/composites/properties.js.map +1 -0
  285. package/dist/features/composites/react/BibliographyEditor.js +5 -5
  286. package/dist/features/composites/react/BibliographyEditor.js.map +1 -1
  287. package/dist/features/composites/react/CompositeBasketMenu.js +2 -2
  288. package/dist/features/composites/react/CompositeBasketMenu.js.map +1 -1
  289. package/dist/features/composites/react/CompositeChrome.d.ts +1 -1
  290. package/dist/features/composites/react/CompositeChrome.js +15 -6
  291. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  292. package/dist/features/composites/react/CompositeDetails.d.ts +1 -1
  293. package/dist/features/composites/react/CompositeDetails.js +11 -10
  294. package/dist/features/composites/react/CompositeDetails.js.map +1 -1
  295. package/dist/features/composites/react/CompositeFieldEditor.d.ts +5 -5
  296. package/dist/features/composites/react/CompositeFieldEditor.js +6 -6
  297. package/dist/features/composites/react/CompositeFieldEditor.js.map +1 -1
  298. package/dist/features/composites/react/CompositeInspector.d.ts +3 -3
  299. package/dist/features/composites/react/CompositeInspector.js +142 -128
  300. package/dist/features/composites/react/CompositeInspector.js.map +1 -1
  301. package/dist/features/composites/react/CompositeStatusMenu.js +2 -2
  302. package/dist/features/composites/react/CompositeStatusMenu.js.map +1 -1
  303. package/dist/features/composites/react/CompositeSummaryEditor.d.ts +1 -1
  304. package/dist/features/composites/react/CompositeSummaryEditor.js +1 -1
  305. package/dist/features/composites/react/CompositeSummaryEditor.js.map +1 -1
  306. package/dist/features/composites/react/CompositeTable.d.ts +1 -1
  307. package/dist/features/composites/react/CompositeTable.js +3 -3
  308. package/dist/features/composites/react/CompositeTable.js.map +1 -1
  309. package/dist/features/composites/react/CompositeTagsEditor.d.ts +1 -1
  310. package/dist/features/composites/react/CompositeTagsEditor.js +1 -1
  311. package/dist/features/composites/react/CompositeTagsEditor.js.map +1 -1
  312. package/dist/features/composites/react/CompositeView.js +17 -15
  313. package/dist/features/composites/react/CompositeView.js.map +1 -1
  314. package/dist/features/composites/react/DerivationBadge.js +13 -4
  315. package/dist/features/composites/react/DerivationBadge.js.map +1 -1
  316. package/dist/features/composites/react/FocusChip.d.ts +1 -1
  317. package/dist/features/composites/react/FocusChip.js +1 -1
  318. package/dist/features/composites/react/FocusChip.js.map +1 -1
  319. package/dist/features/composites/react/ReferenceFieldSelect.js +3 -3
  320. package/dist/features/composites/react/ReferenceFieldSelect.js.map +1 -1
  321. package/dist/features/composites/react/ReferenceView.d.ts +1 -1
  322. package/dist/features/composites/react/ReferenceView.js +1 -1
  323. package/dist/features/composites/react/ReferenceView.js.map +1 -1
  324. package/dist/features/composites/react/SettingsEditingTab.d.ts +4 -4
  325. package/dist/features/composites/react/SettingsEditingTab.js +20 -20
  326. package/dist/features/composites/react/SettingsEditingTab.js.map +1 -1
  327. package/dist/features/composites/react.js +1 -1
  328. package/dist/features/composites/react.js.map +1 -1
  329. package/dist/features/composites/reference-ai.js +1 -1
  330. package/dist/features/composites/reference-ai.js.map +1 -1
  331. package/dist/features/composites/reference-label.d.ts +3 -3
  332. package/dist/features/composites/reference-label.js +6 -6
  333. package/dist/features/composites/reference-label.js.map +1 -1
  334. package/dist/features/composites/reference-suggestion.d.ts +1 -1
  335. package/dist/features/composites/reference-suggestion.js +3 -3
  336. package/dist/features/composites/reference-suggestion.js.map +1 -1
  337. package/dist/features/composites/reference-text.d.ts +2 -2
  338. package/dist/features/composites/reference-text.js +4 -4
  339. package/dist/features/composites/reference-text.js.map +1 -1
  340. package/dist/features/composites/reference.d.ts +3 -3
  341. package/dist/features/composites/reference.js +1 -1
  342. package/dist/features/composites/reference.js.map +1 -1
  343. package/dist/features/composites/reveal-source.d.ts +3 -3
  344. package/dist/features/composites/reveal-source.js +6 -6
  345. package/dist/features/composites/reveal-source.js.map +1 -1
  346. package/dist/features/composites/rows-model.d.ts +2 -2
  347. package/dist/features/composites/rows-model.js +4 -3
  348. package/dist/features/composites/rows-model.js.map +1 -1
  349. package/dist/features/composites/rows.d.ts +16 -28
  350. package/dist/features/composites/rows.js +20 -54
  351. package/dist/features/composites/rows.js.map +1 -1
  352. package/dist/features/composites/schema.d.ts +12 -11
  353. package/dist/features/composites/schema.js +10 -9
  354. package/dist/features/composites/schema.js.map +1 -1
  355. package/dist/features/composites/source.d.ts +20 -0
  356. package/dist/features/composites/source.js +40 -0
  357. package/dist/features/composites/source.js.map +1 -0
  358. package/dist/features/composites/split.d.ts +11 -11
  359. package/dist/features/composites/split.js +20 -20
  360. package/dist/features/composites/split.js.map +1 -1
  361. package/dist/features/composites/structure-mode.js +3 -3
  362. package/dist/features/composites/structure-mode.js.map +1 -1
  363. package/dist/features/composites/table-model.js +2 -2
  364. package/dist/features/composites/table-model.js.map +1 -1
  365. package/dist/features/composites/use-composite-inspector.js +3 -1
  366. package/dist/features/composites/use-composite-inspector.js.map +1 -1
  367. package/dist/features/composites/use-composite-rows.d.ts +2 -2
  368. package/dist/features/composites/use-composite-rows.js.map +1 -1
  369. package/dist/features/composites/vue.js +1 -1
  370. package/dist/features/composites/vue.js.map +1 -1
  371. package/dist/features/image-studio/components/ImageStudioDialog.js +1 -1
  372. package/dist/features/image-studio/components/ImageStudioDialog.js.map +1 -1
  373. package/dist/features/image-studio/components/ImageStudioDialog.vue.d.ts +1 -1
  374. package/dist/features/image-studio/components/ImageStudioView.js.map +1 -1
  375. package/dist/features/image-studio/feature.d.ts +1 -1
  376. package/dist/features/image-studio/feature.js +1 -1
  377. package/dist/features/image-studio/feature.js.map +1 -1
  378. package/dist/features/image-studio/node-core.d.ts +3 -3
  379. package/dist/features/image-studio/node-core.js +1 -1
  380. package/dist/features/image-studio/node-core.js.map +1 -1
  381. package/dist/features/image-studio/node-store.d.ts +1 -1
  382. package/dist/features/image-studio/node-store.js +1 -1
  383. package/dist/features/image-studio/node-store.js.map +1 -1
  384. package/dist/features/image-studio/react/ImageStudioDialog.d.ts +1 -1
  385. package/dist/features/image-studio/react/ImageStudioDialog.js +1 -1
  386. package/dist/features/image-studio/react/ImageStudioDialog.js.map +1 -1
  387. package/dist/features/image-studio/react/ImageStudioView.js +1 -1
  388. package/dist/features/image-studio/react/ImageStudioView.js.map +1 -1
  389. package/dist/features/image-studio/react/store.d.ts +3 -3
  390. package/dist/features/image-studio/react/store.js.map +1 -1
  391. package/dist/features/image-studio/react.d.ts +1 -1
  392. package/dist/features/image-studio/react.js +1 -1
  393. package/dist/features/image-studio/react.js.map +1 -1
  394. package/dist/features/image-studio/schema.d.ts +1 -1
  395. package/dist/features/image-studio/schema.js +1 -1
  396. package/dist/features/image-studio/schema.js.map +1 -1
  397. package/dist/features/image-studio/state.d.ts +3 -3
  398. package/dist/features/image-studio/state.js +2 -2
  399. package/dist/features/image-studio/state.js.map +1 -1
  400. package/dist/features/image-studio/store.d.ts +3 -3
  401. package/dist/features/image-studio/vue.d.ts +1 -1
  402. package/dist/features/image-studio/vue.js +1 -1
  403. package/dist/features/image-studio/vue.js.map +1 -1
  404. package/dist/features/media/index.js +1 -1
  405. package/dist/features/media/react/AudioRecorderModal.js +1 -1
  406. package/dist/features/media/react.js +1 -1
  407. package/dist/features/mentions/tag.js +2 -1
  408. package/dist/features/mentions/tag.js.map +1 -1
  409. package/dist/features/themes/components/SettingsAppearanceTab.js +1 -1
  410. package/dist/features/themes/components/SettingsAppearanceTab.js.map +1 -1
  411. package/dist/features/themes/feature.d.ts +1 -1
  412. package/dist/features/themes/feature.js.map +1 -1
  413. package/dist/features/themes/react/SettingsAppearanceTab.d.ts +1 -1
  414. package/dist/features/themes/react/SettingsAppearanceTab.js +1 -1
  415. package/dist/features/themes/react/SettingsAppearanceTab.js.map +1 -1
  416. package/dist/features/themes/theme-runtime.js +1 -1
  417. package/dist/features/themes/theme-runtime.js.map +1 -1
  418. package/dist/features/transcript/TranscriptSegmentView.js +1 -1
  419. package/dist/features/transcript/TranscriptSegmentView.js.map +1 -1
  420. package/dist/features/transcript/react/TranscriptSegmentView.js +1 -1
  421. package/dist/features/transcript/react/TranscriptSegmentView.js.map +1 -1
  422. package/dist/features/transcript/segment-node.js.map +1 -1
  423. package/dist/features/transfer/feature.js +1 -1
  424. package/dist/features/transfer/feature.js.map +1 -1
  425. package/dist/features/transfer/transforms.d.ts +4 -4
  426. package/dist/features/transfer/transforms.js +12 -12
  427. package/dist/features/transfer/transforms.js.map +1 -1
  428. package/dist/i18n/de/ai.js +4 -4
  429. package/dist/i18n/de/ai.js.map +1 -1
  430. package/dist/i18n/de/authors.js +1 -1
  431. package/dist/i18n/de/authors.js.map +1 -1
  432. package/dist/i18n/de/comments.js +1 -1
  433. package/dist/i18n/de/comments.js.map +1 -1
  434. package/dist/i18n/de/composites.js +62 -59
  435. package/dist/i18n/de/composites.js.map +1 -1
  436. package/dist/i18n/de/image-studio.js +2 -2
  437. package/dist/i18n/de/image-studio.js.map +1 -1
  438. package/dist/i18n/de/integrations.js +20 -20
  439. package/dist/i18n/de/integrations.js.map +1 -1
  440. package/dist/i18n/de/markdown.js +1 -1
  441. package/dist/i18n/de/markdown.js.map +1 -1
  442. package/dist/i18n/de/palette.js +1 -1
  443. package/dist/i18n/de/palette.js.map +1 -1
  444. package/dist/i18n/de/themes.js +3 -3
  445. package/dist/i18n/de/themes.js.map +1 -1
  446. package/dist/i18n/de/transfer.js +2 -2
  447. package/dist/i18n/de/transfer.js.map +1 -1
  448. package/dist/i18n/de/tree.js +6 -6
  449. package/dist/i18n/de/tree.js.map +1 -1
  450. package/dist/i18n/de.d.ts +101 -98
  451. package/dist/i18n/en/ai.js +2 -2
  452. package/dist/i18n/en/ai.js.map +1 -1
  453. package/dist/i18n/en/composites.js +64 -61
  454. package/dist/i18n/en/composites.js.map +1 -1
  455. package/dist/i18n/en/integrations.js +9 -9
  456. package/dist/i18n/en/integrations.js.map +1 -1
  457. package/dist/i18n/en/markdown.js +1 -1
  458. package/dist/i18n/en/markdown.js.map +1 -1
  459. package/dist/i18n/en/palette.js +1 -1
  460. package/dist/i18n/en/palette.js.map +1 -1
  461. package/dist/i18n/en/themes.js +4 -4
  462. package/dist/i18n/en/themes.js.map +1 -1
  463. package/dist/i18n/en/tree.js +5 -5
  464. package/dist/i18n/en/tree.js.map +1 -1
  465. package/dist/index.d.ts +4 -1
  466. package/dist/integrations/composite-ai/bibliography-fetch.d.ts +6 -6
  467. package/dist/integrations/composite-ai/bibliography-fetch.js +19 -19
  468. package/dist/integrations/composite-ai/bibliography-fetch.js.map +1 -1
  469. package/dist/integrations/composite-ai/composite-fill.d.ts +4 -4
  470. package/dist/integrations/composite-ai/composite-fill.js +5 -5
  471. package/dist/integrations/composite-ai/composite-fill.js.map +1 -1
  472. package/dist/integrations/composite-ai/composite-summary.d.ts +2 -2
  473. package/dist/integrations/composite-ai/composite-summary.js +5 -5
  474. package/dist/integrations/composite-ai/composite-summary.js.map +1 -1
  475. package/dist/integrations/composite-ai/derive.d.ts +17 -17
  476. package/dist/integrations/composite-ai/derive.js +27 -26
  477. package/dist/integrations/composite-ai/derive.js.map +1 -1
  478. package/dist/integrations/composite-ai/index.d.ts +14 -15
  479. package/dist/integrations/composite-ai/index.js +98 -77
  480. package/dist/integrations/composite-ai/index.js.map +1 -1
  481. package/dist/integrations/composite-ai/outline-sanitize.d.ts +1 -1
  482. package/dist/integrations/composite-ai/outline-sanitize.js +1 -1
  483. package/dist/integrations/composite-ai/outline-sanitize.js.map +1 -1
  484. package/dist/integrations/composite-ai/rendition.d.ts +9 -9
  485. package/dist/integrations/composite-ai/rendition.js +27 -27
  486. package/dist/integrations/composite-ai/rendition.js.map +1 -1
  487. package/dist/integrations/composite-ai/split.d.ts +8 -8
  488. package/dist/integrations/composite-ai/split.js +12 -12
  489. package/dist/integrations/composite-ai/split.js.map +1 -1
  490. package/dist/integrations/composite-ai/transfer-transforms.d.ts +2 -2
  491. package/dist/integrations/composite-ai/transfer-transforms.js +12 -12
  492. package/dist/integrations/composite-ai/transfer-transforms.js.map +1 -1
  493. package/dist/integrations/composite-media/index.d.ts +4 -3
  494. package/dist/integrations/composite-media/index.js +20 -14
  495. package/dist/integrations/composite-media/index.js.map +1 -1
  496. package/dist/markdown/document.js +1 -1
  497. package/dist/markdown/document.js.map +1 -1
  498. package/dist/nuxt/module.d.ts +30 -24
  499. package/dist/nuxt/module.js.map +1 -1
  500. package/dist/nuxt/runtime/server/plugins/editor-server.js +3 -2
  501. package/dist/nuxt/runtime/server/plugins/editor-server.js.map +1 -1
  502. package/dist/presets/compose.d.ts +6 -6
  503. package/dist/presets/compose.js +8 -5
  504. package/dist/presets/compose.js.map +1 -1
  505. package/dist/react/AdvancedEditor.d.ts +2 -2
  506. package/dist/react/AdvancedEditor.js +1 -1
  507. package/dist/react/AdvancedEditor.js.map +1 -1
  508. package/dist/react/compose.d.ts +1 -1
  509. package/dist/react/compose.js +2 -2
  510. package/dist/react/compose.js.map +1 -1
  511. package/dist/react/composed-surface.d.ts +5 -5
  512. package/dist/react/composed-surface.js +2 -2
  513. package/dist/react/composed-surface.js.map +1 -1
  514. package/dist/server/ai.d.ts +7 -7
  515. package/dist/server/ai.js +1 -1
  516. package/dist/server/ai.js.map +1 -1
  517. package/dist/server/auth.d.ts +1 -1
  518. package/dist/server/collab-state.d.ts +1 -1
  519. package/dist/server/collab-state.js.map +1 -1
  520. package/dist/server/collab.d.ts +4 -4
  521. package/dist/server/collab.js +22 -17
  522. package/dist/server/collab.js.map +1 -1
  523. package/dist/server/handlers/ai.d.ts +7 -4
  524. package/dist/server/handlers/ai.js +13 -17
  525. package/dist/server/handlers/ai.js.map +1 -1
  526. package/dist/server/handlers/context.d.ts +4 -0
  527. package/dist/server/handlers/context.js.map +1 -1
  528. package/dist/server/handlers/editor.d.ts +33 -32
  529. package/dist/server/handlers/editor.js +13 -8
  530. package/dist/server/handlers/editor.js.map +1 -1
  531. package/dist/server/handlers/images.d.ts +1 -1
  532. package/dist/server/handlers/images.js +1 -1
  533. package/dist/server/handlers/images.js.map +1 -1
  534. package/dist/server/host.d.ts +4 -0
  535. package/dist/server/host.js +10 -2
  536. package/dist/server/host.js.map +1 -1
  537. package/dist/server/index.d.ts +3 -3
  538. package/dist/server/ports.d.ts +8 -8
  539. package/dist/server/ports.js.map +1 -1
  540. package/dist/settings/index.d.ts +20 -19
  541. package/dist/settings/index.js +25 -24
  542. package/dist/settings/index.js.map +1 -1
  543. package/dist/settings/migrate.d.ts +18 -0
  544. package/dist/settings/migrate.js +46 -0
  545. package/dist/settings/migrate.js.map +1 -0
  546. package/dist/settings/values.d.ts +12 -12
  547. package/dist/settings/values.js +11 -11
  548. package/dist/settings/values.js.map +1 -1
  549. package/dist/styles/composites.css +10 -10
  550. package/dist/tree/DocumentTree.js +12 -5
  551. package/dist/tree/DocumentTree.js.map +1 -1
  552. package/dist/tree/drop-rules.js +5 -5
  553. package/dist/tree/drop-rules.js.map +1 -1
  554. package/dist/tree/react/DocumentTree.js +14 -5
  555. package/dist/tree/react/DocumentTree.js.map +1 -1
  556. package/dist/tree/tree-host.js +6 -6
  557. package/dist/tree/tree-host.js.map +1 -1
  558. package/dist/tree/tree-items.js +34 -28
  559. package/dist/tree/tree-items.js.map +1 -1
  560. package/dist/tree/tree-model.js +16 -16
  561. package/dist/tree/tree-model.js.map +1 -1
  562. package/dist/tree/tree-rows.js +2 -2
  563. package/dist/tree/tree-rows.js.map +1 -1
  564. package/dist/tree/tree-view-model.d.ts +2 -2
  565. package/dist/tree/tree-view-model.js.map +1 -1
  566. package/dist/vue/AdvancedEditor.js +2 -2
  567. package/dist/vue/AdvancedEditor.js.map +1 -1
  568. package/dist/vue/AdvancedEditor.vue.d.ts +6 -6
  569. package/dist/vue/compose.d.ts +1 -1
  570. package/dist/vue/compose.js +2 -2
  571. package/dist/vue/compose.js.map +1 -1
  572. package/package.json +1 -1
  573. package/dist/features/composites/derivation.d.ts +0 -62
  574. package/dist/features/composites/derivation.js +0 -102
  575. package/dist/features/composites/derivation.js.map +0 -1
  576. package/dist/features/composites/kinds.d.ts +0 -89
  577. package/dist/features/composites/kinds.js +0 -285
  578. package/dist/features/composites/kinds.js.map +0 -1
@@ -8,7 +8,9 @@ import { readBlockLines } from "../../features/ai/protocol/block-lines.js";
8
8
  import { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_TYPES, AGENT_TOOL, agentOpTarget, agentRetryMessage, agentUnreadableProblem, checkAgentOps } from "../../features/ai/protocol/agent-ops.js";
9
9
  import { CHAT_LIMITS, documentChatMessages } from "../../features/ai/protocol/chat.js";
10
10
  import { TranscriptCleanupUnreadableError, runTranscriptCleanup, transcriptCleanupRequestSchema } from "../../integrations/transcript-ai/protocol.js";
11
+ import { PROPERTY_TYPES } from "../../core/properties.js";
11
12
  import { HttpError } from "../errors.js";
13
+ import { aiBlockTypesOf } from "../../features/composites/block-types.js";
12
14
  import { ASSET_DOCUMENT_ID, ASSET_NAME } from "../../features/media/assets.js";
13
15
  import { authenticated, enforceRateLimit, eventStream, json, port, readBody } from "./context.js";
14
16
  import { DOI_PATTERN } from "../crossref.js";
@@ -21,7 +23,7 @@ import { jsonSchema, tool } from "ai";
21
23
  const blockId = z.string().min(1).max(100);
22
24
  const opId = z.string().regex(/^op\d+$/);
23
25
  const markdown = z.string().min(1).max(AGENT_LIMITS.markdown);
24
- /** A `convert` target: a `CONVERT_TARGET_IDS` entry, `composite:<kind>`, or a host block id (`aiContext.blocks` with a conversion provider). */
26
+ /** A `convert` target: a `CONVERT_TARGET_IDS` entry, `typedBlock:<kind>`, or a host block id (`aiContext.blocks` with a conversion provider). */
25
27
  const convertTarget = z.string().min(1).max(100);
26
28
  const opSchema = z.discriminatedUnion("type", [
27
29
  z.object({
@@ -49,21 +51,14 @@ const opSchema = z.discriminatedUnion("type", [
49
51
  })
50
52
  ]);
51
53
  const label = z.string().min(1).max(100);
52
- const kindSchema = z.object({
54
+ const blockTypeSchema = z.object({
53
55
  id: z.string().regex(AI_ID_PATTERN).max(50),
54
56
  label,
55
57
  purpose: z.string().min(1).max(500).optional(),
56
58
  fields: z.array(z.object({
57
59
  key: z.string().min(1).max(50),
58
60
  label,
59
- type: z.enum([
60
- "text",
61
- "textarea",
62
- "date",
63
- "number",
64
- "select",
65
- "multiselect"
66
- ]),
61
+ type: z.enum(PROPERTY_TYPES),
67
62
  options: z.array(z.object({
68
63
  value: z.string().min(1).max(100),
69
64
  label
@@ -93,8 +88,8 @@ const chatBodySchema = z.object({
93
88
  /** Number of the first new operation (`op<n>`), so IDs stay unique across the conversation. */
94
89
  nextOp: z.number().int().min(1).max(1e6).default(1),
95
90
  model: modelRefSchema,
96
- /** The editor's composite kinds (`compositeKindsOf`), for the block model in the system prompt. */
97
- kinds: z.array(kindSchema).max(50),
91
+ /** The editor's Block types (`blockTypesOf`), for the block model in the system prompt. Ignored when the server resolves them itself (`AiHandlerContext.blockTypes`). */
92
+ blockTypes: z.array(blockTypeSchema).max(50),
98
93
  /** The editor's host blocks (`aiContext.blocks`), for the block model in the system prompt. */
99
94
  blocks: z.array(aiBlockSchema).max(50).default([]),
100
95
  /** Block types `convert` may turn a block into. */
@@ -146,13 +141,14 @@ const agentTools = { [AGENT_TOOL]: tool({
146
141
  * `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, an `add` without `afterId`, an
147
142
  * operation inside a block that another operation replaces or deletes, or converting into a type not in
148
143
  * `targets`, make the model retry (at most `AGENT_LIMITS.retries` times); what is still invalid then, or left out by a
149
- * corrected list, is dropped. The system prompt describes the editor's block model from `kinds`, `blocks` and `targets`. SSE like
144
+ * corrected list, is dropped. The system prompt describes the editor's block model from `blockTypes`, `blocks` and `targets`. SSE like
150
145
  * `/api/ai/generate`: `delta` (text), `ops` (`{ ops: AgentOp[], dropped: AgentOpProblem[], retries: number }`, once,
151
146
  * before `done`), `done`, `error`.
152
147
  */
153
148
  async function chatPost(ctx, request) {
154
149
  const user = await enforceRateLimit(ctx, request);
155
- const { messages, document, scope, nextOp, model, kinds, blocks, targets, structure } = await readBody(request, chatBodySchema);
150
+ const { messages, document, scope, nextOp, model, blockTypes: clientBlockTypes, blocks, targets, structure } = await readBody(request, chatBodySchema);
151
+ const blockTypes = ctx.blockTypes ? aiBlockTypesOf(ctx.blockTypes) : clientBlockTypes;
156
152
  const languageModel = await ctx.ai.resolveModel(model, user);
157
153
  if (!messages.at(-1).content.trim()) throw new HttpError(400, "Nachricht fehlt");
158
154
  const lines = readBlockLines(document);
@@ -166,7 +162,7 @@ async function chatPost(ctx, request) {
166
162
  return eventStream(request, async (emit, signal) => {
167
163
  try {
168
164
  let conversation = documentChatMessages(messages, document, allowed && [...allowed], {
169
- kinds,
165
+ blockTypes,
170
166
  blocks,
171
167
  targets,
172
168
  structure
@@ -288,14 +284,14 @@ async function generatePost(ctx, request) {
288
284
  }
289
285
  /**
290
286
  * The models the client may choose from (without keys) — the `models` port's `listModels` for the user, else the
291
- * server-defined presets — and the default preset id per model slot, for the client's model profiles. Requires an
287
+ * server-defined presets — and the default preset id per model role, for the client's model profiles. Requires an
292
288
  * authenticated user through the `identity` port; not rate limited, since it calls no model.
293
289
  */
294
290
  async function modelsGet(ctx, request) {
295
291
  const user = await authenticated(ctx, request);
296
292
  return json({
297
293
  presets: await ctx.ai.listModels(user),
298
- slots: ctx.ai.slots
294
+ roles: ctx.ai.roles
299
295
  });
300
296
  }
301
297
  const sha256Hex = (data) => createHash("sha256").update(data).digest("hex");
@@ -1 +1 @@
1
- {"version":3,"file":"ai.js","names":[],"sources":["../../../src/server/handlers/ai.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto'\nimport { jsonSchema, type ModelMessage, tool } from 'ai'\nimport { z } from 'zod'\nimport {\n AGENT_LIMITS,\n AGENT_OP_OUTCOMES,\n AGENT_OP_TYPES,\n AGENT_TOOL,\n AI_ID_PATTERN,\n AI_TASKS,\n type AgentOp,\n type AgentOpProblem,\n agentOpTarget,\n agentRetryMessage,\n agentUnreadableProblem,\n aiBlockSchema,\n checkAgentOps,\n CHAT_LIMITS,\n completeBodySchema,\n documentChatMessages,\n generateBodySchema,\n generateProblem,\n type ModelAgentOp,\n modelRefSchema,\n readBlockLines,\n runCompletion,\n runGenerate,\n} from '../../features/ai/protocol'\nimport type { ExtractMethod, ExtractRequest, ExtractResponse } from '../../core/backend-types'\nimport type { CompositeMetaField } from '../../features/composites/schema'\nimport { ASSET_DOCUMENT_ID, ASSET_NAME } from '../../features/media/schema'\nimport {\n runTranscriptCleanup,\n TranscriptCleanupUnreadableError,\n transcriptCleanupRequestSchema,\n} from '../../integrations/transcript-ai/protocol'\nimport { STRUCTURE_MODES } from '../../settings'\nimport { HttpError } from '../errors'\nimport { BIBLIOGRAPHY_PAGES, bibliographyText, describeImage, extractBibliography, isScanned, joinPages, MAX_OCR_PAGES, MAX_TRANSCRIBE_BYTES, ocrPages, pdfPagesText, RasterError, transcribe, type TranscriptionResult } from '../extract'\nimport { DOI_PATTERN } from '../crossref'\nimport { openPdf } from '../pdf'\nimport { type AiHandlerContext, authenticated, enforceRateLimit, eventStream, type HandlerRequest, type HandlerResponse, json, port, readBody } from './context'\n\nconst blockId = z.string().min(1).max(100)\nconst opId = z.string().regex(/^op\\d+$/)\nconst markdown = z.string().min(1).max(AGENT_LIMITS.markdown)\n/** A `convert` target: a `CONVERT_TARGET_IDS` entry, `composite:<kind>`, or a host block id (`aiContext.blocks` with a conversion provider). */\nconst convertTarget = z.string().min(1).max(100)\n\nconst opSchema = z.discriminatedUnion('type', [\n z.object({ op: opId, type: z.literal('update'), id: blockId, markdown }),\n z.object({ op: opId, type: z.literal('delete'), id: blockId }),\n z.object({ op: opId, type: z.literal('add'), afterId: blockId.nullable(), markdown }),\n z.object({ op: opId, type: z.literal('convert'), id: blockId, to: convertTarget }),\n])\n\nconst label = z.string().min(1).max(100)\nconst kindSchema = z.object({\n // Same characters as the `::: kind` fence opener accepts.\n id: z.string().regex(AI_ID_PATTERN).max(50),\n label,\n purpose: z.string().min(1).max(500).optional(),\n fields: z.array(z.object({\n key: z.string().min(1).max(50),\n label,\n type: z.enum(['text', 'textarea', 'date', 'number', 'select', 'multiselect'] as const satisfies readonly CompositeMetaField['type'][]),\n options: z.array(z.object({ value: z.string().min(1).max(100), label })).max(50).optional(),\n })).max(20),\n})\n\nconst messageSchema = z.discriminatedUnion('role', [\n z.object({\n role: z.literal('user'),\n content: z.string().min(1).max(CHAT_LIMITS.message),\n feedback: z.array(z.object({ op: opId, type: z.enum(AGENT_OP_TYPES), target: blockId.nullable(), outcome: z.enum(AGENT_OP_OUTCOMES) })).max(500).optional(),\n }),\n z.object({\n role: z.literal('assistant'),\n content: z.string().max(CHAT_LIMITS.message),\n ops: z.array(opSchema).max(AGENT_LIMITS.ops).optional(),\n dropped: z.number().int().min(0).max(1000).optional(),\n }).refine(m => m.content || m.ops?.length || m.dropped, 'Leere Antwort im Verlauf'),\n])\n\nexport const chatBodySchema = z.object({\n messages: z.array(messageSchema).min(1).max(CHAT_LIMITS.messages).refine(messages => messages.at(-1)!.role === 'user', 'Die letzte Nachricht muss von der Nutzerin bzw. dem Nutzer sein'),\n document: z.string().max(CHAT_LIMITS.document),\n /** Block IDs edits are limited to (the selected blocks); null or missing: the whole document. */\n scope: z.array(blockId).min(1).max(AGENT_LIMITS.scope).nullish(),\n /** Number of the first new operation (`op<n>`), so IDs stay unique across the conversation. */\n nextOp: z.number().int().min(1).max(1_000_000).default(1),\n model: modelRefSchema,\n /** The editor's composite kinds (`compositeKindsOf`), for the block model in the system prompt. */\n kinds: z.array(kindSchema).max(50),\n /** The editor's host blocks (`aiContext.blocks`), for the block model in the system prompt. */\n blocks: z.array(aiBlockSchema).max(50).default([]),\n /** Block types `convert` may turn a block into. */\n targets: z.array(convertTarget).max(100),\n /** The user's preferred structure of new content (settings `blocks.structure`). */\n structure: z.enum(STRUCTURE_MODES),\n})\n\n/** `edit_document`: deliberately loose, so malformed calls reach `checkAgentOps` and get a precise retry message. */\nconst agentTools = {\n [AGENT_TOOL]: tool({\n description: 'Schlägt Änderungen am Dokument vor. Jede Operation wird der Nutzerin bzw. dem Nutzer im Dokument zur Prüfung angezeigt.',\n inputSchema: jsonSchema<{ ops: unknown[] }>({\n type: 'object',\n properties: {\n ops: {\n type: 'array',\n description: 'Die Operationen in Dokumentreihenfolge.',\n items: {\n type: 'object',\n properties: {\n type: { type: 'string', enum: [...AGENT_OP_TYPES], description: 'update: Blockinhalt ersetzen; delete: Block löschen; add: Blöcke einfügen; convert: Blocktyp ändern.' },\n id: { type: 'string', description: 'update/delete/convert: ID des Blocks aus dem Dokument.' },\n afterId: { type: ['string', 'null'], description: 'add (Pflicht): ID des Blocks, nach dem eingefügt wird; für den Dokumentanfang ausdrücklich null.' },\n markdown: { type: 'string', description: 'update/add: der neue Inhalt als Markdown, ohne Block-ID.' },\n to: { type: 'string', description: 'convert: der neue Blocktyp, einer der im Systemprompt erlaubten Werte (z. B. paragraph, heading2, composite:task).' },\n },\n required: ['type'],\n },\n },\n },\n required: ['ops'],\n }),\n }),\n}\n\n/**\n * Document chat: answers the last message of `messages` about `document`, or proposes edit operations through the\n * `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, an `add` without `afterId`, an\n * operation inside a block that another operation replaces or deletes, or converting into a type not in\n * `targets`, make the model retry (at most `AGENT_LIMITS.retries` times); what is still invalid then, or left out by a\n * corrected list, is dropped. The system prompt describes the editor's block model from `kinds`, `blocks` and `targets`. SSE like\n * `/api/ai/generate`: `delta` (text), `ops` (`{ ops: AgentOp[], dropped: AgentOpProblem[], retries: number }`, once,\n * before `done`), `done`, `error`.\n */\nexport async function chatPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { messages, document, scope, nextOp, model, kinds, blocks, targets, structure } = await readBody(request, chatBodySchema)\n // Unknown preset or missing key must fail as a plain JSON error before the SSE response starts.\n const languageModel = await ctx.ai.resolveModel(model, user)\n if (!messages.at(-1)!.content.trim()) throw new HttpError(400, 'Nachricht fehlt')\n\n const lines = readBlockLines(document)\n const allowed = scope ? new Set(scope.filter(id => lines.ids.has(id))) : null\n if (allowed && !allowed.size) throw new HttpError(400, 'Die Auswahl enthält keine Blöcke des Dokuments')\n const context = { lines, allowed, targets: new Set(targets) }\n\n return eventStream(request, async (emit, signal) => {\n try {\n let conversation: ModelMessage[] = documentChatMessages(messages, document, allowed && [...allowed], { kinds, blocks, targets, structure })\n let answered = false\n let result: { valid: ModelAgentOp[], problems: AgentOpProblem[] } | null = null\n // Problems of attempts that were retried: an operation the corrected list leaves out counts as dropped too.\n const retried: AgentOpProblem[] = []\n let retries = 0\n for (let attempt = 0; attempt <= AGENT_LIMITS.retries; attempt++) {\n const calls: { toolCallId: string, input: unknown, invalid: boolean }[] = []\n let text = ''\n for await (const part of ctx.ai.streamChatTurn({\n model: languageModel,\n messages: conversation,\n tools: agentTools,\n // Retries only correct the operations: no second answer text.\n toolChoice: attempt ? { type: 'tool', toolName: AGENT_TOOL } : 'auto',\n signal,\n })) {\n if (part.type === 'tool-call') {\n if (part.toolName === AGENT_TOOL) calls.push(part)\n }\n else if (!attempt) {\n text += part.text\n answered = true\n emit('delta', JSON.stringify({ text: part.text }))\n }\n }\n if (!calls.length) break\n\n const unreadable = calls.some(call => call.invalid || !Array.isArray((call.input as { ops?: unknown } | null)?.ops))\n const raw = calls.flatMap(call => (call.input as { ops?: unknown } | null)?.ops).filter(op => op !== undefined)\n const checked = checkAgentOps(raw, context)\n if (unreadable) checked.problems.unshift(agentUnreadableProblem())\n result = checked\n if (!checked.problems.length || attempt === AGENT_LIMITS.retries) break\n\n retried.push(...checked.problems)\n retries++\n const retry = agentRetryMessage(checked.problems, !!allowed)\n conversation = [\n ...conversation,\n {\n role: 'assistant',\n content: [\n ...(text ? [{ type: 'text' as const, text }] : []),\n ...calls.map(call => ({ type: 'tool-call' as const, toolCallId: call.toolCallId, toolName: AGENT_TOOL, input: call.input ?? {} })),\n ],\n },\n { role: 'tool', content: calls.map(call => ({ type: 'tool-result' as const, toolCallId: call.toolCallId, toolName: AGENT_TOOL, output: { type: 'text' as const, value: retry } })) },\n ]\n }\n\n if (result) {\n const ops: AgentOp[] = result.valid.map((op, i) => ({ ...op, op: `op${nextOp + i}` }))\n const targets = new Set(ops.map(op => agentOpTarget(op)))\n const dropped = [...result.problems]\n for (const problem of retried) {\n if (problem.id !== null && !targets.has(problem.id) && !dropped.some(d => d.id === problem.id && d.type === problem.type)) dropped.push(problem)\n }\n if (!answered && !ops.length) {\n emit('delta', JSON.stringify({ text: 'Ich konnte keine gültigen Änderungen vorschlagen.' }))\n }\n emit('ops', JSON.stringify({ ops, dropped, retries }))\n }\n emit('done', '{}')\n \n }\n catch (error) {\n if (!signal.aborted) {\n const { statusMessage, message } = error as { statusMessage?: string, message?: string }\n emit('error', JSON.stringify({ message: statusMessage ?? message ?? 'Unbekannter Fehler' }))\n }\n }\n })\n}\n\n// The body schemas live with the protocol (the browser-direct adapter validates with them too); the handlers keep exporting them.\nexport { completeBodySchema, generateBodySchema }\n\n/** Short inline continuation (ghost text) of the text before the caret. Plain JSON: `{ text }`, empty when nothing fits. */\nexport async function completePost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { before, model } = await readBody(request, completeBodySchema)\n const languageModel = await ctx.ai.resolveModel(model, user)\n\n // Typing on the client aborts its fetch; stop the upstream request with it.\n const abort = new AbortController()\n request.signal?.addEventListener('abort', () => abort.abort(), { once: true })\n return json({ text: await runCompletion(before, (messages, signal) => ctx.ai.streamChat({ model: languageModel, messages, signal }), abort.signal) })\n}\n\n/** Streams one text task (rewrite, translate, generate, …) as SSE: `delta` (`{ text }`), `done`, `error` (`{ message }`). */\nexport async function generatePost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { task: taskId, model, ...input } = await readBody(request, generateBodySchema)\n // Unknown preset or missing key must fail as a plain JSON error before the SSE response starts.\n const languageModel = await ctx.ai.resolveModel(model, user)\n const task = AI_TASKS[taskId]\n const problem = generateProblem(task, input)\n if (problem) throw new HttpError(400, problem)\n const users = task.needsUsers ? (await port(ctx, 'directory').listUsers()).map(u => u.username) : undefined\n\n return eventStream(request, async (emit, signal) => {\n for await (const { event, data } of runGenerate(task, input, users, (messages, streamSignal) => ctx.ai.streamChat({ model: languageModel, messages, signal: streamSignal }), signal)) {\n emit(event, JSON.stringify(data))\n }\n })\n}\n\n/**\n * The models the client may choose from (without keys) — the `models` port's `listModels` for the user, else the\n * server-defined presets — and the default preset id per model slot, for the client's model profiles. Requires an\n * authenticated user through the `identity` port; not rate limited, since it calls no model.\n */\nexport async function modelsGet(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await authenticated(ctx, request)\n return json({ presets: await ctx.ai.listModels(user), slots: ctx.ai.slots })\n}\n\nconst sha256Hex = (data: string | Uint8Array): string => createHash('sha256').update(data).digest('hex')\n\n/** PDF pages read by `pdf-text` and `ocr` unless the request caps them lower. */\nconst DEFAULT_MAX_PAGES = 200\n\n/** Body schema of `extractPost`. */\nexport const extractBodySchema = z.object({\n documentId: z.string().regex(ASSET_DOCUMENT_ID),\n name: z.string().regex(ASSET_NAME).optional(),\n method: z.enum(['auto', 'transcription', 'pdf-text', 'ocr', 'description', 'bibliography']),\n model: modelRefSchema,\n bibliography: z.boolean().optional(),\n doi: z.string().regex(DOI_PATTERN).optional(),\n maxPages: z.number().int().min(1).max(500).default(DEFAULT_MAX_PAGES),\n}).refine(body => body.name !== undefined || (body.method === 'bibliography' && body.doi !== undefined), {\n message: 'name fehlt (nur eine Literaturabfrage mit DOI braucht keine Datei)',\n path: ['name'],\n}) satisfies z.ZodType<ExtractRequest>\n\n/** The method an `ExtractRequest` runs for an asset of `mimeType`: `auto` picks by type; 415 when the type does not fit the method. */\nfunction extractMethodOf(requested: ExtractMethod, mimeType: string, hasDoi: boolean): ExtractResponse['method'] {\n const audioVideo = /^(?:audio|video)\\//.test(mimeType)\n const image = mimeType.startsWith('image/')\n const pdf = mimeType === 'application/pdf'\n const fits: Record<ExtractMethod, boolean> = {\n 'auto': audioVideo || image || pdf,\n 'transcription': audioVideo,\n 'description': image,\n 'pdf-text': pdf,\n 'ocr': pdf,\n // A given DOI needs no document text, so any asset can stand for the work.\n 'bibliography': pdf || hasDoi,\n }\n if (!fits[requested]) throw new HttpError(415, `Dateityp ${mimeType} wird für diese Auswertung nicht unterstützt`)\n if (requested !== 'auto') return requested\n if (audioVideo) return 'transcription'\n return image ? 'description' : 'pdf-text'\n}\n\n/**\n * Turns a stored asset into readable content (`ExtractRequest` → `ExtractResponse`): audio/video into a transcription\n * with segment timestamps, a PDF into its text per page (OCR through the model when it has no text layer), an image\n * into a description, bibliographic data from Crossref or the model. The raw Markdown is stored as an asset of the same\n * document (`output`), with the SHA-256 of input and output. Errors: 404 unknown asset, 415 type not supported, 413\n * oversize audio, 422 unreadable PDF, 501 transcription or OCR unavailable, 502 upstream failure.\n */\nexport async function extractPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const body = await readBody(request, extractBodySchema)\n if (body.name === undefined) {\n // Only a DOI lookup comes without a file (the schema allows nothing else): no asset, no output, empty hashes.\n const { bib, warnings } = await extractBibliography({ doi: body.doi, text: '', resolveModel: () => ctx.ai.resolveModel(body.model, user), signal: request.signal })\n return json({ method: 'bibliography', markdown: '', bib, inputHash: '', outputHash: sha256Hex(''), output: null, warnings } satisfies ExtractResponse)\n }\n const assets = port(ctx, 'assets')\n const asset = await assets.loadAsset(body.documentId, body.name)\n if (!asset) throw new HttpError(404, 'Datei nicht gefunden')\n const inputHash = sha256Hex(asset.data)\n let method = extractMethodOf(body.method, asset.record.mimeType, body.doi !== undefined)\n // Closing the page aborts the client's fetch; the upstream requests stop with it.\n const { signal } = request\n const resolveModel = () => ctx.ai.resolveModel(body.model, user)\n\n const warnings: string[] = []\n let markdown = ''\n let model: string | undefined\n let segments: TranscriptionResult['segments']\n let bib: ExtractResponse['bib']\n\n if (method === 'transcription') {\n const config = ctx.ai.transcription\n if (!config?.endpoint || !config.model) throw new HttpError(501, 'Transkription nicht konfiguriert')\n if (asset.data.byteLength > MAX_TRANSCRIBE_BYTES) throw new HttpError(413, `Datei ist größer als ${MAX_TRANSCRIBE_BYTES / 1024 / 1024} MB`)\n ;({ markdown, segments } = await transcribe(config, asset, signal))\n model = config.model\n }\n else if (method === 'description') {\n markdown = await describeImage(await resolveModel(), asset.data, asset.record.mimeType, signal)\n }\n else if (asset.record.mimeType !== 'application/pdf') {\n // `bibliography` of a non-PDF asset: only reachable with a DOI.\n const found = await extractBibliography({ doi: body.doi, text: '', resolveModel, signal })\n bib = found.bib\n warnings.push(...found.warnings)\n }\n else {\n const pdf = await openPdf(asset.data)\n try {\n if (method === 'bibliography') {\n const pages = await pdfPagesText(pdf, Math.min(pdf.pageCount, BIBLIOGRAPHY_PAGES), signal)\n const found = await extractBibliography({ doi: body.doi, text: bibliographyText(pages), resolveModel, signal })\n bib = found.bib\n warnings.push(...found.warnings)\n }\n else {\n const count = Math.min(pdf.pageCount, body.maxPages)\n if (pdf.pageCount > count) warnings.push('pages-truncated')\n let pages = await pdfPagesText(pdf, count, signal)\n if (body.method === 'auto' && isScanned(pages)) method = 'ocr'\n if (method === 'ocr') {\n // One vision call per page: the OCR reads fewer pages than the text layer would.\n const ocrCount = Math.min(count, MAX_OCR_PAGES)\n try {\n pages = await ocrPages(pdf, ocrCount, resolveModel, signal)\n if (ocrCount < count) warnings.push('ocr-truncated')\n }\n catch (error) {\n if (!(error instanceof RasterError)) throw error\n // Pages cannot be rendered here: keep what the text layer has.\n if (!pages.some(Boolean)) throw new HttpError(501, 'OCR nicht verfügbar')\n warnings.push('ocr-unavailable')\n method = 'pdf-text'\n }\n }\n markdown = joinPages(pages)\n if (body.bibliography) {\n try {\n const found = await extractBibliography({ doi: body.doi, text: bibliographyText(pages), resolveModel, signal })\n bib = found.bib\n warnings.push(...found.warnings)\n }\n catch (error) {\n // The text is the result; missing bibliographic data must not lose it.\n if (signal?.aborted) throw error\n warnings.push('bibliography-failed')\n }\n }\n }\n }\n finally {\n await pdf.close()\n }\n }\n\n const outputHash = sha256Hex(markdown)\n let output: string | null = null\n if (method !== 'bibliography' && markdown) {\n const data = Buffer.from(markdown, 'utf8')\n output = `${randomUUID()}.md`\n await assets.storeAsset(body.documentId, {\n name: output,\n filename: `${asset.record.filename}.${method}.md`.slice(0, 255),\n mimeType: 'text/markdown',\n size: data.byteLength,\n createdAt: new Date().toISOString(),\n sha256: outputHash,\n data,\n })\n }\n return json({ method, markdown, ...(segments && { segments }), ...(bib && { bib }), ...(model && { model }), inputHash, outputHash, output, warnings } satisfies ExtractResponse)\n}\n\n/** Body schema of `transcriptCleanupPost`. */\nexport const transcriptCleanupBodySchema = transcriptCleanupRequestSchema\n\n/**\n * Cleans transcript segments (filler words, punctuation, casing, fragmented sentences) without touching speakers or\n * order: the model only returns a new text per segment ID. Plain JSON: `{ cleaned, failed }`.\n */\nexport async function transcriptCleanupPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { segments, model } = await readBody(request, transcriptCleanupBodySchema)\n const languageModel = await ctx.ai.resolveModel(model, user)\n\n // Closing the menu or the page aborts the client's fetch; stop the upstream requests with it.\n const abort = new AbortController()\n request.signal?.addEventListener('abort', () => abort.abort(), { once: true })\n try {\n return json(await runTranscriptCleanup(segments, (messages, signal) => ctx.ai.streamChat({ model: languageModel, messages, signal }), abort.signal))\n }\n catch (error) {\n throw error instanceof TranscriptCleanupUnreadableError ? new HttpError(502, error.message) : error\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ai.js","names":[],"sources":["../../../src/server/handlers/ai.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto'\nimport { jsonSchema, type ModelMessage, tool } from 'ai'\nimport { z } from 'zod'\nimport {\n AGENT_LIMITS,\n AGENT_OP_OUTCOMES,\n AGENT_OP_TYPES,\n AGENT_TOOL,\n AI_ID_PATTERN,\n AI_TASKS,\n type AgentOp,\n type AgentOpProblem,\n agentOpTarget,\n agentRetryMessage,\n agentUnreadableProblem,\n aiBlockSchema,\n checkAgentOps,\n CHAT_LIMITS,\n completeBodySchema,\n documentChatMessages,\n generateBodySchema,\n generateProblem,\n type ModelAgentOp,\n modelRefSchema,\n readBlockLines,\n runCompletion,\n runGenerate,\n} from '../../features/ai/protocol'\nimport type { ExtractMethod, ExtractRequest, ExtractResponse } from '../../core/backend-types'\nimport { PROPERTY_TYPES } from '../../core/properties'\nimport { aiBlockTypesOf } from '../../features/composites/schema'\nimport { ASSET_DOCUMENT_ID, ASSET_NAME } from '../../features/media/schema'\nimport {\n runTranscriptCleanup,\n TranscriptCleanupUnreadableError,\n transcriptCleanupRequestSchema,\n} from '../../integrations/transcript-ai/protocol'\nimport { STRUCTURE_MODES } from '../../settings'\nimport { HttpError } from '../errors'\nimport { BIBLIOGRAPHY_PAGES, bibliographyText, describeImage, extractBibliography, isScanned, joinPages, MAX_OCR_PAGES, MAX_TRANSCRIBE_BYTES, ocrPages, pdfPagesText, RasterError, transcribe, type TranscriptionResult } from '../extract'\nimport { DOI_PATTERN } from '../crossref'\nimport { openPdf } from '../pdf'\nimport { type AiHandlerContext, authenticated, enforceRateLimit, eventStream, type HandlerRequest, type HandlerResponse, json, port, readBody } from './context'\n\nconst blockId = z.string().min(1).max(100)\nconst opId = z.string().regex(/^op\\d+$/)\nconst markdown = z.string().min(1).max(AGENT_LIMITS.markdown)\n/** A `convert` target: a `CONVERT_TARGET_IDS` entry, `typedBlock:<kind>`, or a host block id (`aiContext.blocks` with a conversion provider). */\nconst convertTarget = z.string().min(1).max(100)\n\nconst opSchema = z.discriminatedUnion('type', [\n z.object({ op: opId, type: z.literal('update'), id: blockId, markdown }),\n z.object({ op: opId, type: z.literal('delete'), id: blockId }),\n z.object({ op: opId, type: z.literal('add'), afterId: blockId.nullable(), markdown }),\n z.object({ op: opId, type: z.literal('convert'), id: blockId, to: convertTarget }),\n])\n\nconst label = z.string().min(1).max(100)\nconst blockTypeSchema = z.object({\n // Same characters as the `::: <Block type>` fence opener accepts.\n id: z.string().regex(AI_ID_PATTERN).max(50),\n label,\n purpose: z.string().min(1).max(500).optional(),\n fields: z.array(z.object({\n key: z.string().min(1).max(50),\n label,\n type: z.enum(PROPERTY_TYPES),\n options: z.array(z.object({ value: z.string().min(1).max(100), label })).max(50).optional(),\n })).max(20),\n})\n\nconst messageSchema = z.discriminatedUnion('role', [\n z.object({\n role: z.literal('user'),\n content: z.string().min(1).max(CHAT_LIMITS.message),\n feedback: z.array(z.object({ op: opId, type: z.enum(AGENT_OP_TYPES), target: blockId.nullable(), outcome: z.enum(AGENT_OP_OUTCOMES) })).max(500).optional(),\n }),\n z.object({\n role: z.literal('assistant'),\n content: z.string().max(CHAT_LIMITS.message),\n ops: z.array(opSchema).max(AGENT_LIMITS.ops).optional(),\n dropped: z.number().int().min(0).max(1000).optional(),\n }).refine(m => m.content || m.ops?.length || m.dropped, 'Leere Antwort im Verlauf'),\n])\n\nexport const chatBodySchema = z.object({\n messages: z.array(messageSchema).min(1).max(CHAT_LIMITS.messages).refine(messages => messages.at(-1)!.role === 'user', 'Die letzte Nachricht muss von der Nutzerin bzw. dem Nutzer sein'),\n document: z.string().max(CHAT_LIMITS.document),\n /** Block IDs edits are limited to (the selected blocks); null or missing: the whole document. */\n scope: z.array(blockId).min(1).max(AGENT_LIMITS.scope).nullish(),\n /** Number of the first new operation (`op<n>`), so IDs stay unique across the conversation. */\n nextOp: z.number().int().min(1).max(1_000_000).default(1),\n model: modelRefSchema,\n /** The editor's Block types (`blockTypesOf`), for the block model in the system prompt. Ignored when the server resolves them itself (`AiHandlerContext.blockTypes`). */\n blockTypes: z.array(blockTypeSchema).max(50),\n /** The editor's host blocks (`aiContext.blocks`), for the block model in the system prompt. */\n blocks: z.array(aiBlockSchema).max(50).default([]),\n /** Block types `convert` may turn a block into. */\n targets: z.array(convertTarget).max(100),\n /** The user's preferred structure of new content (settings `blocks.structure`). */\n structure: z.enum(STRUCTURE_MODES),\n})\n\n/** `edit_document`: deliberately loose, so malformed calls reach `checkAgentOps` and get a precise retry message. */\nconst agentTools = {\n [AGENT_TOOL]: tool({\n description: 'Schlägt Änderungen am Dokument vor. Jede Operation wird der Nutzerin bzw. dem Nutzer im Dokument zur Prüfung angezeigt.',\n inputSchema: jsonSchema<{ ops: unknown[] }>({\n type: 'object',\n properties: {\n ops: {\n type: 'array',\n description: 'Die Operationen in Dokumentreihenfolge.',\n items: {\n type: 'object',\n properties: {\n type: { type: 'string', enum: [...AGENT_OP_TYPES], description: 'update: Blockinhalt ersetzen; delete: Block löschen; add: Blöcke einfügen; convert: Blocktyp ändern.' },\n id: { type: 'string', description: 'update/delete/convert: ID des Blocks aus dem Dokument.' },\n afterId: { type: ['string', 'null'], description: 'add (Pflicht): ID des Blocks, nach dem eingefügt wird; für den Dokumentanfang ausdrücklich null.' },\n markdown: { type: 'string', description: 'update/add: der neue Inhalt als Markdown, ohne Block-ID.' },\n to: { type: 'string', description: 'convert: der neue Blocktyp, einer der im Systemprompt erlaubten Werte (z. B. paragraph, heading2, composite:task).' },\n },\n required: ['type'],\n },\n },\n },\n required: ['ops'],\n }),\n }),\n}\n\n/**\n * Document chat: answers the last message of `messages` about `document`, or proposes edit operations through the\n * `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, an `add` without `afterId`, an\n * operation inside a block that another operation replaces or deletes, or converting into a type not in\n * `targets`, make the model retry (at most `AGENT_LIMITS.retries` times); what is still invalid then, or left out by a\n * corrected list, is dropped. The system prompt describes the editor's block model from `blockTypes`, `blocks` and `targets`. SSE like\n * `/api/ai/generate`: `delta` (text), `ops` (`{ ops: AgentOp[], dropped: AgentOpProblem[], retries: number }`, once,\n * before `done`), `done`, `error`.\n */\nexport async function chatPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { messages, document, scope, nextOp, model, blockTypes: clientBlockTypes, blocks, targets, structure } = await readBody(request, chatBodySchema)\n const blockTypes = ctx.blockTypes ? aiBlockTypesOf(ctx.blockTypes) : clientBlockTypes\n // Unknown preset or missing key must fail as a plain JSON error before the SSE response starts.\n const languageModel = await ctx.ai.resolveModel(model, user)\n if (!messages.at(-1)!.content.trim()) throw new HttpError(400, 'Nachricht fehlt')\n\n const lines = readBlockLines(document)\n const allowed = scope ? new Set(scope.filter(id => lines.ids.has(id))) : null\n if (allowed && !allowed.size) throw new HttpError(400, 'Die Auswahl enthält keine Blöcke des Dokuments')\n const context = { lines, allowed, targets: new Set(targets) }\n\n return eventStream(request, async (emit, signal) => {\n try {\n let conversation: ModelMessage[] = documentChatMessages(messages, document, allowed && [...allowed], { blockTypes, blocks, targets, structure })\n let answered = false\n let result: { valid: ModelAgentOp[], problems: AgentOpProblem[] } | null = null\n // Problems of attempts that were retried: an operation the corrected list leaves out counts as dropped too.\n const retried: AgentOpProblem[] = []\n let retries = 0\n for (let attempt = 0; attempt <= AGENT_LIMITS.retries; attempt++) {\n const calls: { toolCallId: string, input: unknown, invalid: boolean }[] = []\n let text = ''\n for await (const part of ctx.ai.streamChatTurn({\n model: languageModel,\n messages: conversation,\n tools: agentTools,\n // Retries only correct the operations: no second answer text.\n toolChoice: attempt ? { type: 'tool', toolName: AGENT_TOOL } : 'auto',\n signal,\n })) {\n if (part.type === 'tool-call') {\n if (part.toolName === AGENT_TOOL) calls.push(part)\n }\n else if (!attempt) {\n text += part.text\n answered = true\n emit('delta', JSON.stringify({ text: part.text }))\n }\n }\n if (!calls.length) break\n\n const unreadable = calls.some(call => call.invalid || !Array.isArray((call.input as { ops?: unknown } | null)?.ops))\n const raw = calls.flatMap(call => (call.input as { ops?: unknown } | null)?.ops).filter(op => op !== undefined)\n const checked = checkAgentOps(raw, context)\n if (unreadable) checked.problems.unshift(agentUnreadableProblem())\n result = checked\n if (!checked.problems.length || attempt === AGENT_LIMITS.retries) break\n\n retried.push(...checked.problems)\n retries++\n const retry = agentRetryMessage(checked.problems, !!allowed)\n conversation = [\n ...conversation,\n {\n role: 'assistant',\n content: [\n ...(text ? [{ type: 'text' as const, text }] : []),\n ...calls.map(call => ({ type: 'tool-call' as const, toolCallId: call.toolCallId, toolName: AGENT_TOOL, input: call.input ?? {} })),\n ],\n },\n { role: 'tool', content: calls.map(call => ({ type: 'tool-result' as const, toolCallId: call.toolCallId, toolName: AGENT_TOOL, output: { type: 'text' as const, value: retry } })) },\n ]\n }\n\n if (result) {\n const ops: AgentOp[] = result.valid.map((op, i) => ({ ...op, op: `op${nextOp + i}` }))\n const targets = new Set(ops.map(op => agentOpTarget(op)))\n const dropped = [...result.problems]\n for (const problem of retried) {\n if (problem.id !== null && !targets.has(problem.id) && !dropped.some(d => d.id === problem.id && d.type === problem.type)) dropped.push(problem)\n }\n if (!answered && !ops.length) {\n emit('delta', JSON.stringify({ text: 'Ich konnte keine gültigen Änderungen vorschlagen.' }))\n }\n emit('ops', JSON.stringify({ ops, dropped, retries }))\n }\n emit('done', '{}')\n \n }\n catch (error) {\n if (!signal.aborted) {\n const { statusMessage, message } = error as { statusMessage?: string, message?: string }\n emit('error', JSON.stringify({ message: statusMessage ?? message ?? 'Unbekannter Fehler' }))\n }\n }\n })\n}\n\n// The body schemas live with the protocol (the browser-direct adapter validates with them too); the handlers keep exporting them.\nexport { completeBodySchema, generateBodySchema }\n\n/** Short inline continuation (ghost text) of the text before the caret. Plain JSON: `{ text }`, empty when nothing fits. */\nexport async function completePost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { before, model } = await readBody(request, completeBodySchema)\n const languageModel = await ctx.ai.resolveModel(model, user)\n\n // Typing on the client aborts its fetch; stop the upstream request with it.\n const abort = new AbortController()\n request.signal?.addEventListener('abort', () => abort.abort(), { once: true })\n return json({ text: await runCompletion(before, (messages, signal) => ctx.ai.streamChat({ model: languageModel, messages, signal }), abort.signal) })\n}\n\n/** Streams one text task (rewrite, translate, generate, …) as SSE: `delta` (`{ text }`), `done`, `error` (`{ message }`). */\nexport async function generatePost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { task: taskId, model, ...input } = await readBody(request, generateBodySchema)\n // Unknown preset or missing key must fail as a plain JSON error before the SSE response starts.\n const languageModel = await ctx.ai.resolveModel(model, user)\n const task = AI_TASKS[taskId]\n const problem = generateProblem(task, input)\n if (problem) throw new HttpError(400, problem)\n const users = task.needsUsers ? (await port(ctx, 'directory').listUsers()).map(u => u.username) : undefined\n\n return eventStream(request, async (emit, signal) => {\n for await (const { event, data } of runGenerate(task, input, users, (messages, streamSignal) => ctx.ai.streamChat({ model: languageModel, messages, signal: streamSignal }), signal)) {\n emit(event, JSON.stringify(data))\n }\n })\n}\n\n/**\n * The models the client may choose from (without keys) — the `models` port's `listModels` for the user, else the\n * server-defined presets — and the default preset id per model role, for the client's model profiles. Requires an\n * authenticated user through the `identity` port; not rate limited, since it calls no model.\n */\nexport async function modelsGet(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await authenticated(ctx, request)\n return json({ presets: await ctx.ai.listModels(user), roles: ctx.ai.roles })\n}\n\nconst sha256Hex = (data: string | Uint8Array): string => createHash('sha256').update(data).digest('hex')\n\n/** PDF pages read by `pdf-text` and `ocr` unless the request caps them lower. */\nconst DEFAULT_MAX_PAGES = 200\n\n/** Body schema of `extractPost`. */\nexport const extractBodySchema = z.object({\n documentId: z.string().regex(ASSET_DOCUMENT_ID),\n name: z.string().regex(ASSET_NAME).optional(),\n method: z.enum(['auto', 'transcription', 'pdf-text', 'ocr', 'description', 'bibliography']),\n model: modelRefSchema,\n bibliography: z.boolean().optional(),\n doi: z.string().regex(DOI_PATTERN).optional(),\n maxPages: z.number().int().min(1).max(500).default(DEFAULT_MAX_PAGES),\n}).refine(body => body.name !== undefined || (body.method === 'bibliography' && body.doi !== undefined), {\n message: 'name fehlt (nur eine Literaturabfrage mit DOI braucht keine Datei)',\n path: ['name'],\n}) satisfies z.ZodType<ExtractRequest>\n\n/** The method an `ExtractRequest` runs for an asset of `mimeType`: `auto` picks by type; 415 when the type does not fit the method. */\nfunction extractMethodOf(requested: ExtractMethod, mimeType: string, hasDoi: boolean): ExtractResponse['method'] {\n const audioVideo = /^(?:audio|video)\\//.test(mimeType)\n const image = mimeType.startsWith('image/')\n const pdf = mimeType === 'application/pdf'\n const fits: Record<ExtractMethod, boolean> = {\n 'auto': audioVideo || image || pdf,\n 'transcription': audioVideo,\n 'description': image,\n 'pdf-text': pdf,\n 'ocr': pdf,\n // A given DOI needs no document text, so any asset can stand for the work.\n 'bibliography': pdf || hasDoi,\n }\n if (!fits[requested]) throw new HttpError(415, `Dateityp ${mimeType} wird für diese Auswertung nicht unterstützt`)\n if (requested !== 'auto') return requested\n if (audioVideo) return 'transcription'\n return image ? 'description' : 'pdf-text'\n}\n\n/**\n * Turns a stored asset into readable content (`ExtractRequest` → `ExtractResponse`): audio/video into a transcription\n * with segment timestamps, a PDF into its text per page (OCR through the model when it has no text layer), an image\n * into a description, bibliographic data from Crossref or the model. The raw Markdown is stored as an asset of the same\n * document (`output`), with the SHA-256 of input and output. Errors: 404 unknown asset, 415 type not supported, 413\n * oversize audio, 422 unreadable PDF, 501 transcription or OCR unavailable, 502 upstream failure.\n */\nexport async function extractPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const body = await readBody(request, extractBodySchema)\n if (body.name === undefined) {\n // Only a DOI lookup comes without a file (the schema allows nothing else): no asset, no output, empty hashes.\n const { bib, warnings } = await extractBibliography({ doi: body.doi, text: '', resolveModel: () => ctx.ai.resolveModel(body.model, user), signal: request.signal })\n return json({ method: 'bibliography', markdown: '', bib, inputHash: '', outputHash: sha256Hex(''), output: null, warnings } satisfies ExtractResponse)\n }\n const assets = port(ctx, 'assets')\n const asset = await assets.loadAsset(body.documentId, body.name)\n if (!asset) throw new HttpError(404, 'Datei nicht gefunden')\n const inputHash = sha256Hex(asset.data)\n let method = extractMethodOf(body.method, asset.record.mimeType, body.doi !== undefined)\n // Closing the page aborts the client's fetch; the upstream requests stop with it.\n const { signal } = request\n const resolveModel = () => ctx.ai.resolveModel(body.model, user)\n\n const warnings: string[] = []\n let markdown = ''\n let model: string | undefined\n let segments: TranscriptionResult['segments']\n let bib: ExtractResponse['bib']\n\n if (method === 'transcription') {\n const config = ctx.ai.transcription\n if (!config?.endpoint || !config.model) throw new HttpError(501, 'Transkription nicht konfiguriert')\n if (asset.data.byteLength > MAX_TRANSCRIBE_BYTES) throw new HttpError(413, `Datei ist größer als ${MAX_TRANSCRIBE_BYTES / 1024 / 1024} MB`)\n ;({ markdown, segments } = await transcribe(config, asset, signal))\n model = config.model\n }\n else if (method === 'description') {\n markdown = await describeImage(await resolveModel(), asset.data, asset.record.mimeType, signal)\n }\n else if (asset.record.mimeType !== 'application/pdf') {\n // `bibliography` of a non-PDF asset: only reachable with a DOI.\n const found = await extractBibliography({ doi: body.doi, text: '', resolveModel, signal })\n bib = found.bib\n warnings.push(...found.warnings)\n }\n else {\n const pdf = await openPdf(asset.data)\n try {\n if (method === 'bibliography') {\n const pages = await pdfPagesText(pdf, Math.min(pdf.pageCount, BIBLIOGRAPHY_PAGES), signal)\n const found = await extractBibliography({ doi: body.doi, text: bibliographyText(pages), resolveModel, signal })\n bib = found.bib\n warnings.push(...found.warnings)\n }\n else {\n const count = Math.min(pdf.pageCount, body.maxPages)\n if (pdf.pageCount > count) warnings.push('pages-truncated')\n let pages = await pdfPagesText(pdf, count, signal)\n if (body.method === 'auto' && isScanned(pages)) method = 'ocr'\n if (method === 'ocr') {\n // One vision call per page: the OCR reads fewer pages than the text layer would.\n const ocrCount = Math.min(count, MAX_OCR_PAGES)\n try {\n pages = await ocrPages(pdf, ocrCount, resolveModel, signal)\n if (ocrCount < count) warnings.push('ocr-truncated')\n }\n catch (error) {\n if (!(error instanceof RasterError)) throw error\n // Pages cannot be rendered here: keep what the text layer has.\n if (!pages.some(Boolean)) throw new HttpError(501, 'OCR nicht verfügbar')\n warnings.push('ocr-unavailable')\n method = 'pdf-text'\n }\n }\n markdown = joinPages(pages)\n if (body.bibliography) {\n try {\n const found = await extractBibliography({ doi: body.doi, text: bibliographyText(pages), resolveModel, signal })\n bib = found.bib\n warnings.push(...found.warnings)\n }\n catch (error) {\n // The text is the result; missing bibliographic data must not lose it.\n if (signal?.aborted) throw error\n warnings.push('bibliography-failed')\n }\n }\n }\n }\n finally {\n await pdf.close()\n }\n }\n\n const outputHash = sha256Hex(markdown)\n let output: string | null = null\n if (method !== 'bibliography' && markdown) {\n const data = Buffer.from(markdown, 'utf8')\n output = `${randomUUID()}.md`\n await assets.storeAsset(body.documentId, {\n name: output,\n filename: `${asset.record.filename}.${method}.md`.slice(0, 255),\n mimeType: 'text/markdown',\n size: data.byteLength,\n createdAt: new Date().toISOString(),\n sha256: outputHash,\n data,\n })\n }\n return json({ method, markdown, ...(segments && { segments }), ...(bib && { bib }), ...(model && { model }), inputHash, outputHash, output, warnings } satisfies ExtractResponse)\n}\n\n/** Body schema of `transcriptCleanupPost`. */\nexport const transcriptCleanupBodySchema = transcriptCleanupRequestSchema\n\n/**\n * Cleans transcript segments (filler words, punctuation, casing, fragmented sentences) without touching speakers or\n * order: the model only returns a new text per segment ID. Plain JSON: `{ cleaned, failed }`.\n */\nexport async function transcriptCleanupPost(ctx: AiHandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await enforceRateLimit(ctx, request)\n const { segments, model } = await readBody(request, transcriptCleanupBodySchema)\n const languageModel = await ctx.ai.resolveModel(model, user)\n\n // Closing the menu or the page aborts the client's fetch; stop the upstream requests with it.\n const abort = new AbortController()\n request.signal?.addEventListener('abort', () => abort.abort(), { once: true })\n try {\n return json(await runTranscriptCleanup(segments, (messages, signal) => ctx.ai.streamChat({ model: languageModel, messages, signal }), abort.signal))\n }\n catch (error) {\n throw error instanceof TranscriptCleanupUnreadableError ? new HttpError(502, error.message) : error\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,8 @@
1
1
  import { AiModels } from "../ai.js";
2
2
  import { EditorPorts } from "../ports.js";
3
3
  import { CollabServer } from "../collab.js";
4
+ import { BlockType } from "../../features/composites/block-types.js";
5
+ import "../../features/composites/schema.js";
4
6
  import { RateLimiter } from "../rate-limit.js";
5
7
  //#region src/server/handlers/context.d.ts
6
8
  /** What every editor handler needs: the ports of the backend (identity first). */
@@ -18,6 +20,8 @@ export interface HandlerContext {
18
20
  export interface AiHandlerContext extends HandlerContext {
19
21
  ai: AiModels;
20
22
  limiter: RateLimiter;
23
+ /** The Block types of the host's config (`EditorServerOptions.blockTypes`). Given: the chat describes these to the model and ignores the `blockTypes` the client sends. */
24
+ blockTypes?: readonly BlockType[];
21
25
  }
22
26
  /** A request, reduced to what the handlers read: any HTTP server maps its own request type onto it. */
23
27
  export interface HandlerRequest {
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","names":[],"sources":["../../../src/server/handlers/context.ts"],"sourcesContent":["import type { AiModels } from '../ai'\nimport type { EditorPorts } from '../ports'\nimport type { CollabServer } from '../collab'\nimport { HttpError } from '../errors'\nimport type { RateLimiter } from '../rate-limit'\nimport { userKey } from '../rate-limit'\n\n/** What every editor handler needs: the ports of the backend (identity first). */\nexport interface HandlerContext {\n ports: EditorPorts\n /** The collaboration server of the process: documents open in it count for `tagsGet` with their unsaved content. */\n collab?: CollabServer\n /** What the installed server really serves (`capabilities` answers with it); without it, what `ports` imply. */\n served?: { ai?: boolean, collaboration?: boolean }\n}\n\n/** Context of the AI handlers: the configured models and the per-user limit shared by all of them. */\nexport interface AiHandlerContext extends HandlerContext {\n ai: AiModels\n limiter: RateLimiter\n}\n\n/** A request, reduced to what the handlers read: any HTTP server maps its own request type onto it. */\nexport interface HandlerRequest {\n headers: Headers\n /** Route parameters (`id`, `name`). */\n params?: Readonly<Record<string, string | undefined>>\n /** Query string values (`q`). */\n query?: Readonly<Record<string, string | undefined>>\n /** The JSON body. Lazy, so a handler authenticates (401) before it reads or validates anything. Rejects when the body is no JSON. */\n readJson?: () => Promise<unknown>\n /** The multipart body; `null` when it is none. Lazy like `readJson`. */\n readForm?: () => Promise<FormData | null>\n /** Aborts when the client goes away: handlers stop upstream requests with it. */\n signal?: AbortSignal\n}\n\n/** A response, as plain data any HTTP server can send (`new Response(body, { status, headers })` maps it 1:1). */\nexport interface HandlerResponse {\n status: number\n headers: Record<string, string>\n body: string | Uint8Array | ReadableStream<Uint8Array> | null\n}\n\nexport const json = (value: unknown, status = 200): HandlerResponse => ({\n status,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(value),\n})\n\nexport const text = (value: string, contentType: string, headers: Record<string, string> = {}): HandlerResponse => ({\n status: 200,\n headers: { 'content-type': contentType, ...headers },\n body: value,\n})\n\nexport const empty = (status = 204, headers: Record<string, string> = {}): HandlerResponse => ({ status, headers, body: null })\n\n/**\n * Server-sent events. `run` writes events through `emit` and ends the stream by returning; `signal` aborts when the\n * client cancels the response or `request.signal` aborts. An `emit` after that is dropped.\n */\nexport function eventStream(request: HandlerRequest, run: (emit: (event: string, data: string) => void, signal: AbortSignal) => Promise<void>): HandlerResponse {\n const abort = new AbortController()\n if (request.signal?.aborted) abort.abort()\n else request.signal?.addEventListener('abort', () => abort.abort(), { once: true })\n const encoder = new TextEncoder()\n return {\n status: 200,\n headers: { 'content-type': 'text/event-stream', 'cache-control': 'no-cache, no-transform', 'x-accel-buffering': 'no' },\n body: new ReadableStream<Uint8Array>({\n start(controller) {\n const emit = (event: string, data: string) => {\n if (!abort.signal.aborted) controller.enqueue(encoder.encode(`event: ${event}\\ndata: ${data}\\n\\n`))\n }\n void run(emit, abort.signal)\n .catch((error) => { if (!abort.signal.aborted) console.error('[editor-server]', error) })\n .finally(() => { try { controller.close() } catch { /* the client cancelled */ } })\n },\n cancel() { abort.abort() },\n }),\n }\n}\n\n/** The user `ports.identity.authenticate` finds behind the request's headers; 401 without one. */\nexport async function authenticated({ ports }: HandlerContext, request: HandlerRequest): Promise<object> {\n const user = await ports.identity.authenticate({ headers: request.headers })\n if (!user) throw new HttpError(401, 'Nicht angemeldet')\n return user\n}\n\n/** The port `name` of the backend; 501 when the host serves none. The one place absent ports turn into an answer. */\nexport function port<K extends Exclude<keyof EditorPorts, 'identity' | 'documents'>>(ctx: HandlerContext, name: K): NonNullable<EditorPorts[K]> {\n const found = ctx.ports[name]\n if (found === undefined) throw new HttpError(501, `Port \"${name}\" not supported`)\n return found as NonNullable<EditorPorts[K]>\n}\n\n/** Per-user limit for the AI handlers: 401 without a user, 429 with `Retry-After` when exceeded; returns the user, for `resolveModel`. */\nexport async function enforceRateLimit(ctx: AiHandlerContext, request: HandlerRequest): Promise<object> {\n const user = await authenticated(ctx, request)\n const waitMs = ctx.limiter.hit(userKey(user))\n if (!waitMs) return user\n throw new HttpError(429, 'Zu viele KI-Anfragen, bitte kurz warten', { 'retry-after': String(Math.max(1, Math.ceil(waitMs / 1000))) })\n}\n\ninterface BodySchema<T> {\n safeParse(data: unknown): { success: true, data: T } | { success: false, error: { issues: unknown[] } }\n}\n\n/** The request's JSON body validated with `schema`; 400 for malformed JSON and for validation failures (`data`: the issues). */\nexport async function readBody<T>(request: HandlerRequest, schema: BodySchema<T>): Promise<T> {\n let raw: unknown\n try {\n raw = await request.readJson?.()\n }\n catch {\n throw new HttpError(400, 'Ungültiger JSON-Body')\n }\n const parsed = schema.safeParse(raw)\n if (!parsed.success) throw new HttpError(400, 'Validation Error', {}, parsed.error.issues)\n return parsed.data\n}\n"],"mappings":";;;AA4CA,MAAa,QAAQ,OAAgB,SAAS,SAA0B;CACtE;CACA,SAAS,EAAE,gBAAgB,mBAAmB;CAC9C,MAAM,KAAK,UAAU,KAAK;AAC5B;AAEA,MAAa,QAAQ,OAAe,aAAqB,UAAkC,CAAC,OAAwB;CAClH,QAAQ;CACR,SAAS;EAAE,gBAAgB;EAAa,GAAG;CAAQ;CACnD,MAAM;AACR;AAEA,MAAa,SAAS,SAAS,KAAK,UAAkC,CAAC,OAAwB;CAAE;CAAQ;CAAS,MAAM;AAAK;;;;;AAM7H,SAAgB,YAAY,SAAyB,KAA2G;CAC9J,MAAM,QAAQ,IAAI,gBAAgB;CAClC,IAAI,QAAQ,QAAQ,SAAS,MAAM,MAAM;MACpC,QAAQ,QAAQ,iBAAiB,eAAe,MAAM,MAAM,GAAG,EAAE,MAAM,KAAK,CAAC;CAClF,MAAM,UAAU,IAAI,YAAY;CAChC,OAAO;EACL,QAAQ;EACR,SAAS;GAAE,gBAAgB;GAAqB,iBAAiB;GAA0B,qBAAqB;EAAK;EACrH,MAAM,IAAI,eAA2B;GACnC,MAAM,YAAY;IAChB,MAAM,QAAQ,OAAe,SAAiB;KAC5C,IAAI,CAAC,MAAM,OAAO,SAAS,WAAW,QAAQ,QAAQ,OAAO,UAAU,MAAM,UAAU,KAAK,KAAK,CAAC;IACpG;IACA,IAAS,MAAM,MAAM,MAAM,CAAC,CACzB,OAAO,UAAU;KAAE,IAAI,CAAC,MAAM,OAAO,SAAS,QAAQ,MAAM,mBAAmB,KAAK;IAAE,CAAC,CAAC,CACxF,cAAc;KAAE,IAAI;MAAE,WAAW,MAAM;KAAE,QAAQ,CAA6B;IAAE,CAAC;GACtF;GACA,SAAS;IAAE,MAAM,MAAM;GAAE;EAC3B,CAAC;CACH;AACF;;AAGA,eAAsB,cAAc,EAAE,SAAyB,SAA0C;CACvG,MAAM,OAAO,MAAM,MAAM,SAAS,aAAa,EAAE,SAAS,QAAQ,QAAQ,CAAC;CAC3E,IAAI,CAAC,MAAM,MAAM,IAAI,UAAU,KAAK,kBAAkB;CACtD,OAAO;AACT;;AAGA,SAAgB,KAAqE,KAAqB,MAAsC;CAC9I,MAAM,QAAQ,IAAI,MAAM;CACxB,IAAI,UAAU,KAAA,GAAW,MAAM,IAAI,UAAU,KAAK,SAAS,KAAK,gBAAgB;CAChF,OAAO;AACT;;AAGA,eAAsB,iBAAiB,KAAuB,SAA0C;CACtG,MAAM,OAAO,MAAM,cAAc,KAAK,OAAO;CAC7C,MAAM,SAAS,IAAI,QAAQ,IAAI,QAAQ,IAAI,CAAC;CAC5C,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,IAAI,UAAU,KAAK,2CAA2C,EAAE,eAAe,OAAO,KAAK,IAAI,GAAG,KAAK,KAAK,SAAS,GAAI,CAAC,CAAC,EAAE,CAAC;AACtI;;AAOA,eAAsB,SAAY,SAAyB,QAAmC;CAC5F,IAAI;CACJ,IAAI;EACF,MAAM,MAAM,QAAQ,WAAW;CACjC,QACM;EACJ,MAAM,IAAI,UAAU,KAAK,sBAAsB;CACjD;CACA,MAAM,SAAS,OAAO,UAAU,GAAG;CACnC,IAAI,CAAC,OAAO,SAAS,MAAM,IAAI,UAAU,KAAK,oBAAoB,CAAC,GAAG,OAAO,MAAM,MAAM;CACzF,OAAO,OAAO;AAChB"}
1
+ {"version":3,"file":"context.js","names":[],"sources":["../../../src/server/handlers/context.ts"],"sourcesContent":["import type { BlockType } from '../../features/composites/schema'\nimport type { AiModels } from '../ai'\nimport type { EditorPorts } from '../ports'\nimport type { CollabServer } from '../collab'\nimport { HttpError } from '../errors'\nimport type { RateLimiter } from '../rate-limit'\nimport { userKey } from '../rate-limit'\n\n/** What every editor handler needs: the ports of the backend (identity first). */\nexport interface HandlerContext {\n ports: EditorPorts\n /** The collaboration server of the process: documents open in it count for `tagsGet` with their unsaved content. */\n collab?: CollabServer\n /** What the installed server really serves (`capabilities` answers with it); without it, what `ports` imply. */\n served?: { ai?: boolean, collaboration?: boolean }\n}\n\n/** Context of the AI handlers: the configured models and the per-user limit shared by all of them. */\nexport interface AiHandlerContext extends HandlerContext {\n ai: AiModels\n limiter: RateLimiter\n /** The Block types of the host's config (`EditorServerOptions.blockTypes`). Given: the chat describes these to the model and ignores the `blockTypes` the client sends. */\n blockTypes?: readonly BlockType[]\n}\n\n/** A request, reduced to what the handlers read: any HTTP server maps its own request type onto it. */\nexport interface HandlerRequest {\n headers: Headers\n /** Route parameters (`id`, `name`). */\n params?: Readonly<Record<string, string | undefined>>\n /** Query string values (`q`). */\n query?: Readonly<Record<string, string | undefined>>\n /** The JSON body. Lazy, so a handler authenticates (401) before it reads or validates anything. Rejects when the body is no JSON. */\n readJson?: () => Promise<unknown>\n /** The multipart body; `null` when it is none. Lazy like `readJson`. */\n readForm?: () => Promise<FormData | null>\n /** Aborts when the client goes away: handlers stop upstream requests with it. */\n signal?: AbortSignal\n}\n\n/** A response, as plain data any HTTP server can send (`new Response(body, { status, headers })` maps it 1:1). */\nexport interface HandlerResponse {\n status: number\n headers: Record<string, string>\n body: string | Uint8Array | ReadableStream<Uint8Array> | null\n}\n\nexport const json = (value: unknown, status = 200): HandlerResponse => ({\n status,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(value),\n})\n\nexport const text = (value: string, contentType: string, headers: Record<string, string> = {}): HandlerResponse => ({\n status: 200,\n headers: { 'content-type': contentType, ...headers },\n body: value,\n})\n\nexport const empty = (status = 204, headers: Record<string, string> = {}): HandlerResponse => ({ status, headers, body: null })\n\n/**\n * Server-sent events. `run` writes events through `emit` and ends the stream by returning; `signal` aborts when the\n * client cancels the response or `request.signal` aborts. An `emit` after that is dropped.\n */\nexport function eventStream(request: HandlerRequest, run: (emit: (event: string, data: string) => void, signal: AbortSignal) => Promise<void>): HandlerResponse {\n const abort = new AbortController()\n if (request.signal?.aborted) abort.abort()\n else request.signal?.addEventListener('abort', () => abort.abort(), { once: true })\n const encoder = new TextEncoder()\n return {\n status: 200,\n headers: { 'content-type': 'text/event-stream', 'cache-control': 'no-cache, no-transform', 'x-accel-buffering': 'no' },\n body: new ReadableStream<Uint8Array>({\n start(controller) {\n const emit = (event: string, data: string) => {\n if (!abort.signal.aborted) controller.enqueue(encoder.encode(`event: ${event}\\ndata: ${data}\\n\\n`))\n }\n void run(emit, abort.signal)\n .catch((error) => { if (!abort.signal.aborted) console.error('[editor-server]', error) })\n .finally(() => { try { controller.close() } catch { /* the client cancelled */ } })\n },\n cancel() { abort.abort() },\n }),\n }\n}\n\n/** The user `ports.identity.authenticate` finds behind the request's headers; 401 without one. */\nexport async function authenticated({ ports }: HandlerContext, request: HandlerRequest): Promise<object> {\n const user = await ports.identity.authenticate({ headers: request.headers })\n if (!user) throw new HttpError(401, 'Nicht angemeldet')\n return user\n}\n\n/** The port `name` of the backend; 501 when the host serves none. The one place absent ports turn into an answer. */\nexport function port<K extends Exclude<keyof EditorPorts, 'identity' | 'documents'>>(ctx: HandlerContext, name: K): NonNullable<EditorPorts[K]> {\n const found = ctx.ports[name]\n if (found === undefined) throw new HttpError(501, `Port \"${name}\" not supported`)\n return found as NonNullable<EditorPorts[K]>\n}\n\n/** Per-user limit for the AI handlers: 401 without a user, 429 with `Retry-After` when exceeded; returns the user, for `resolveModel`. */\nexport async function enforceRateLimit(ctx: AiHandlerContext, request: HandlerRequest): Promise<object> {\n const user = await authenticated(ctx, request)\n const waitMs = ctx.limiter.hit(userKey(user))\n if (!waitMs) return user\n throw new HttpError(429, 'Zu viele KI-Anfragen, bitte kurz warten', { 'retry-after': String(Math.max(1, Math.ceil(waitMs / 1000))) })\n}\n\ninterface BodySchema<T> {\n safeParse(data: unknown): { success: true, data: T } | { success: false, error: { issues: unknown[] } }\n}\n\n/** The request's JSON body validated with `schema`; 400 for malformed JSON and for validation failures (`data`: the issues). */\nexport async function readBody<T>(request: HandlerRequest, schema: BodySchema<T>): Promise<T> {\n let raw: unknown\n try {\n raw = await request.readJson?.()\n }\n catch {\n throw new HttpError(400, 'Ungültiger JSON-Body')\n }\n const parsed = schema.safeParse(raw)\n if (!parsed.success) throw new HttpError(400, 'Validation Error', {}, parsed.error.issues)\n return parsed.data\n}\n"],"mappings":";;;AA+CA,MAAa,QAAQ,OAAgB,SAAS,SAA0B;CACtE;CACA,SAAS,EAAE,gBAAgB,mBAAmB;CAC9C,MAAM,KAAK,UAAU,KAAK;AAC5B;AAEA,MAAa,QAAQ,OAAe,aAAqB,UAAkC,CAAC,OAAwB;CAClH,QAAQ;CACR,SAAS;EAAE,gBAAgB;EAAa,GAAG;CAAQ;CACnD,MAAM;AACR;AAEA,MAAa,SAAS,SAAS,KAAK,UAAkC,CAAC,OAAwB;CAAE;CAAQ;CAAS,MAAM;AAAK;;;;;AAM7H,SAAgB,YAAY,SAAyB,KAA2G;CAC9J,MAAM,QAAQ,IAAI,gBAAgB;CAClC,IAAI,QAAQ,QAAQ,SAAS,MAAM,MAAM;MACpC,QAAQ,QAAQ,iBAAiB,eAAe,MAAM,MAAM,GAAG,EAAE,MAAM,KAAK,CAAC;CAClF,MAAM,UAAU,IAAI,YAAY;CAChC,OAAO;EACL,QAAQ;EACR,SAAS;GAAE,gBAAgB;GAAqB,iBAAiB;GAA0B,qBAAqB;EAAK;EACrH,MAAM,IAAI,eAA2B;GACnC,MAAM,YAAY;IAChB,MAAM,QAAQ,OAAe,SAAiB;KAC5C,IAAI,CAAC,MAAM,OAAO,SAAS,WAAW,QAAQ,QAAQ,OAAO,UAAU,MAAM,UAAU,KAAK,KAAK,CAAC;IACpG;IACA,IAAS,MAAM,MAAM,MAAM,CAAC,CACzB,OAAO,UAAU;KAAE,IAAI,CAAC,MAAM,OAAO,SAAS,QAAQ,MAAM,mBAAmB,KAAK;IAAE,CAAC,CAAC,CACxF,cAAc;KAAE,IAAI;MAAE,WAAW,MAAM;KAAE,QAAQ,CAA6B;IAAE,CAAC;GACtF;GACA,SAAS;IAAE,MAAM,MAAM;GAAE;EAC3B,CAAC;CACH;AACF;;AAGA,eAAsB,cAAc,EAAE,SAAyB,SAA0C;CACvG,MAAM,OAAO,MAAM,MAAM,SAAS,aAAa,EAAE,SAAS,QAAQ,QAAQ,CAAC;CAC3E,IAAI,CAAC,MAAM,MAAM,IAAI,UAAU,KAAK,kBAAkB;CACtD,OAAO;AACT;;AAGA,SAAgB,KAAqE,KAAqB,MAAsC;CAC9I,MAAM,QAAQ,IAAI,MAAM;CACxB,IAAI,UAAU,KAAA,GAAW,MAAM,IAAI,UAAU,KAAK,SAAS,KAAK,gBAAgB;CAChF,OAAO;AACT;;AAGA,eAAsB,iBAAiB,KAAuB,SAA0C;CACtG,MAAM,OAAO,MAAM,cAAc,KAAK,OAAO;CAC7C,MAAM,SAAS,IAAI,QAAQ,IAAI,QAAQ,IAAI,CAAC;CAC5C,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,IAAI,UAAU,KAAK,2CAA2C,EAAE,eAAe,OAAO,KAAK,IAAI,GAAG,KAAK,KAAK,SAAS,GAAI,CAAC,CAAC,EAAE,CAAC;AACtI;;AAOA,eAAsB,SAAY,SAAyB,QAAmC;CAC5F,IAAI;CACJ,IAAI;EACF,MAAM,MAAM,QAAQ,WAAW;CACjC,QACM;EACJ,MAAM,IAAI,UAAU,KAAK,sBAAsB;CACjD;CACA,MAAM,SAAS,OAAO,UAAU,GAAG;CACnC,IAAI,CAAC,OAAO,SAAS,MAAM,IAAI,UAAU,KAAK,oBAAoB,CAAC,GAAG,OAAO,MAAM,MAAM;CACzF,OAAO,OAAO;AAChB"}
@@ -1,4 +1,5 @@
1
1
  import { HandlerContext, HandlerRequest, HandlerResponse } from "./context.js";
2
+ import { z } from "zod";
2
3
  //#region src/server/handlers/editor.d.ts
3
4
  /**
4
5
  * The capabilities this server serves (`BackendCapability[]`, see `serverCapabilities`): what the client's probe
@@ -6,68 +7,68 @@ import { HandlerContext, HandlerRequest, HandlerResponse } from "./context.js";
6
7
  * routes whose capabilities it lists.
7
8
  */
8
9
  export declare function capabilitiesGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse>;
9
- /** Body schema of `settingsPut`: the complete editor settings. */
10
- export declare const settingsPutBodySchema: import("zod").ZodObject<{
11
- version: import("zod").ZodLiteral<1>;
12
- editing: import("zod").ZodObject<{
13
- headingConversionScope: import("zod").ZodEnum<{
10
+ /** Body schema of `settingsPut`: the complete editor settings; a body of an older version (a tab from before the rename) is migrated first. */
11
+ export declare const settingsPutBodySchema: z.ZodPreprocess<z.ZodObject<{
12
+ version: z.ZodLiteral<2>;
13
+ editing: z.ZodObject<{
14
+ headingConversionScope: z.ZodEnum<{
14
15
  container: "container";
15
16
  document: "document";
16
17
  heading: "heading";
17
18
  }>;
18
- authorColors: import("zod").ZodBoolean;
19
- authorColorStyle: import("zod").ZodEnum<{
19
+ authorColors: z.ZodBoolean;
20
+ authorColorStyle: z.ZodEnum<{
20
21
  text: "text";
21
22
  background: "background";
22
23
  }>;
23
- commentsLayout: import("zod").ZodEnum<{
24
+ commentsLayout: z.ZodEnum<{
24
25
  margin: "margin";
25
26
  inline: "inline";
26
27
  }>;
27
- }, import("zod/v4/core").$strip>;
28
- ai: import("zod").ZodObject<{
29
- autocomplete: import("zod").ZodBoolean;
30
- slots: import("zod").ZodRecord<import("zod").ZodEnum<{
28
+ }, z.core.$strip>;
29
+ ai: z.ZodObject<{
30
+ autocomplete: z.ZodBoolean;
31
+ roles: z.ZodRecord<z.ZodEnum<{
31
32
  complete: "complete";
32
33
  rewrite: "rewrite";
33
34
  generate: "generate";
34
- }> & import("zod/v4/core").$partial, import("zod").ZodString>;
35
- translateLanguage: import("zod").ZodEnum<{
35
+ }> & z.core.$partial, z.ZodString>;
36
+ translateLanguage: z.ZodEnum<{
36
37
  Deutsch: "Deutsch";
37
38
  Englisch: "Englisch";
38
39
  Französisch: "Französisch";
39
40
  Spanisch: "Spanisch";
40
41
  Italienisch: "Italienisch";
41
42
  }>;
42
- }, import("zod/v4/core").$strip>;
43
- blocks: import("zod").ZodObject<{
44
- defaultKind: import("zod").ZodString;
45
- structure: import("zod").ZodEnum<{
43
+ }, z.core.$strip>;
44
+ blocks: z.ZodObject<{
45
+ defaultBlockType: z.ZodString;
46
+ structure: z.ZodEnum<{
46
47
  flat: "flat";
47
- composite: "composite";
48
+ structured: "structured";
48
49
  }>;
49
- treeRoot: import("zod").ZodEnum<{
50
+ treeRoot: z.ZodEnum<{
50
51
  file: "file";
51
52
  folders: "folders";
52
53
  }>;
53
- kindIcon: import("zod").ZodEnum<{
54
+ blockTypeIcon: z.ZodEnum<{
54
55
  always: "always";
55
56
  hover: "hover";
56
57
  }>;
57
- dragHandle: import("zod").ZodEnum<{
58
+ dragHandle: z.ZodEnum<{
58
59
  always: "always";
59
60
  hover: "hover";
60
61
  }>;
61
- splitTargetChars: import("zod").ZodNumber;
62
- }, import("zod/v4/core").$strip>;
63
- appearance: import("zod").ZodObject<{
64
- theme: import("zod").ZodNullable<import("zod").ZodString>;
65
- }, import("zod/v4/core").$strip>;
66
- }, import("zod/v4/core").$strip>;
62
+ splitTargetChars: z.ZodNumber;
63
+ }, z.core.$strip>;
64
+ appearance: z.ZodObject<{
65
+ theme: z.ZodNullable<z.ZodString>;
66
+ }, z.core.$strip>;
67
+ }, z.core.$strip>, unknown>;
67
68
  /**
68
- * The authenticated user's editor settings, normalized with `parseEditorSettings`. `204` without a `settings` port (the
69
- * client stays on localStorage); `200` with JSON `null` when the user has none stored yet (the client uploads its local
70
- * ones).
69
+ * The authenticated user's editor settings, normalized with `parseEditorSettings`. A stored record older than
70
+ * `EDITOR_SETTINGS_VERSION` is written back migrated, once. `204` without a `settings` port (the client stays on
71
+ * localStorage); `200` with JSON `null` when the user has none stored yet (the client uploads its local ones).
71
72
  */
72
73
  export declare function settingsGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse>;
73
74
  /** Stores the authenticated user's editor settings (complete, validated: 400 otherwise); `501` without a `settings` port. */
@@ -75,7 +76,7 @@ export declare function settingsPut(ctx: HandlerContext, request: HandlerRequest
75
76
  /**
76
77
  * All `#` tag names used in any document, deduplicated and sorted. Documents currently open in the collaboration server
77
78
  * are read from their live Y.Doc (a tag added in document A shows up in document B before the debounced store); all
78
- * others from the stored content of `ports.documents`.
79
+ * others from the stored content of `ports.documents`, with its old persisted names migrated first (a stored document may predate the rename of the tags row).
79
80
  */
80
81
  export declare function listWorkspaceTags({ ports, collab }: HandlerContext): Promise<string[]>;
81
82
  /**
@@ -1,3 +1,5 @@
1
+ import { migratePersistedNames } from "../../core/schema/persisted-names.js";
2
+ import { isOutdatedEditorSettings, migrateEditorSettings } from "../../settings/migrate.js";
1
3
  import { THEME_ID, editorSettingsSchema, parseEditorSettings } from "../../settings/index.js";
2
4
  import { HttpError } from "../errors.js";
3
5
  import { ASSET_DOCUMENT_ID, ASSET_NAME, assetUrl } from "../../features/media/assets.js";
@@ -7,6 +9,7 @@ import { COLLAB_FIELD } from "../collab.js";
7
9
  import { authenticated, empty, json, port, readBody, text } from "./context.js";
8
10
  import { serverCapabilities } from "../ports.js";
9
11
  import { MAX_THEME_BYTES, themeManifestSchema } from "../../features/themes/schema.js";
12
+ import { z } from "zod";
10
13
  import { createHash } from "node:crypto";
11
14
  import { yDocToProsemirrorJSON } from "@tiptap/y-tiptap";
12
15
  //#region src/server/handlers/editor.ts
@@ -19,21 +22,23 @@ async function capabilitiesGet(ctx, request) {
19
22
  await authenticated(ctx, request);
20
23
  return json(serverCapabilities(ctx.ports, ctx.served));
21
24
  }
22
- /** Body schema of `settingsPut`: the complete editor settings. */
23
- const settingsPutBodySchema = editorSettingsSchema;
25
+ /** Body schema of `settingsPut`: the complete editor settings; a body of an older version (a tab from before the rename) is migrated first. */
26
+ const settingsPutBodySchema = z.preprocess(migrateEditorSettings, editorSettingsSchema);
24
27
  const MAX_USERS = 20;
25
28
  const MAX_TAGS = 20;
26
29
  /**
27
- * The authenticated user's editor settings, normalized with `parseEditorSettings`. `204` without a `settings` port (the
28
- * client stays on localStorage); `200` with JSON `null` when the user has none stored yet (the client uploads its local
29
- * ones).
30
+ * The authenticated user's editor settings, normalized with `parseEditorSettings`. A stored record older than
31
+ * `EDITOR_SETTINGS_VERSION` is written back migrated, once. `204` without a `settings` port (the client stays on
32
+ * localStorage); `200` with JSON `null` when the user has none stored yet (the client uploads its local ones).
30
33
  */
31
34
  async function settingsGet(ctx, request) {
32
35
  const user = await authenticated(ctx, request);
33
36
  if (!ctx.ports.settings) return empty();
34
37
  const stored = await ctx.ports.settings.loadSettings(user);
35
38
  if (stored === null || stored === void 0) return json(null);
36
- return json(parseEditorSettings(stored));
39
+ const settings = parseEditorSettings(stored);
40
+ if (isOutdatedEditorSettings(stored)) await ctx.ports.settings.storeSettings(user, settings);
41
+ return json(settings);
37
42
  }
38
43
  /** Stores the authenticated user's editor settings (complete, validated: 400 otherwise); `501` without a `settings` port. */
39
44
  async function settingsPut(ctx, request) {
@@ -46,13 +51,13 @@ async function settingsPut(ctx, request) {
46
51
  /**
47
52
  * All `#` tag names used in any document, deduplicated and sorted. Documents currently open in the collaboration server
48
53
  * are read from their live Y.Doc (a tag added in document A shows up in document B before the debounced store); all
49
- * others from the stored content of `ports.documents`.
54
+ * others from the stored content of `ports.documents`, with its old persisted names migrated first (a stored document may predate the rename of the tags row).
50
55
  */
51
56
  async function listWorkspaceTags({ ports, collab }) {
52
57
  const names = /* @__PURE__ */ new Set();
53
58
  const open = collab?.hocuspocus.documents;
54
59
  if (open) for (const doc of open.values()) collectTagNames(yDocToProsemirrorJSON(doc, COLLAB_FIELD), names);
55
- for (const { id, content } of await ports.documents.listContents()) if (!open?.has(id)) collectTagNames(content, names);
60
+ for (const { id, content } of await ports.documents.listContents()) if (!open?.has(id)) collectTagNames(migratePersistedNames(content, { earliest: true }), names);
56
61
  return [...names].sort((a, b) => a.localeCompare(b, "de"));
57
62
  }
58
63
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"editor.js","names":[],"sources":["../../../src/server/handlers/editor.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\nimport { yDocToProsemirrorJSON } from '@tiptap/y-tiptap'\nimport { ASSET_DOCUMENT_ID, ASSET_NAME, assetUrl, MAX_MEDIA_BYTES, MEDIA_TYPES, mediaKindOf, mediaMime } from '../../features/media/schema'\nimport { collectTagNames, normalizeTagName } from '../../features/mentions/schema'\nimport { MAX_THEME_BYTES, THEME_ID, themeManifestSchema, type ThemeManifest, type ThemeRecord } from '../../features/themes/schema'\nimport { editorSettingsSchema, parseEditorSettings } from '../../settings'\nimport { COLLAB_FIELD } from '../collab'\nimport { HttpError } from '../errors'\nimport { serverCapabilities } from '../ports'\nimport { authenticated, empty, type HandlerContext, type HandlerRequest, type HandlerResponse, json, port, readBody, text } from './context'\n\n/**\n * The capabilities this server serves (`BackendCapability[]`, see `serverCapabilities`): what the client's probe\n * (`createHttpBackend({ probe: true })`) prunes its backend to. Requires an authenticated user through the `identity` port, like the\n * routes whose capabilities it lists.\n */\nexport async function capabilitiesGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n return json(serverCapabilities(ctx.ports, ctx.served))\n}\n\n/** Body schema of `settingsPut`: the complete editor settings. */\nexport const settingsPutBodySchema = editorSettingsSchema\n\nconst MAX_USERS = 20\nconst MAX_TAGS = 20\n\n/**\n * The authenticated user's editor settings, normalized with `parseEditorSettings`. `204` without a `settings` port (the\n * client stays on localStorage); `200` with JSON `null` when the user has none stored yet (the client uploads its local\n * ones).\n */\nexport async function settingsGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await authenticated(ctx, request)\n if (!ctx.ports.settings) return empty()\n const stored = await ctx.ports.settings.loadSettings(user)\n if (stored === null || stored === undefined) return json(null)\n return json(parseEditorSettings(stored))\n}\n\n/** Stores the authenticated user's editor settings (complete, validated: 400 otherwise); `501` without a `settings` port. */\nexport async function settingsPut(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await authenticated(ctx, request)\n const store = port(ctx, 'settings')\n const settings = await readBody(request, settingsPutBodySchema)\n await store.storeSettings(user, settings)\n return empty()\n}\n\n/**\n * All `#` tag names used in any document, deduplicated and sorted. Documents currently open in the collaboration server\n * are read from their live Y.Doc (a tag added in document A shows up in document B before the debounced store); all\n * others from the stored content of `ports.documents`.\n */\nexport async function listWorkspaceTags({ ports, collab }: HandlerContext): Promise<string[]> {\n const names = new Set<string>()\n const open = collab?.hocuspocus.documents\n if (open) for (const doc of open.values()) collectTagNames(yDocToProsemirrorJSON(doc, COLLAB_FIELD), names)\n for (const { id, content } of await ports.documents.listContents()) {\n if (!open?.has(id)) collectTagNames(content, names)\n }\n return [...names].sort((a, b) => a.localeCompare(b, 'de'))\n}\n\n/**\n * Tag names for the `#` popup: tags used in any document, filtered by the normalised `?q=` (prefix matches first, then\n * substrings). Requires an authenticated user through the `identity` port.\n */\nexport async function tagsGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const q = normalizeTagName(String(request.query?.q ?? ''))\n const matches = (await listWorkspaceTags(ctx)).filter(tag => tag.includes(q))\n const prefixMatches = matches.filter(tag => tag.startsWith(q))\n return json([...prefixMatches, ...matches.filter(tag => !tag.startsWith(q))].slice(0, MAX_TAGS))\n}\n\n/**\n * Users for the `@` mention popup, filtered case-insensitively by `?q=` (prefix matches first, then substrings).\n * Requires an authenticated user through the `identity` port.\n */\nexport async function usersGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const q = String(request.query?.q ?? '').trim().toLowerCase()\n // Re-pick the fields so a directory returning richer user records cannot leak them.\n const matches = (await port(ctx, 'directory').listUsers())\n .map(({ id, username, color }) => ({ id, username, color }))\n .filter(u => u.username.toLowerCase().includes(q))\n .sort((a, b) => a.username.localeCompare(b.username, 'de'))\n const prefixMatches = matches.filter(u => u.username.toLowerCase().startsWith(q))\n return json([...prefixMatches, ...matches.filter(u => !prefixMatches.includes(u))].slice(0, MAX_USERS))\n}\n\n/**\n * Stores a file (multipart field `file`) in the package of document `params.id` (or `scratch` for editors not bound to\n * a stored document); answers `{ url, name, sha256 }`. Requires an authenticated user through the `identity` port.\n */\nexport async function assetPost(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const id = request.params?.id ?? ''\n if (!ASSET_DOCUMENT_ID.test(id)) throw new HttpError(400, 'Ungültige Dokument-ID')\n // A body that is no multipart form at all counts as „no file“.\n const form = await request.readForm?.().catch(() => null)\n const file = form?.get('file')\n if (!(file instanceof File)) throw new HttpError(400, 'Keine Datei übermittelt')\n const mime = mediaMime(file.type) || 'application/octet-stream'\n const maxBytes = MAX_MEDIA_BYTES[mediaKindOf(mime) ?? 'file']\n if (file.size > maxBytes) throw new HttpError(413, `Datei ist größer als ${maxBytes / 1024 / 1024} MB`)\n const nameExt = file.name.includes('.') ? file.name.split('.').pop()?.toLowerCase() : undefined\n const ext = MEDIA_TYPES[mime]?.ext ?? (nameExt && /^[a-z0-9]{1,8}$/.test(nameExt) ? nameExt : 'bin')\n const name = `${globalThis.crypto.randomUUID()}.${ext}`\n const data = Buffer.from(await file.arrayBuffer())\n const sha256 = createHash('sha256').update(data).digest('hex')\n await port(ctx, 'assets').storeAsset(id, {\n name,\n filename: file.name.slice(0, 255) || name,\n mimeType: mime,\n size: file.size,\n createdAt: new Date().toISOString(),\n sha256,\n data,\n })\n return json({ url: assetUrl(id, name), name, sha256 })\n}\n\n// Answers single byte ranges (`Range: bytes=a-b`, `bytes=a-`, `bytes=-n`): Safari only plays and seeks audio/video\n// served with range support. Other Range forms (several ranges) get the whole file, which RFC 9110 allows.\nconst BYTE_RANGE = /^bytes=(\\d*)-(\\d*)$/\n\n/**\n * The stored file `params.name` of document `params.id`, with `Range` support (media elements send the session cookie\n * like any same-origin request). Requires an authenticated user through the `identity` port.\n */\nexport async function assetGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const id = request.params?.id ?? ''\n const name = request.params?.name ?? ''\n if (!ASSET_DOCUMENT_ID.test(id) || !ASSET_NAME.test(name)) throw new HttpError(404, 'Datei nicht gefunden')\n const asset = await port(ctx, 'assets').loadAsset(id, name)\n if (!asset) throw new HttpError(404, 'Datei nicht gefunden')\n const { record, data } = asset\n const total = data.length\n const disposition = record.mimeType === 'application/octet-stream' ? 'attachment' : 'inline'\n const headers: Record<string, string> = {\n 'accept-ranges': 'bytes',\n 'cache-control': 'private, max-age=31536000, immutable',\n 'content-type': record.mimeType,\n 'content-disposition': `${disposition}; filename*=UTF-8''${encodeURIComponent(record.filename)}`,\n }\n const range = BYTE_RANGE.exec(request.headers.get('range') ?? '')\n if (!range) return { status: 200, headers: { ...headers, 'content-length': String(total) }, body: data }\n const [, first, last] = range\n const start = first ? Number(first) : Math.max(0, total - Number(last))\n const end = first && last ? Math.min(Number(last), total - 1) : total - 1\n if (start >= total || start > end) return empty(416, { 'content-range': `bytes */${total}` })\n return {\n status: 206,\n headers: { ...headers, 'content-range': `bytes ${start}-${end}/${total}`, 'content-length': String(end - start + 1) },\n body: data.subarray(start, end + 1),\n }\n}\n\n/** The theme library (builtins and uploads) as `ThemeRecord[]`. `501` without a `themes` port. */\nexport async function themesGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n return json(await port(ctx, 'themes').listThemes())\n}\n\n/** `theme.css` of theme `params.id` as `text/css`. `400` for a malformed id, `404` for an unknown one, `501` without a `themes` port. */\nexport async function themeGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const themes = port(ctx, 'themes')\n const id = request.params?.id ?? ''\n if (!THEME_ID.test(id)) throw new HttpError(400, 'Ungültige Theme-ID')\n const theme = await themes.loadTheme(id)\n if (!theme) throw new HttpError(404, 'Theme nicht gefunden')\n const etag = `\"${theme.record.version ?? '0'}-${new TextEncoder().encode(theme.css).length}\"`\n const headers = { 'cache-control': 'private, max-age=0, must-revalidate', 'etag': etag }\n if (request.headers.get('if-none-match') === etag) return empty(304, headers)\n return text(theme.css, 'text/css; charset=utf-8', headers)\n}\n\n/** Deletes the uploaded theme `params.id` (`204`). `400` for a malformed id, `404` for an unknown one, `403` for a builtin, `501` without a `themes` port. */\nexport async function themeDelete(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const themes = port(ctx, 'themes')\n const id = request.params?.id ?? ''\n if (!THEME_ID.test(id)) throw new HttpError(400, 'Ungültige Theme-ID')\n // `removeTheme` answers `false` for builtins and unknown ids alike, so the record decides between 403 and 404.\n const record = (await themes.listThemes()).find(t => t.id === id)\n if (!record) throw new HttpError(404, 'Theme nicht gefunden')\n if (record.builtin) throw new HttpError(403, 'Mitgelieferte Themes können nicht gelöscht werden')\n if (!await themes.removeTheme(id)) throw new HttpError(404, 'Theme nicht gefunden')\n return empty()\n}\n\nconst MAX_MANIFEST_BYTES = 64 * 1024\nconst MAX_ID_LENGTH = 64\n// Markers of an HTML/XML/SVG document in the head of a „stylesheet“.\nconst MARKUP_HEAD = /<script|<\\?xml/i\n\n/** `[^a-z0-9]+` → `-`, trimmed, at most `max` characters; `theme` when nothing is left. */\nfunction slugOf(name: string, max: number): string {\n const slug = name.normalize('NFKD').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+/, '').slice(0, max).replace(/-+$/, '')\n return slug || 'theme'\n}\n\n/**\n * Uploads an Obsidian theme into the shared library: multipart `css` (File, required), `manifest` (File) and `name`\n * (string) optional. Answers 201 with the new `ThemeRecord`; the id is the slug of the name, made unique with `-2`,\n * `-3` … against the library.\n */\nexport async function themePost(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const themes = port(ctx, 'themes')\n // A body that is no multipart form counts as „no file“.\n const form = await request.readForm?.().catch(() => null)\n const cssFile = form?.get('css')\n if (!(cssFile instanceof File) || cssFile.size === 0) throw new HttpError(400, 'Keine CSS-Datei übermittelt')\n if (cssFile.size > MAX_THEME_BYTES) throw new HttpError(413, `Theme ist größer als ${MAX_THEME_BYTES / 1024 / 1024} MB`)\n const bytes = new Uint8Array(await cssFile.arrayBuffer())\n if (bytes.includes(0) || MARKUP_HEAD.test(new TextDecoder('latin1').decode(bytes.subarray(0, 512)))) {\n throw new HttpError(400, 'Theme ist keine CSS-Textdatei')\n }\n let css: string\n try {\n css = new TextDecoder('utf-8', { fatal: true }).decode(bytes)\n }\n catch {\n throw new HttpError(400, 'Theme ist keine gültige UTF-8-Textdatei')\n }\n\n let manifest: ThemeManifest | undefined\n const manifestFile = form?.get('manifest')\n if (manifestFile instanceof File && manifestFile.size > 0) {\n try {\n if (manifestFile.size > MAX_MANIFEST_BYTES) throw new Error('manifest too large')\n manifest = themeManifestSchema.parse(JSON.parse(await manifestFile.text()))\n }\n catch {\n throw new HttpError(400, 'Ungültiges manifest.json')\n }\n }\n\n const nameField = form?.get('name')\n const cssBaseName = cssFile.name.replace(/\\.css$/i, '')\n const name = manifest?.name\n ?? [typeof nameField === 'string' ? nameField : '', cssBaseName].map(n => n.trim().slice(0, 120)).find(Boolean)\n ?? 'Theme'\n\n const taken = new Set((await themes.listThemes()).map(t => t.id))\n const base = slugOf(name, MAX_ID_LENGTH)\n let id = base\n for (let n = 2; taken.has(id); n++) {\n const suffix = `-${n}`\n id = `${slugOf(name, MAX_ID_LENGTH - suffix.length)}${suffix}`\n }\n\n const record: Omit<ThemeRecord, 'builtin'> = { ...manifest, name, id }\n await themes.storeTheme(record, css)\n return json({ ...record, builtin: false } satisfies ThemeRecord, 201)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"editor.js","names":[],"sources":["../../../src/server/handlers/editor.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\nimport { yDocToProsemirrorJSON } from '@tiptap/y-tiptap'\nimport { z } from 'zod'\nimport { migratePersistedNames } from '../../core/schema'\nimport { ASSET_DOCUMENT_ID, ASSET_NAME, assetUrl, MAX_MEDIA_BYTES, MEDIA_TYPES, mediaKindOf, mediaMime } from '../../features/media/schema'\nimport { collectTagNames, normalizeTagName } from '../../features/mentions/schema'\nimport { MAX_THEME_BYTES, THEME_ID, themeManifestSchema, type ThemeManifest, type ThemeRecord } from '../../features/themes/schema'\nimport { editorSettingsSchema, isOutdatedEditorSettings, migrateEditorSettings, parseEditorSettings } from '../../settings'\nimport { COLLAB_FIELD } from '../collab'\nimport { HttpError } from '../errors'\nimport { serverCapabilities } from '../ports'\nimport { authenticated, empty, type HandlerContext, type HandlerRequest, type HandlerResponse, json, port, readBody, text } from './context'\n\n/**\n * The capabilities this server serves (`BackendCapability[]`, see `serverCapabilities`): what the client's probe\n * (`createHttpBackend({ probe: true })`) prunes its backend to. Requires an authenticated user through the `identity` port, like the\n * routes whose capabilities it lists.\n */\nexport async function capabilitiesGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n return json(serverCapabilities(ctx.ports, ctx.served))\n}\n\n/** Body schema of `settingsPut`: the complete editor settings; a body of an older version (a tab from before the rename) is migrated first. */\nexport const settingsPutBodySchema = z.preprocess(migrateEditorSettings, editorSettingsSchema)\n\nconst MAX_USERS = 20\nconst MAX_TAGS = 20\n\n/**\n * The authenticated user's editor settings, normalized with `parseEditorSettings`. A stored record older than\n * `EDITOR_SETTINGS_VERSION` is written back migrated, once. `204` without a `settings` port (the client stays on\n * localStorage); `200` with JSON `null` when the user has none stored yet (the client uploads its local ones).\n */\nexport async function settingsGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await authenticated(ctx, request)\n if (!ctx.ports.settings) return empty()\n const stored = await ctx.ports.settings.loadSettings(user)\n if (stored === null || stored === undefined) return json(null)\n const settings = parseEditorSettings(stored)\n if (isOutdatedEditorSettings(stored)) await ctx.ports.settings.storeSettings(user, settings)\n return json(settings)\n}\n\n/** Stores the authenticated user's editor settings (complete, validated: 400 otherwise); `501` without a `settings` port. */\nexport async function settingsPut(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n const user = await authenticated(ctx, request)\n const store = port(ctx, 'settings')\n const settings = await readBody(request, settingsPutBodySchema)\n await store.storeSettings(user, settings)\n return empty()\n}\n\n/**\n * All `#` tag names used in any document, deduplicated and sorted. Documents currently open in the collaboration server\n * are read from their live Y.Doc (a tag added in document A shows up in document B before the debounced store); all\n * others from the stored content of `ports.documents`, with its old persisted names migrated first (a stored document may predate the rename of the tags row).\n */\nexport async function listWorkspaceTags({ ports, collab }: HandlerContext): Promise<string[]> {\n const names = new Set<string>()\n const open = collab?.hocuspocus.documents\n if (open) for (const doc of open.values()) collectTagNames(yDocToProsemirrorJSON(doc, COLLAB_FIELD), names)\n for (const { id, content } of await ports.documents.listContents()) {\n if (!open?.has(id)) collectTagNames(migratePersistedNames(content, { earliest: true }), names)\n }\n return [...names].sort((a, b) => a.localeCompare(b, 'de'))\n}\n\n/**\n * Tag names for the `#` popup: tags used in any document, filtered by the normalised `?q=` (prefix matches first, then\n * substrings). Requires an authenticated user through the `identity` port.\n */\nexport async function tagsGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const q = normalizeTagName(String(request.query?.q ?? ''))\n const matches = (await listWorkspaceTags(ctx)).filter(tag => tag.includes(q))\n const prefixMatches = matches.filter(tag => tag.startsWith(q))\n return json([...prefixMatches, ...matches.filter(tag => !tag.startsWith(q))].slice(0, MAX_TAGS))\n}\n\n/**\n * Users for the `@` mention popup, filtered case-insensitively by `?q=` (prefix matches first, then substrings).\n * Requires an authenticated user through the `identity` port.\n */\nexport async function usersGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const q = String(request.query?.q ?? '').trim().toLowerCase()\n // Re-pick the fields so a directory returning richer user records cannot leak them.\n const matches = (await port(ctx, 'directory').listUsers())\n .map(({ id, username, color }) => ({ id, username, color }))\n .filter(u => u.username.toLowerCase().includes(q))\n .sort((a, b) => a.username.localeCompare(b.username, 'de'))\n const prefixMatches = matches.filter(u => u.username.toLowerCase().startsWith(q))\n return json([...prefixMatches, ...matches.filter(u => !prefixMatches.includes(u))].slice(0, MAX_USERS))\n}\n\n/**\n * Stores a file (multipart field `file`) in the package of document `params.id` (or `scratch` for editors not bound to\n * a stored document); answers `{ url, name, sha256 }`. Requires an authenticated user through the `identity` port.\n */\nexport async function assetPost(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const id = request.params?.id ?? ''\n if (!ASSET_DOCUMENT_ID.test(id)) throw new HttpError(400, 'Ungültige Dokument-ID')\n // A body that is no multipart form at all counts as „no file“.\n const form = await request.readForm?.().catch(() => null)\n const file = form?.get('file')\n if (!(file instanceof File)) throw new HttpError(400, 'Keine Datei übermittelt')\n const mime = mediaMime(file.type) || 'application/octet-stream'\n const maxBytes = MAX_MEDIA_BYTES[mediaKindOf(mime) ?? 'file']\n if (file.size > maxBytes) throw new HttpError(413, `Datei ist größer als ${maxBytes / 1024 / 1024} MB`)\n const nameExt = file.name.includes('.') ? file.name.split('.').pop()?.toLowerCase() : undefined\n const ext = MEDIA_TYPES[mime]?.ext ?? (nameExt && /^[a-z0-9]{1,8}$/.test(nameExt) ? nameExt : 'bin')\n const name = `${globalThis.crypto.randomUUID()}.${ext}`\n const data = Buffer.from(await file.arrayBuffer())\n const sha256 = createHash('sha256').update(data).digest('hex')\n await port(ctx, 'assets').storeAsset(id, {\n name,\n filename: file.name.slice(0, 255) || name,\n mimeType: mime,\n size: file.size,\n createdAt: new Date().toISOString(),\n sha256,\n data,\n })\n return json({ url: assetUrl(id, name), name, sha256 })\n}\n\n// Answers single byte ranges (`Range: bytes=a-b`, `bytes=a-`, `bytes=-n`): Safari only plays and seeks audio/video\n// served with range support. Other Range forms (several ranges) get the whole file, which RFC 9110 allows.\nconst BYTE_RANGE = /^bytes=(\\d*)-(\\d*)$/\n\n/**\n * The stored file `params.name` of document `params.id`, with `Range` support (media elements send the session cookie\n * like any same-origin request). Requires an authenticated user through the `identity` port.\n */\nexport async function assetGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const id = request.params?.id ?? ''\n const name = request.params?.name ?? ''\n if (!ASSET_DOCUMENT_ID.test(id) || !ASSET_NAME.test(name)) throw new HttpError(404, 'Datei nicht gefunden')\n const asset = await port(ctx, 'assets').loadAsset(id, name)\n if (!asset) throw new HttpError(404, 'Datei nicht gefunden')\n const { record, data } = asset\n const total = data.length\n const disposition = record.mimeType === 'application/octet-stream' ? 'attachment' : 'inline'\n const headers: Record<string, string> = {\n 'accept-ranges': 'bytes',\n 'cache-control': 'private, max-age=31536000, immutable',\n 'content-type': record.mimeType,\n 'content-disposition': `${disposition}; filename*=UTF-8''${encodeURIComponent(record.filename)}`,\n }\n const range = BYTE_RANGE.exec(request.headers.get('range') ?? '')\n if (!range) return { status: 200, headers: { ...headers, 'content-length': String(total) }, body: data }\n const [, first, last] = range\n const start = first ? Number(first) : Math.max(0, total - Number(last))\n const end = first && last ? Math.min(Number(last), total - 1) : total - 1\n if (start >= total || start > end) return empty(416, { 'content-range': `bytes */${total}` })\n return {\n status: 206,\n headers: { ...headers, 'content-range': `bytes ${start}-${end}/${total}`, 'content-length': String(end - start + 1) },\n body: data.subarray(start, end + 1),\n }\n}\n\n/** The theme library (builtins and uploads) as `ThemeRecord[]`. `501` without a `themes` port. */\nexport async function themesGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n return json(await port(ctx, 'themes').listThemes())\n}\n\n/** `theme.css` of theme `params.id` as `text/css`. `400` for a malformed id, `404` for an unknown one, `501` without a `themes` port. */\nexport async function themeGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const themes = port(ctx, 'themes')\n const id = request.params?.id ?? ''\n if (!THEME_ID.test(id)) throw new HttpError(400, 'Ungültige Theme-ID')\n const theme = await themes.loadTheme(id)\n if (!theme) throw new HttpError(404, 'Theme nicht gefunden')\n const etag = `\"${theme.record.version ?? '0'}-${new TextEncoder().encode(theme.css).length}\"`\n const headers = { 'cache-control': 'private, max-age=0, must-revalidate', 'etag': etag }\n if (request.headers.get('if-none-match') === etag) return empty(304, headers)\n return text(theme.css, 'text/css; charset=utf-8', headers)\n}\n\n/** Deletes the uploaded theme `params.id` (`204`). `400` for a malformed id, `404` for an unknown one, `403` for a builtin, `501` without a `themes` port. */\nexport async function themeDelete(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const themes = port(ctx, 'themes')\n const id = request.params?.id ?? ''\n if (!THEME_ID.test(id)) throw new HttpError(400, 'Ungültige Theme-ID')\n // `removeTheme` answers `false` for builtins and unknown ids alike, so the record decides between 403 and 404.\n const record = (await themes.listThemes()).find(t => t.id === id)\n if (!record) throw new HttpError(404, 'Theme nicht gefunden')\n if (record.builtin) throw new HttpError(403, 'Mitgelieferte Themes können nicht gelöscht werden')\n if (!await themes.removeTheme(id)) throw new HttpError(404, 'Theme nicht gefunden')\n return empty()\n}\n\nconst MAX_MANIFEST_BYTES = 64 * 1024\nconst MAX_ID_LENGTH = 64\n// Markers of an HTML/XML/SVG document in the head of a „stylesheet“.\nconst MARKUP_HEAD = /<script|<\\?xml/i\n\n/** `[^a-z0-9]+` → `-`, trimmed, at most `max` characters; `theme` when nothing is left. */\nfunction slugOf(name: string, max: number): string {\n const slug = name.normalize('NFKD').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+/, '').slice(0, max).replace(/-+$/, '')\n return slug || 'theme'\n}\n\n/**\n * Uploads an Obsidian theme into the shared library: multipart `css` (File, required), `manifest` (File) and `name`\n * (string) optional. Answers 201 with the new `ThemeRecord`; the id is the slug of the name, made unique with `-2`,\n * `-3` … against the library.\n */\nexport async function themePost(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse> {\n await authenticated(ctx, request)\n const themes = port(ctx, 'themes')\n // A body that is no multipart form counts as „no file“.\n const form = await request.readForm?.().catch(() => null)\n const cssFile = form?.get('css')\n if (!(cssFile instanceof File) || cssFile.size === 0) throw new HttpError(400, 'Keine CSS-Datei übermittelt')\n if (cssFile.size > MAX_THEME_BYTES) throw new HttpError(413, `Theme ist größer als ${MAX_THEME_BYTES / 1024 / 1024} MB`)\n const bytes = new Uint8Array(await cssFile.arrayBuffer())\n if (bytes.includes(0) || MARKUP_HEAD.test(new TextDecoder('latin1').decode(bytes.subarray(0, 512)))) {\n throw new HttpError(400, 'Theme ist keine CSS-Textdatei')\n }\n let css: string\n try {\n css = new TextDecoder('utf-8', { fatal: true }).decode(bytes)\n }\n catch {\n throw new HttpError(400, 'Theme ist keine gültige UTF-8-Textdatei')\n }\n\n let manifest: ThemeManifest | undefined\n const manifestFile = form?.get('manifest')\n if (manifestFile instanceof File && manifestFile.size > 0) {\n try {\n if (manifestFile.size > MAX_MANIFEST_BYTES) throw new Error('manifest too large')\n manifest = themeManifestSchema.parse(JSON.parse(await manifestFile.text()))\n }\n catch {\n throw new HttpError(400, 'Ungültiges manifest.json')\n }\n }\n\n const nameField = form?.get('name')\n const cssBaseName = cssFile.name.replace(/\\.css$/i, '')\n const name = manifest?.name\n ?? [typeof nameField === 'string' ? nameField : '', cssBaseName].map(n => n.trim().slice(0, 120)).find(Boolean)\n ?? 'Theme'\n\n const taken = new Set((await themes.listThemes()).map(t => t.id))\n const base = slugOf(name, MAX_ID_LENGTH)\n let id = base\n for (let n = 2; taken.has(id); n++) {\n const suffix = `-${n}`\n id = `${slugOf(name, MAX_ID_LENGTH - suffix.length)}${suffix}`\n }\n\n const record: Omit<ThemeRecord, 'builtin'> = { ...manifest, name, id }\n await themes.storeTheme(record, css)\n return json({ ...record, builtin: false } satisfies ThemeRecord, 201)\n}\n"],"mappings":"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"}