@readymadeit/mcp 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (916) hide show
  1. package/README.md +114 -2
  2. package/bundle/LICENSE +201 -0
  3. package/bundle/boards/aggregator-demo-board.json +54 -0
  4. package/bundle/boards/alpaca-board.json +316 -0
  5. package/bundle/boards/animate-board.json +159 -0
  6. package/bundle/boards/ascii-art-demo-board.json +33 -0
  7. package/bundle/boards/ascii-cam-board.json +184 -0
  8. package/bundle/boards/asset-demo-board.json +54 -0
  9. package/bundle/boards/audio-encode-demo-board.json +153 -0
  10. package/bundle/boards/audiolize-board.json +152 -0
  11. package/bundle/boards/batcher-demo-board.json +43 -0
  12. package/bundle/boards/buffer-demo-board.json +53 -0
  13. package/bundle/boards/cache-demo-board.json +59 -0
  14. package/bundle/boards/camera-demo-board.json +28 -0
  15. package/bundle/boards/canvas-demo-board.json +55 -0
  16. package/bundle/boards/changes-demo-board.json +60 -0
  17. package/bundle/boards/court-booking-browser-demo-board.json +221 -0
  18. package/bundle/boards/court-booking-demo-board.json +218 -0
  19. package/bundle/boards/current-time-board.json +32 -0
  20. package/bundle/boards/debounce-demo-board.json +43 -0
  21. package/bundle/boards/decrypt-demo-board.json +47 -0
  22. package/bundle/boards/delay-demo-board.json +28 -0
  23. package/bundle/boards/detect-demo-board.json +64 -0
  24. package/bundle/boards/document-extract-demo-board.json +55 -0
  25. package/bundle/boards/dropitapp-board.json +138 -0
  26. package/bundle/boards/encode-demo-board.json +39 -0
  27. package/bundle/boards/encrypt-board.json +185 -0
  28. package/bundle/boards/encrypt-demo-board.json +39 -0
  29. package/bundle/boards/face-alert-board.json +181 -0
  30. package/bundle/boards/fetcher-demo-board.json +40 -0
  31. package/bundle/boards/fft-demo-board.json +28 -0
  32. package/bundle/boards/filter-demo-board.json +55 -0
  33. package/bundle/boards/flat-map-demo-board.json +42 -0
  34. package/bundle/boards/frame-limit-test-board.json +130 -0
  35. package/bundle/boards/game-board.json +413 -0
  36. package/bundle/boards/game-of-life-board.json +139 -0
  37. package/bundle/boards/game-of-life-demo-board.json +34 -0
  38. package/bundle/boards/gather-demo-board.json +62 -0
  39. package/bundle/boards/group-by-demo-board.json +62 -0
  40. package/bundle/boards/hacker-demo-board.json +45 -0
  41. package/bundle/boards/hacker-news-board.json +741 -0
  42. package/bundle/boards/hash-demo-board.json +40 -0
  43. package/bundle/boards/health-log-browser-demo-board.json +537 -0
  44. package/bundle/boards/health-log-demo-board.json +498 -0
  45. package/bundle/boards/helloworld-board.json +57 -0
  46. package/bundle/boards/hold-demo-board.json +67 -0
  47. package/bundle/boards/hold-scopes-demo-board.json +141 -0
  48. package/bundle/boards/http-client-browser-demo-board.json +379 -0
  49. package/bundle/boards/http-client-demo-board.json +380 -0
  50. package/bundle/boards/image-picker-demo-board.json +22 -0
  51. package/bundle/boards/imap-email-demo-board.json +35 -0
  52. package/bundle/boards/injector-demo-board.json +55 -0
  53. package/bundle/boards/iterator-demo-board.json +55 -0
  54. package/bundle/boards/limit-demo-board.json +66 -0
  55. package/bundle/boards/link-debugger.json +32 -0
  56. package/bundle/boards/live-location-demo-board.json +93 -0
  57. package/bundle/boards/live-radio-cloud-demo-board.json +216 -0
  58. package/bundle/boards/live-radio-cloud-direct-demo-board.json +311 -0
  59. package/bundle/boards/live-radio-demo-board.json +211 -0
  60. package/bundle/boards/live-radio-members-demo-board.json +314 -0
  61. package/bundle/boards/local-storage-demo-board.json +30 -0
  62. package/bundle/boards/lz-compress-demo-board.json +52 -0
  63. package/bundle/boards/map-demo-board.json +46 -0
  64. package/bundle/boards/match-filter-demo-board.json +43 -0
  65. package/bundle/boards/meeting-poll-browser-demo-board.json +649 -0
  66. package/bundle/boards/meeting-poll-demo-board.json +650 -0
  67. package/bundle/boards/microphone-monitor-demo-board.json +28 -0
  68. package/bundle/boards/microphone-speaker-demo-board.json +86 -0
  69. package/bundle/boards/monitor-demo-board.json +52 -0
  70. package/bundle/boards/mounted-endpoint-demo-board-hkp-rt.json +84 -0
  71. package/bundle/boards/mounted-endpoint-demo-board.json +84 -0
  72. package/bundle/boards/nested-rhythm-demo-board.json +1160 -0
  73. package/bundle/boards/next-bus-board.json +52 -0
  74. package/bundle/boards/next-bus-switch-board.json +79 -0
  75. package/bundle/boards/night-groove-demo-board.json +742 -0
  76. package/bundle/boards/noise-alert-board.json +146 -0
  77. package/bundle/boards/offer-followup-demo-board.json +291 -0
  78. package/bundle/boards/offer-intake-demo-board.json +289 -0
  79. package/bundle/boards/offer-intake-hetzner-demo-board.json +286 -0
  80. package/bundle/boards/offer-intake-imap-demo-board.json +289 -0
  81. package/bundle/boards/offer-intake-imap-hetzner-demo-board.json +354 -0
  82. package/bundle/boards/p2p-receiver-demo-board.json +65 -0
  83. package/bundle/boards/p2p-sender-demo-board.json +94 -0
  84. package/bundle/boards/pad-groove-demo-board.json +644 -0
  85. package/bundle/boards/peer-chat-board.json +109 -0
  86. package/bundle/boards/peer-chat-node-board.json +97 -0
  87. package/bundle/boards/qr-code-demo-board.json +22 -0
  88. package/bundle/boards/queue-demo-board.json +239 -0
  89. package/bundle/boards/reduce-demo-board.json +54 -0
  90. package/bundle/boards/remote-named-demo-board-hkp-rt.json +61 -0
  91. package/bundle/boards/remote-named-demo-board.json +61 -0
  92. package/bundle/boards/rss-demo-board.json +530 -0
  93. package/bundle/boards/rss-radio-board.json +515 -0
  94. package/bundle/boards/rss-radio-brief-one-board.json +237 -0
  95. package/bundle/boards/rss-radio-editor-unit-board.json +555 -0
  96. package/bundle/boards/rss-radio-library-unit-board.json +293 -0
  97. package/bundle/boards/rss-radio-unit-board.json +809 -0
  98. package/bundle/boards/rss-radio-voice-unit-board.json +146 -0
  99. package/bundle/boards/runtime-configs/hold-example.json +41 -0
  100. package/bundle/boards/runtime-configs/injector-example.json +23 -0
  101. package/bundle/boards/runtime-configs/speech-to-text-example.json +35 -0
  102. package/bundle/boards/runtime-configs/text-generation-example.json +31 -0
  103. package/bundle/boards/runtime-configs/text-to-speech-example.json +28 -0
  104. package/bundle/boards/runtime-configs/text-to-speech-inflect-example.json +28 -0
  105. package/bundle/boards/runtime-configs/ws-audio-playback-example.json +23 -0
  106. package/bundle/boards/select-demo-board.json +32 -0
  107. package/bundle/boards/send-ntfy-board.json +52 -0
  108. package/bundle/boards/shared-costs-demo-board.json +361 -0
  109. package/bundle/boards/sign-demo-board.json +41 -0
  110. package/bundle/boards/skill-router-demo-board.json +210 -0
  111. package/bundle/boards/smooth-demo-board.json +91 -0
  112. package/bundle/boards/smtp-email-demo-board.json +34 -0
  113. package/bundle/boards/sort-demo-board.json +55 -0
  114. package/bundle/boards/sound-demo-board.json +65 -0
  115. package/bundle/boards/spectral-modifier-board.json +98 -0
  116. package/bundle/boards/speech-synth-demo-board.json +37 -0
  117. package/bundle/boards/speech-to-text-demo-board.json +146 -0
  118. package/bundle/boards/spotify-board.json +75 -0
  119. package/bundle/boards/spread-demo-board.json +35 -0
  120. package/bundle/boards/sql-demo-board.json +56 -0
  121. package/bundle/boards/sql-explorer-board.json +447 -0
  122. package/bundle/boards/sql-explorer-node-board.json +474 -0
  123. package/bundle/boards/sql-import-board.json +156 -0
  124. package/bundle/boards/state-demo-board.json +65 -0
  125. package/bundle/boards/stopper-demo-board.json +40 -0
  126. package/bundle/boards/storage-demo-board.json +193 -0
  127. package/bundle/boards/store-demo-board.json +63 -0
  128. package/bundle/boards/swimlanes-demo-board.json +197 -0
  129. package/bundle/boards/switch-demo-board.json +51 -0
  130. package/bundle/boards/syn-board.json +19 -0
  131. package/bundle/boards/syn-booking-unit-board.json +998 -0
  132. package/bundle/boards/syn-conversations-anthropic-demo-board.json +925 -0
  133. package/bundle/boards/syn-conversations-demo-board.json +925 -0
  134. package/bundle/boards/syn-hotels-unit-board.json +287 -0
  135. package/bundle/boards/syn-mail-desk-board.json +25 -0
  136. package/bundle/boards/syn-mail-desk-unit-board.json +254 -0
  137. package/bundle/boards/telegram-listener-demo-board.json +31 -0
  138. package/bundle/boards/text-generation-anthropic-demo-board.json +66 -0
  139. package/bundle/boards/text-generation-demo-board.json +51 -0
  140. package/bundle/boards/text-generation-local-demo-board.json +80 -0
  141. package/bundle/boards/text-to-speech-demo-board.json +58 -0
  142. package/bundle/boards/timeline-demo-board.json +477 -0
  143. package/bundle/boards/timer-demo-board.json +35 -0
  144. package/bundle/boards/tracks-demo-board.json +95 -0
  145. package/bundle/boards/trigger-pad-demo-board.json +47 -0
  146. package/bundle/boards/triggerpad-board.json +31 -0
  147. package/bundle/boards/triggerpad-recorder-board.json +56 -0
  148. package/bundle/boards/uuid-generator-demo-board.json +166 -0
  149. package/bundle/boards/voice-assistant-demo-board.json +230 -0
  150. package/bundle/boards/voice-assistant-hkp-rt-demo-board.json +239 -0
  151. package/bundle/boards/voice-assistant-local-demo-board.json +232 -0
  152. package/bundle/boards/voice-assistant-skills-demo-board.json +316 -0
  153. package/bundle/boards/voice-assistant-skills-local-demo-board.json +316 -0
  154. package/bundle/boards/xy-pad-demo-board.json +36 -0
  155. package/bundle/docs/content/board-json.md +277 -0
  156. package/bundle/docs/content/boards/alpaca-board.md +56 -0
  157. package/bundle/docs/content/boards/animate-board.md +40 -0
  158. package/bundle/docs/content/boards/ascii-cam-board.md +37 -0
  159. package/bundle/docs/content/boards/audiolize-board.md +47 -0
  160. package/bundle/docs/content/boards/court-booking-browser-demo-board.md +43 -0
  161. package/bundle/docs/content/boards/court-booking-demo-board.md +120 -0
  162. package/bundle/docs/content/boards/current-time-board.md +32 -0
  163. package/bundle/docs/content/boards/dropitapp-board.md +57 -0
  164. package/bundle/docs/content/boards/encrypt-board.md +36 -0
  165. package/bundle/docs/content/boards/face-alert-board.md +50 -0
  166. package/bundle/docs/content/boards/game-board.md +69 -0
  167. package/bundle/docs/content/boards/game-of-life-board.md +47 -0
  168. package/bundle/docs/content/boards/hacker-news-board.md +76 -0
  169. package/bundle/docs/content/boards/health-log-browser-demo-board.md +56 -0
  170. package/bundle/docs/content/boards/health-log-demo-board.md +138 -0
  171. package/bundle/docs/content/boards/helloworld-board.md +40 -0
  172. package/bundle/docs/content/boards/link-debugger.md +31 -0
  173. package/bundle/docs/content/boards/live-radio-cloud-demo-board.md +70 -0
  174. package/bundle/docs/content/boards/live-radio-cloud-direct-demo-board.md +89 -0
  175. package/bundle/docs/content/boards/live-radio-demo-board.md +102 -0
  176. package/bundle/docs/content/boards/live-radio-members-demo-board.md +91 -0
  177. package/bundle/docs/content/boards/meeting-poll-browser-demo-board.md +45 -0
  178. package/bundle/docs/content/boards/meeting-poll-demo-board.md +121 -0
  179. package/bundle/docs/content/boards/mounted-endpoint-demo-board-hkp-rt.md +50 -0
  180. package/bundle/docs/content/boards/nested-rhythm-demo-board.md +165 -0
  181. package/bundle/docs/content/boards/next-bus-board.md +38 -0
  182. package/bundle/docs/content/boards/next-bus-switch-board.md +44 -0
  183. package/bundle/docs/content/boards/night-groove-demo-board.md +110 -0
  184. package/bundle/docs/content/boards/noise-alert-board.md +44 -0
  185. package/bundle/docs/content/boards/pad-groove-demo-board.md +93 -0
  186. package/bundle/docs/content/boards/peer-chat-board.md +49 -0
  187. package/bundle/docs/content/boards/peer-chat-node-board.md +47 -0
  188. package/bundle/docs/content/boards/rss-radio-board.md +221 -0
  189. package/bundle/docs/content/boards/send-ntfy-board.md +33 -0
  190. package/bundle/docs/content/boards/shared-costs-demo-board.md +65 -0
  191. package/bundle/docs/content/boards/spectral-modifier-board.md +55 -0
  192. package/bundle/docs/content/boards/spotify-board.md +46 -0
  193. package/bundle/docs/content/boards/sql-explorer-board.md +70 -0
  194. package/bundle/docs/content/boards/sql-explorer-node-board.md +76 -0
  195. package/bundle/docs/content/boards/sql-import-board.md +37 -0
  196. package/bundle/docs/content/boards/swimlanes-demo-board.md +148 -0
  197. package/bundle/docs/content/boards/syn-mail-desk-board.md +52 -0
  198. package/bundle/docs/content/boards/timeline-demo-board.md +115 -0
  199. package/bundle/docs/content/boards/triggerpad-board.md +32 -0
  200. package/bundle/docs/content/boards/triggerpad-recorder-board.md +52 -0
  201. package/bundle/docs/content/boards/voice-assistant-demo-board.md +51 -0
  202. package/bundle/docs/content/concepts/assets.md +245 -0
  203. package/bundle/docs/content/concepts/blocks.md +300 -0
  204. package/bundle/docs/content/concepts/board-assistant.md +307 -0
  205. package/bundle/docs/content/concepts/board.md +265 -0
  206. package/bundle/docs/content/concepts/cloud-boards.md +803 -0
  207. package/bundle/docs/content/concepts/coordinator.md +297 -0
  208. package/bundle/docs/content/concepts/logging.md +293 -0
  209. package/bundle/docs/content/concepts/mounts.md +314 -0
  210. package/bundle/docs/content/concepts/presets.md +360 -0
  211. package/bundle/docs/content/concepts/remotes.md +423 -0
  212. package/bundle/docs/content/concepts/runtime.md +664 -0
  213. package/bundle/docs/content/concepts/scopes.md +256 -0
  214. package/bundle/docs/content/concepts/service.md +227 -0
  215. package/bundle/docs/content/concepts/units.md +321 -0
  216. package/bundle/docs/content/introduction.md +163 -0
  217. package/bundle/docs/content/llm/circle-text-board.json +55 -0
  218. package/bundle/docs/content/llm/howto-workflow.md +369 -0
  219. package/bundle/docs/content/repository.md +161 -0
  220. package/bundle/docs/content/services/aggregator.md +76 -0
  221. package/bundle/docs/content/services/analyzer.md +44 -0
  222. package/bundle/docs/content/services/ascii-art.md +58 -0
  223. package/bundle/docs/content/services/asset.md +88 -0
  224. package/bundle/docs/content/services/audio-editor.md +57 -0
  225. package/bundle/docs/content/services/audio-encode.md +115 -0
  226. package/bundle/docs/content/services/audio-io.md +173 -0
  227. package/bundle/docs/content/services/batcher.md +49 -0
  228. package/bundle/docs/content/services/board-service.md +48 -0
  229. package/bundle/docs/content/services/browser-sub-service.md +75 -0
  230. package/bundle/docs/content/services/buffer.md +70 -0
  231. package/bundle/docs/content/services/cache.md +92 -0
  232. package/bundle/docs/content/services/camera.md +85 -0
  233. package/bundle/docs/content/services/canvas.md +196 -0
  234. package/bundle/docs/content/services/changes.md +87 -0
  235. package/bundle/docs/content/services/chunked-file-provider.md +63 -0
  236. package/bundle/docs/content/services/communication-dispatcher.md +190 -0
  237. package/bundle/docs/content/services/configurator.md +66 -0
  238. package/bundle/docs/content/services/conversations.md +244 -0
  239. package/bundle/docs/content/services/debounce.md +45 -0
  240. package/bundle/docs/content/services/decrypt.md +53 -0
  241. package/bundle/docs/content/services/delay.md +55 -0
  242. package/bundle/docs/content/services/detect.md +101 -0
  243. package/bundle/docs/content/services/document-extract.md +174 -0
  244. package/bundle/docs/content/services/download.md +67 -0
  245. package/bundle/docs/content/services/encode.md +63 -0
  246. package/bundle/docs/content/services/encrypt.md +53 -0
  247. package/bundle/docs/content/services/fetcher.md +122 -0
  248. package/bundle/docs/content/services/fft.md +96 -0
  249. package/bundle/docs/content/services/file-source.md +67 -0
  250. package/bundle/docs/content/services/filesystem.md +157 -0
  251. package/bundle/docs/content/services/filter.md +133 -0
  252. package/bundle/docs/content/services/flat-map.md +62 -0
  253. package/bundle/docs/content/services/game-of-life.md +93 -0
  254. package/bundle/docs/content/services/gather.md +135 -0
  255. package/bundle/docs/content/services/gif-encoder.md +56 -0
  256. package/bundle/docs/content/services/github.md +85 -0
  257. package/bundle/docs/content/services/group-by.md +54 -0
  258. package/bundle/docs/content/services/hacker.md +79 -0
  259. package/bundle/docs/content/services/hash.md +59 -0
  260. package/bundle/docs/content/services/hold.md +182 -0
  261. package/bundle/docs/content/services/http-relay-client.md +48 -0
  262. package/bundle/docs/content/services/http-uploader.md +62 -0
  263. package/bundle/docs/content/services/http.md +424 -0
  264. package/bundle/docs/content/services/if.md +79 -0
  265. package/bundle/docs/content/services/ifft.md +46 -0
  266. package/bundle/docs/content/services/image-picker.md +50 -0
  267. package/bundle/docs/content/services/imap-email.md +104 -0
  268. package/bundle/docs/content/services/injector.md +129 -0
  269. package/bundle/docs/content/services/input.md +90 -0
  270. package/bundle/docs/content/services/iterator.md +186 -0
  271. package/bundle/docs/content/services/join.md +153 -0
  272. package/bundle/docs/content/services/limit.md +47 -0
  273. package/bundle/docs/content/services/local-storage.md +47 -0
  274. package/bundle/docs/content/services/looper.md +62 -0
  275. package/bundle/docs/content/services/lz-compress.md +52 -0
  276. package/bundle/docs/content/services/map.md +257 -0
  277. package/bundle/docs/content/services/match-filter.md +38 -0
  278. package/bundle/docs/content/services/microphone-monitor.md +66 -0
  279. package/bundle/docs/content/services/monitor.md +67 -0
  280. package/bundle/docs/content/services/ollama.md +116 -0
  281. package/bundle/docs/content/services/openai-prompt.md +74 -0
  282. package/bundle/docs/content/services/output.md +107 -0
  283. package/bundle/docs/content/services/peer-socket.md +61 -0
  284. package/bundle/docs/content/services/qr-code.md +48 -0
  285. package/bundle/docs/content/services/queue.md +201 -0
  286. package/bundle/docs/content/services/reduce.md +51 -0
  287. package/bundle/docs/content/services/rss.md +232 -0
  288. package/bundle/docs/content/services/select.md +47 -0
  289. package/bundle/docs/content/services/sign.md +53 -0
  290. package/bundle/docs/content/services/skill-router.md +154 -0
  291. package/bundle/docs/content/services/smooth.md +59 -0
  292. package/bundle/docs/content/services/smtp-email.md +129 -0
  293. package/bundle/docs/content/services/sort.md +48 -0
  294. package/bundle/docs/content/services/sound.md +87 -0
  295. package/bundle/docs/content/services/speech-synth.md +46 -0
  296. package/bundle/docs/content/services/speech-to-text.md +172 -0
  297. package/bundle/docs/content/services/spotify.md +77 -0
  298. package/bundle/docs/content/services/spread.md +111 -0
  299. package/bundle/docs/content/services/sql.md +491 -0
  300. package/bundle/docs/content/services/state.md +57 -0
  301. package/bundle/docs/content/services/static.md +47 -0
  302. package/bundle/docs/content/services/stopper.md +55 -0
  303. package/bundle/docs/content/services/storage.md +155 -0
  304. package/bundle/docs/content/services/store.md +255 -0
  305. package/bundle/docs/content/services/sub-service.md +152 -0
  306. package/bundle/docs/content/services/switch.md +67 -0
  307. package/bundle/docs/content/services/telegram-listener.md +76 -0
  308. package/bundle/docs/content/services/text-generation.md +390 -0
  309. package/bundle/docs/content/services/text-to-speech.md +130 -0
  310. package/bundle/docs/content/services/timeline.md +333 -0
  311. package/bundle/docs/content/services/timer.md +113 -0
  312. package/bundle/docs/content/services/tracks.md +207 -0
  313. package/bundle/docs/content/services/trigger-pad.md +68 -0
  314. package/bundle/docs/content/services/uuid-generator.md +48 -0
  315. package/bundle/docs/content/services/wav-reader.md +66 -0
  316. package/bundle/docs/content/services/websocket.md +189 -0
  317. package/bundle/docs/content/services/workflow-board-builder.md +65 -0
  318. package/bundle/docs/content/services/xy-pad.md +60 -0
  319. package/bundle/docs/content/targets.md +434 -0
  320. package/bundle/docs/content/testing.md +263 -0
  321. package/bundle/docs/content/vocabulary.md +171 -0
  322. package/bundle/hkp-frontend/src/runtime/browser/BrowserRegistry.tsx +141 -0
  323. package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntime.tsx +202 -0
  324. package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeApi.ts +306 -0
  325. package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeApp.ts +310 -0
  326. package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeScope.ts +500 -0
  327. package/bundle/hkp-frontend/src/runtime/browser/UIRegistry.ts +150 -0
  328. package/bundle/hkp-frontend/src/runtime/browser/assetResolver.ts +123 -0
  329. package/bundle/hkp-frontend/src/runtime/browser/pipelineNames.ts +65 -0
  330. package/bundle/hkp-frontend/src/runtime/browser/registry/Default.ts +237 -0
  331. package/bundle/hkp-frontend/src/runtime/browser/services/Aggregator.ts +127 -0
  332. package/bundle/hkp-frontend/src/runtime/browser/services/AggregatorUI.tsx +83 -0
  333. package/bundle/hkp-frontend/src/runtime/browser/services/Analyzer.tsx +43 -0
  334. package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/Bars.tsx +33 -0
  335. package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/HorizontalSpectrogram.tsx +53 -0
  336. package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/VerticalSpectogram.tsx +56 -0
  337. package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/Waveform.tsx +109 -0
  338. package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/index.tsx +99 -0
  339. package/bundle/hkp-frontend/src/runtime/browser/services/AsciiArt.ts +119 -0
  340. package/bundle/hkp-frontend/src/runtime/browser/services/AsciiArtUI.tsx +131 -0
  341. package/bundle/hkp-frontend/src/runtime/browser/services/Asset.ts +125 -0
  342. package/bundle/hkp-frontend/src/runtime/browser/services/AssetUI.tsx +37 -0
  343. package/bundle/hkp-frontend/src/runtime/browser/services/AudioDeviceSettings.tsx +56 -0
  344. package/bundle/hkp-frontend/src/runtime/browser/services/AudioEditor.ts +129 -0
  345. package/bundle/hkp-frontend/src/runtime/browser/services/AudioEditorUI.tsx +549 -0
  346. package/bundle/hkp-frontend/src/runtime/browser/services/AudioInput.ts +298 -0
  347. package/bundle/hkp-frontend/src/runtime/browser/services/AudioInputUI.tsx +257 -0
  348. package/bundle/hkp-frontend/src/runtime/browser/services/AudioOutput.ts +189 -0
  349. package/bundle/hkp-frontend/src/runtime/browser/services/AudioOutputUI.tsx +63 -0
  350. package/bundle/hkp-frontend/src/runtime/browser/services/Batcher.ts +11 -0
  351. package/bundle/hkp-frontend/src/runtime/browser/services/BoardService.tsx +86 -0
  352. package/bundle/hkp-frontend/src/runtime/browser/services/BoardServiceUI.tsx +410 -0
  353. package/bundle/hkp-frontend/src/runtime/browser/services/BrowserGather.ts +311 -0
  354. package/bundle/hkp-frontend/src/runtime/browser/services/BrowserIterator.ts +167 -0
  355. package/bundle/hkp-frontend/src/runtime/browser/services/BrowserSubService.tsx +583 -0
  356. package/bundle/hkp-frontend/src/runtime/browser/services/BrowserSubServiceUI.tsx +187 -0
  357. package/bundle/hkp-frontend/src/runtime/browser/services/BrowserTracks.tsx +477 -0
  358. package/bundle/hkp-frontend/src/runtime/browser/services/Buffer.ts +11 -0
  359. package/bundle/hkp-frontend/src/runtime/browser/services/Cache.ts +134 -0
  360. package/bundle/hkp-frontend/src/runtime/browser/services/CacheUI.tsx +125 -0
  361. package/bundle/hkp-frontend/src/runtime/browser/services/Camera.ts +90 -0
  362. package/bundle/hkp-frontend/src/runtime/browser/services/CameraUI.tsx +321 -0
  363. package/bundle/hkp-frontend/src/runtime/browser/services/Canvas.ts +139 -0
  364. package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/canvasDraw.ts +543 -0
  365. package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/canvasUtils.ts +39 -0
  366. package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/index.tsx +287 -0
  367. package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/canvasDraw.test.ts +355 -0
  368. package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/canvasUtils.test.ts +89 -0
  369. package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/fullscreen.test.tsx +26 -0
  370. package/bundle/hkp-frontend/src/runtime/browser/services/Changes.ts +201 -0
  371. package/bundle/hkp-frontend/src/runtime/browser/services/ChangesUI.tsx +85 -0
  372. package/bundle/hkp-frontend/src/runtime/browser/services/ChunkedFileProvider.tsx +183 -0
  373. package/bundle/hkp-frontend/src/runtime/browser/services/Configurator.ts +258 -0
  374. package/bundle/hkp-frontend/src/runtime/browser/services/ConfiguratorUI.tsx +83 -0
  375. package/bundle/hkp-frontend/src/runtime/browser/services/ConsideredHacker.tsx +28 -0
  376. package/bundle/hkp-frontend/src/runtime/browser/services/Debounce.ts +75 -0
  377. package/bundle/hkp-frontend/src/runtime/browser/services/DebounceUI.tsx +70 -0
  378. package/bundle/hkp-frontend/src/runtime/browser/services/Decrypt.ts +127 -0
  379. package/bundle/hkp-frontend/src/runtime/browser/services/DecryptUI.tsx +39 -0
  380. package/bundle/hkp-frontend/src/runtime/browser/services/Delay.tsx +110 -0
  381. package/bundle/hkp-frontend/src/runtime/browser/services/Detect.ts +214 -0
  382. package/bundle/hkp-frontend/src/runtime/browser/services/DetectUI.tsx +86 -0
  383. package/bundle/hkp-frontend/src/runtime/browser/services/Download.ts +120 -0
  384. package/bundle/hkp-frontend/src/runtime/browser/services/DownloadUI.tsx +61 -0
  385. package/bundle/hkp-frontend/src/runtime/browser/services/Encode.ts +138 -0
  386. package/bundle/hkp-frontend/src/runtime/browser/services/EncodeUI.tsx +52 -0
  387. package/bundle/hkp-frontend/src/runtime/browser/services/Encrypt.ts +125 -0
  388. package/bundle/hkp-frontend/src/runtime/browser/services/EncryptUI.tsx +39 -0
  389. package/bundle/hkp-frontend/src/runtime/browser/services/FFT.tsx +238 -0
  390. package/bundle/hkp-frontend/src/runtime/browser/services/FFTUI.tsx +112 -0
  391. package/bundle/hkp-frontend/src/runtime/browser/services/FeedbackService.ts +247 -0
  392. package/bundle/hkp-frontend/src/runtime/browser/services/FeedbackServiceUI.tsx +35 -0
  393. package/bundle/hkp-frontend/src/runtime/browser/services/Fetcher.tsx +232 -0
  394. package/bundle/hkp-frontend/src/runtime/browser/services/FetcherUI.tsx +154 -0
  395. package/bundle/hkp-frontend/src/runtime/browser/services/FileSource.tsx +171 -0
  396. package/bundle/hkp-frontend/src/runtime/browser/services/Filter.tsx +18 -0
  397. package/bundle/hkp-frontend/src/runtime/browser/services/FilterUI.tsx +89 -0
  398. package/bundle/hkp-frontend/src/runtime/browser/services/FlatMap.ts +68 -0
  399. package/bundle/hkp-frontend/src/runtime/browser/services/FlatMapUI.tsx +38 -0
  400. package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLife.ts +210 -0
  401. package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeRenderer.ts +182 -0
  402. package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeRendererUI.tsx +48 -0
  403. package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeUI.tsx +58 -0
  404. package/bundle/hkp-frontend/src/runtime/browser/services/GatherUI.tsx +84 -0
  405. package/bundle/hkp-frontend/src/runtime/browser/services/GifEncoder.tsx +651 -0
  406. package/bundle/hkp-frontend/src/runtime/browser/services/GithubAPI.ts +294 -0
  407. package/bundle/hkp-frontend/src/runtime/browser/services/GithubComponents.tsx +389 -0
  408. package/bundle/hkp-frontend/src/runtime/browser/services/GithubSink.tsx +109 -0
  409. package/bundle/hkp-frontend/src/runtime/browser/services/GithubSinkUI.tsx +143 -0
  410. package/bundle/hkp-frontend/src/runtime/browser/services/GithubSource.tsx +86 -0
  411. package/bundle/hkp-frontend/src/runtime/browser/services/GithubSourceUI.tsx +162 -0
  412. package/bundle/hkp-frontend/src/runtime/browser/services/GroupBy.ts +11 -0
  413. package/bundle/hkp-frontend/src/runtime/browser/services/HTTPUploader.ts +102 -0
  414. package/bundle/hkp-frontend/src/runtime/browser/services/Hacker.ts +73 -0
  415. package/bundle/hkp-frontend/src/runtime/browser/services/HackerUI.tsx +134 -0
  416. package/bundle/hkp-frontend/src/runtime/browser/services/Hash.ts +134 -0
  417. package/bundle/hkp-frontend/src/runtime/browser/services/HashUI.tsx +45 -0
  418. package/bundle/hkp-frontend/src/runtime/browser/services/Hold.ts +288 -0
  419. package/bundle/hkp-frontend/src/runtime/browser/services/HoldUI.tsx +43 -0
  420. package/bundle/hkp-frontend/src/runtime/browser/services/HttpClient.ts +558 -0
  421. package/bundle/hkp-frontend/src/runtime/browser/services/HttpClientUI.tsx +212 -0
  422. package/bundle/hkp-frontend/src/runtime/browser/services/HttpUploaderTokenInjector.ts +149 -0
  423. package/bundle/hkp-frontend/src/runtime/browser/services/IfService.ts +231 -0
  424. package/bundle/hkp-frontend/src/runtime/browser/services/IfServiceUI.tsx +50 -0
  425. package/bundle/hkp-frontend/src/runtime/browser/services/ImagePicker.tsx +118 -0
  426. package/bundle/hkp-frontend/src/runtime/browser/services/Injector.ts +61 -0
  427. package/bundle/hkp-frontend/src/runtime/browser/services/InjectorUI.tsx +389 -0
  428. package/bundle/hkp-frontend/src/runtime/browser/services/Input.ts +214 -0
  429. package/bundle/hkp-frontend/src/runtime/browser/services/InputUI.tsx +71 -0
  430. package/bundle/hkp-frontend/src/runtime/browser/services/IteratorUI.tsx +59 -0
  431. package/bundle/hkp-frontend/src/runtime/browser/services/LZCompress.ts +63 -0
  432. package/bundle/hkp-frontend/src/runtime/browser/services/Limit.ts +39 -0
  433. package/bundle/hkp-frontend/src/runtime/browser/services/LimitUI.tsx +41 -0
  434. package/bundle/hkp-frontend/src/runtime/browser/services/LocalStorage.ts +65 -0
  435. package/bundle/hkp-frontend/src/runtime/browser/services/LocalStorageUI.tsx +72 -0
  436. package/bundle/hkp-frontend/src/runtime/browser/services/Map.ts +14 -0
  437. package/bundle/hkp-frontend/src/runtime/browser/services/MapUI.tsx +334 -0
  438. package/bundle/hkp-frontend/src/runtime/browser/services/MapUIModeAssist.tsx +61 -0
  439. package/bundle/hkp-frontend/src/runtime/browser/services/MatchFilter.ts +11 -0
  440. package/bundle/hkp-frontend/src/runtime/browser/services/MicrophoneMonitor.ts +190 -0
  441. package/bundle/hkp-frontend/src/runtime/browser/services/MicrophoneMonitorUI.tsx +94 -0
  442. package/bundle/hkp-frontend/src/runtime/browser/services/Monitor.tsx +15 -0
  443. package/bundle/hkp-frontend/src/runtime/browser/services/MonitorUI.tsx +281 -0
  444. package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPrompt.ts +237 -0
  445. package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/BusyIndicator.tsx +18 -0
  446. package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/PromptInput.tsx +71 -0
  447. package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/StreamingPreview.tsx +24 -0
  448. package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/index.tsx +156 -0
  449. package/bundle/hkp-frontend/src/runtime/browser/services/OpenAIPrompt.ts +254 -0
  450. package/bundle/hkp-frontend/src/runtime/browser/services/OpenAIPromptUI.tsx +189 -0
  451. package/bundle/hkp-frontend/src/runtime/browser/services/Output.tsx +145 -0
  452. package/bundle/hkp-frontend/src/runtime/browser/services/OutputUI.tsx +118 -0
  453. package/bundle/hkp-frontend/src/runtime/browser/services/PeerChatTemplate.json +43 -0
  454. package/bundle/hkp-frontend/src/runtime/browser/services/PeerConnection.ts +260 -0
  455. package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocket.tsx +268 -0
  456. package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocketReceiverTemplate.json +29 -0
  457. package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocketUI.tsx +341 -0
  458. package/bundle/hkp-frontend/src/runtime/browser/services/PlaceholderUI.tsx +72 -0
  459. package/bundle/hkp-frontend/src/runtime/browser/services/QrCode.ts +63 -0
  460. package/bundle/hkp-frontend/src/runtime/browser/services/QrCodeUI.tsx +197 -0
  461. package/bundle/hkp-frontend/src/runtime/browser/services/Select.ts +40 -0
  462. package/bundle/hkp-frontend/src/runtime/browser/services/SelectUI.tsx +41 -0
  463. package/bundle/hkp-frontend/src/runtime/browser/services/ServiceBase.ts +50 -0
  464. package/bundle/hkp-frontend/src/runtime/browser/services/SetRuntimeVariable.ts +65 -0
  465. package/bundle/hkp-frontend/src/runtime/browser/services/SetRuntimeVariableUI.tsx +50 -0
  466. package/bundle/hkp-frontend/src/runtime/browser/services/Sign.ts +182 -0
  467. package/bundle/hkp-frontend/src/runtime/browser/services/SignUI.tsx +55 -0
  468. package/bundle/hkp-frontend/src/runtime/browser/services/Smooth.ts +101 -0
  469. package/bundle/hkp-frontend/src/runtime/browser/services/SmoothUI.tsx +50 -0
  470. package/bundle/hkp-frontend/src/runtime/browser/services/Sort.ts +59 -0
  471. package/bundle/hkp-frontend/src/runtime/browser/services/SortUI.tsx +61 -0
  472. package/bundle/hkp-frontend/src/runtime/browser/services/Sound.ts +541 -0
  473. package/bundle/hkp-frontend/src/runtime/browser/services/SoundUI.tsx +183 -0
  474. package/bundle/hkp-frontend/src/runtime/browser/services/SpeechSynth.ts +56 -0
  475. package/bundle/hkp-frontend/src/runtime/browser/services/SpeechSynthUI.tsx +108 -0
  476. package/bundle/hkp-frontend/src/runtime/browser/services/Spotify.tsx +111 -0
  477. package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyAPI.ts +113 -0
  478. package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyComponents.tsx +104 -0
  479. package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyUI.tsx +186 -0
  480. package/bundle/hkp-frontend/src/runtime/browser/services/Spread.ts +117 -0
  481. package/bundle/hkp-frontend/src/runtime/browser/services/SpreadUI.tsx +38 -0
  482. package/bundle/hkp-frontend/src/runtime/browser/services/Sql.ts +448 -0
  483. package/bundle/hkp-frontend/src/runtime/browser/services/SqlInspector.tsx +332 -0
  484. package/bundle/hkp-frontend/src/runtime/browser/services/SqlUI.tsx +226 -0
  485. package/bundle/hkp-frontend/src/runtime/browser/services/State.ts +149 -0
  486. package/bundle/hkp-frontend/src/runtime/browser/services/Stopper.ts +29 -0
  487. package/bundle/hkp-frontend/src/runtime/browser/services/Switch.tsx +419 -0
  488. package/bundle/hkp-frontend/src/runtime/browser/services/SwitchUI.tsx +171 -0
  489. package/bundle/hkp-frontend/src/runtime/browser/services/TickRuleDelta.ts +101 -0
  490. package/bundle/hkp-frontend/src/runtime/browser/services/TickRuleDeltaUI.tsx +56 -0
  491. package/bundle/hkp-frontend/src/runtime/browser/services/Timeline.ts +709 -0
  492. package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/TimelinePanel.tsx +79 -0
  493. package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/TimelineRows.tsx +574 -0
  494. package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/Transport.tsx +141 -0
  495. package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/index.tsx +119 -0
  496. package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/model.ts +425 -0
  497. package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/parts.tsx +708 -0
  498. package/bundle/hkp-frontend/src/runtime/browser/services/Timer.ts +15 -0
  499. package/bundle/hkp-frontend/src/runtime/browser/services/TimerUI.tsx +213 -0
  500. package/bundle/hkp-frontend/src/runtime/browser/services/TracksUI.tsx +81 -0
  501. package/bundle/hkp-frontend/src/runtime/browser/services/TriggerPad.ts +108 -0
  502. package/bundle/hkp-frontend/src/runtime/browser/services/TriggerPadUI.tsx +160 -0
  503. package/bundle/hkp-frontend/src/runtime/browser/services/UuidGenerator.ts +74 -0
  504. package/bundle/hkp-frontend/src/runtime/browser/services/UuidGeneratorUI.tsx +59 -0
  505. package/bundle/hkp-frontend/src/runtime/browser/services/WaveformDisplay.tsx +104 -0
  506. package/bundle/hkp-frontend/src/runtime/browser/services/WebsocketClient.ts +114 -0
  507. package/bundle/hkp-frontend/src/runtime/browser/services/WebsocketClientUI.tsx +42 -0
  508. package/bundle/hkp-frontend/src/runtime/browser/services/WorkflowBoardBuilder.ts +499 -0
  509. package/bundle/hkp-frontend/src/runtime/browser/services/WorkflowBoardBuilderUI.tsx +521 -0
  510. package/bundle/hkp-frontend/src/runtime/browser/services/XYPad.ts +68 -0
  511. package/bundle/hkp-frontend/src/runtime/browser/services/XYPadUI.tsx +274 -0
  512. package/bundle/hkp-frontend/src/runtime/browser/services/audio/audioHelper.ts +36 -0
  513. package/bundle/hkp-frontend/src/runtime/browser/services/base/Batcher.ts +76 -0
  514. package/bundle/hkp-frontend/src/runtime/browser/services/base/Buffer.ts +142 -0
  515. package/bundle/hkp-frontend/src/runtime/browser/services/base/Filter.ts +125 -0
  516. package/bundle/hkp-frontend/src/runtime/browser/services/base/GroupBy.ts +101 -0
  517. package/bundle/hkp-frontend/src/runtime/browser/services/base/Map.ts +543 -0
  518. package/bundle/hkp-frontend/src/runtime/browser/services/base/MatchFilter.ts +49 -0
  519. package/bundle/hkp-frontend/src/runtime/browser/services/base/Monitor.ts +75 -0
  520. package/bundle/hkp-frontend/src/runtime/browser/services/base/Reduce.ts +74 -0
  521. package/bundle/hkp-frontend/src/runtime/browser/services/base/Timer.ts +342 -0
  522. package/bundle/hkp-frontend/src/runtime/browser/services/base/bundle.js +7375 -0
  523. package/bundle/hkp-frontend/src/runtime/browser/services/base/eval.ts +208 -0
  524. package/bundle/hkp-frontend/src/runtime/browser/services/base/expressionEval.ts +223 -0
  525. package/bundle/hkp-frontend/src/runtime/browser/services/base/helpers.ts +10 -0
  526. package/bundle/hkp-frontend/src/runtime/browser/services/base/index.ts +37 -0
  527. package/bundle/hkp-frontend/src/runtime/browser/services/camera/videoCapture.ts +78 -0
  528. package/bundle/hkp-frontend/src/runtime/browser/services/changes-modes.ts +9 -0
  529. package/bundle/hkp-frontend/src/runtime/browser/services/colorpalette.ts +26 -0
  530. package/bundle/hkp-frontend/src/runtime/browser/services/detect/DetectionPreview.tsx +76 -0
  531. package/bundle/hkp-frontend/src/runtime/browser/services/detect/mediapipe.ts +34 -0
  532. package/bundle/hkp-frontend/src/runtime/browser/services/detect/models/README.md +14 -0
  533. package/bundle/hkp-frontend/src/runtime/browser/services/detect/models/blaze_face_short_range.tflite +0 -0
  534. package/bundle/hkp-frontend/src/runtime/browser/services/detect-modes.ts +7 -0
  535. package/bundle/hkp-frontend/src/runtime/browser/services/encode-modes.ts +10 -0
  536. package/bundle/hkp-frontend/src/runtime/browser/services/helpers.ts +300 -0
  537. package/bundle/hkp-frontend/src/runtime/browser/services/http-client-methods.ts +10 -0
  538. package/bundle/hkp-frontend/src/runtime/browser/services/sql-database.ts +594 -0
  539. package/bundle/hkp-frontend/src/runtime/browser/services/sql-dump.ts +192 -0
  540. package/bundle/hkp-frontend/src/runtime/browser/services/sql-inspect.ts +130 -0
  541. package/bundle/hkp-frontend/src/runtime/browser/services/sql-inspector.css +155 -0
  542. package/bundle/hkp-frontend/src/runtime/browser/services/sql-modes.ts +27 -0
  543. package/bundle/hkp-frontend/src/runtime/browser/services/tests/TESTING_APPROACH.md +169 -0
  544. package/bundle/hkp-frontend/src/runtime/browser/services/tests/__snapshots__/service-ui.future-enhancements.test.tsx.snap +5 -0
  545. package/bundle/hkp-frontend/src/runtime/browser/services/tests/audio-input-permissions.test.ts +42 -0
  546. package/bundle/hkp-frontend/src/runtime/browser/services/tests/audiolizeLanes.test.ts +70 -0
  547. package/bundle/hkp-frontend/src/runtime/browser/services/tests/browser-scopes.test.ts +305 -0
  548. package/bundle/hkp-frontend/src/runtime/browser/services/tests/cache-service.test.ts +53 -0
  549. package/bundle/hkp-frontend/src/runtime/browser/services/tests/camera-video-capture.test.ts +24 -0
  550. package/bundle/hkp-frontend/src/runtime/browser/services/tests/changes-service.test.ts +133 -0
  551. package/bundle/hkp-frontend/src/runtime/browser/services/tests/crypto-secrets.test.ts +196 -0
  552. package/bundle/hkp-frontend/src/runtime/browser/services/tests/detect-service.test.ts +119 -0
  553. package/bundle/hkp-frontend/src/runtime/browser/services/tests/download.test.ts +100 -0
  554. package/bundle/hkp-frontend/src/runtime/browser/services/tests/expressionEval.test.ts +117 -0
  555. package/bundle/hkp-frontend/src/runtime/browser/services/tests/fetcher-panel.test.tsx +35 -0
  556. package/bundle/hkp-frontend/src/runtime/browser/services/tests/fetcher-service.test.ts +80 -0
  557. package/bundle/hkp-frontend/src/runtime/browser/services/tests/filter-service.test.ts +37 -0
  558. package/bundle/hkp-frontend/src/runtime/browser/services/tests/github-secrets.test.ts +108 -0
  559. package/bundle/hkp-frontend/src/runtime/browser/services/tests/group-by-board.test.ts +42 -0
  560. package/bundle/hkp-frontend/src/runtime/browser/services/tests/hold-service.test.ts +199 -0
  561. package/bundle/hkp-frontend/src/runtime/browser/services/tests/http-client-service.test.ts +474 -0
  562. package/bundle/hkp-frontend/src/runtime/browser/services/tests/http-uploader-token-injector.test.ts +152 -0
  563. package/bundle/hkp-frontend/src/runtime/browser/services/tests/iterator-gather-service.test.ts +277 -0
  564. package/bundle/hkp-frontend/src/runtime/browser/services/tests/map-service.test.ts +1047 -0
  565. package/bundle/hkp-frontend/src/runtime/browser/services/tests/map-ui.test.tsx +70 -0
  566. package/bundle/hkp-frontend/src/runtime/browser/services/tests/nested-rhythm-board.test.ts +311 -0
  567. package/bundle/hkp-frontend/src/runtime/browser/services/tests/night-groove-board.test.ts +290 -0
  568. package/bundle/hkp-frontend/src/runtime/browser/services/tests/pad-groove-board.test.ts +230 -0
  569. package/bundle/hkp-frontend/src/runtime/browser/services/tests/peer-host-discovery.test.ts +77 -0
  570. package/bundle/hkp-frontend/src/runtime/browser/services/tests/peer-socket-discovery.test.ts +69 -0
  571. package/bundle/hkp-frontend/src/runtime/browser/services/tests/pipeline-names.test.ts +63 -0
  572. package/bundle/hkp-frontend/src/runtime/browser/services/tests/secret-canary.test.ts +121 -0
  573. package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-modules.contract.test.ts +229 -0
  574. package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.advanced.test.tsx +670 -0
  575. package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.future-enhancements.test.tsx +219 -0
  576. package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.integration.test.tsx +432 -0
  577. package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.smoke.test.tsx +140 -0
  578. package/bundle/hkp-frontend/src/runtime/browser/services/tests/sound-service.test.ts +293 -0
  579. package/bundle/hkp-frontend/src/runtime/browser/services/tests/spread-service.test.ts +137 -0
  580. package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-dump.test.ts +172 -0
  581. package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-inspect.test.ts +163 -0
  582. package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-inspector.test.tsx +121 -0
  583. package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql.test.ts +702 -0
  584. package/bundle/hkp-frontend/src/runtime/browser/services/tests/sub-service-in-block.test.tsx +84 -0
  585. package/bundle/hkp-frontend/src/runtime/browser/services/tests/switch-service.test.ts +254 -0
  586. package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-board.test.ts +218 -0
  587. package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-service.test.ts +1002 -0
  588. package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-ui-model.test.ts +230 -0
  589. package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-ui.test.tsx +371 -0
  590. package/bundle/hkp-frontend/src/runtime/browser/services/tests/timer-service.test.ts +902 -0
  591. package/bundle/hkp-frontend/src/runtime/browser/services/tests/tracks-service.test.ts +275 -0
  592. package/bundle/hkp-frontend/src/runtime/browser/services/tests/tracks-ui.test.tsx +126 -0
  593. package/bundle/hkp-frontend/src/runtime/browser/services/tests/triggerpad-recorder-board.test.ts +100 -0
  594. package/bundle/hkp-frontend/src/runtime/browser/services/tests/uuid-generator-service.test.ts +214 -0
  595. package/bundle/hkp-frontend/src/runtime/browser/services/timeline-core.ts +410 -0
  596. package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/DefaultWorkflowBoard.ts +90 -0
  597. package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/RefinementSession.ts +121 -0
  598. package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/SystemPromptCatalog.ts +245 -0
  599. package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/catalog/browser-service-specs.json +110 -0
  600. package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/catalog/runtime-capabilities.json +37 -0
  601. package/bundle/hkp-frontend/src/runtime/browser/tests/asset-service.test.ts +73 -0
  602. package/bundle/hkp-frontend/src/runtime/browser/tests/board-persistence.integration.test.tsx +391 -0
  603. package/bundle/hkp-frontend/src/runtime/browser/tests/browser-runtime.identity-binding.test.tsx +81 -0
  604. package/bundle/hkp-frontend/src/runtime/browser/tests/demo-boards.regression.test.tsx +386 -0
  605. package/bundle/hkp-frontend/src/runtime/browser/tests/failed-call-lifecycle.test.ts +56 -0
  606. package/bundle/hkp-frontend/src/runtime/browser/tests/http-client.integration.test.ts +146 -0
  607. package/bundle/hkp-frontend/src/runtime/browser/tests/nested-run.integration.test.ts +415 -0
  608. package/bundle/hkp-frontend/src/runtime/browser/tests/replay-next.test.ts +391 -0
  609. package/bundle/hkp-frontend/src/runtime/browser/tests/rerun.test.ts +71 -0
  610. package/bundle/hkp-frontend/src/runtime/browser/tests/run-no-input.test.ts +72 -0
  611. package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-add.integration.test.tsx +256 -0
  612. package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-operations.integration.test.tsx +310 -0
  613. package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-processing.integration.test.ts +311 -0
  614. package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-removal.integration.test.tsx +454 -0
  615. package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-update.identity.integration.test.tsx +145 -0
  616. package/bundle/hkp-frontend/src/runtime/browser/tests/service-operations.integration.test.tsx +511 -0
  617. package/bundle/hkp-node/src/address.ts +72 -0
  618. package/bundle/hkp-node/src/assets.ts +582 -0
  619. package/bundle/hkp-node/src/auth.ts +369 -0
  620. package/bundle/hkp-node/src/callReport.ts +281 -0
  621. package/bundle/hkp-node/src/coordinator/auth.ts +85 -0
  622. package/bundle/hkp-node/src/coordinator/binaryFrame.ts +189 -0
  623. package/bundle/hkp-node/src/coordinator/boardStore.ts +0 -0
  624. package/bundle/hkp-node/src/coordinator/bridge.ts +242 -0
  625. package/bundle/hkp-node/src/coordinator/bridgeProtocol.ts +200 -0
  626. package/bundle/hkp-node/src/coordinator/coordinator.ts +594 -0
  627. package/bundle/hkp-node/src/coordinator/facadeAccess.ts +426 -0
  628. package/bundle/hkp-node/src/coordinator/fileBoardStore.ts +159 -0
  629. package/bundle/hkp-node/src/coordinator/frameLimits.ts +41 -0
  630. package/bundle/hkp-node/src/coordinator/index.ts +12 -0
  631. package/bundle/hkp-node/src/coordinator/join.ts +62 -0
  632. package/bundle/hkp-node/src/coordinator/logStore.ts +335 -0
  633. package/bundle/hkp-node/src/coordinator/members.ts +133 -0
  634. package/bundle/hkp-node/src/coordinator/mount.ts +124 -0
  635. package/bundle/hkp-node/src/coordinator/participantProtocol.ts +169 -0
  636. package/bundle/hkp-node/src/coordinator/participants.ts +666 -0
  637. package/bundle/hkp-node/src/coordinator/router.ts +416 -0
  638. package/bundle/hkp-node/src/coordinator/session.ts +1610 -0
  639. package/bundle/hkp-node/src/coordinator/types.ts +95 -0
  640. package/bundle/hkp-node/src/coordinatorLinks.ts +712 -0
  641. package/bundle/hkp-node/src/credentialedFetch.ts +122 -0
  642. package/bundle/hkp-node/src/egress.ts +344 -0
  643. package/bundle/hkp-node/src/expressionEval.ts +223 -0
  644. package/bundle/hkp-node/src/index.ts +470 -0
  645. package/bundle/hkp-node/src/instance.ts +172 -0
  646. package/bundle/hkp-node/src/mounts.ts +296 -0
  647. package/bundle/hkp-node/src/origins.ts +201 -0
  648. package/bundle/hkp-node/src/reset.ts +187 -0
  649. package/bundle/hkp-node/src/runtime.ts +1345 -0
  650. package/bundle/hkp-node/src/secrets.ts +292 -0
  651. package/bundle/hkp-node/src/server.ts +2120 -0
  652. package/bundle/hkp-node/src/services/asset.ts +150 -0
  653. package/bundle/hkp-node/src/services/claudeModels.ts +370 -0
  654. package/bundle/hkp-node/src/services/communication-dispatcher.ts +735 -0
  655. package/bundle/hkp-node/src/services/conversations.ts +914 -0
  656. package/bundle/hkp-node/src/services/database.ts +386 -0
  657. package/bundle/hkp-node/src/services/document-extract.ts +575 -0
  658. package/bundle/hkp-node/src/services/expression.ts +417 -0
  659. package/bundle/hkp-node/src/services/fileStore.ts +369 -0
  660. package/bundle/hkp-node/src/services/filesystem.ts +280 -0
  661. package/bundle/hkp-node/src/services/gather.ts +335 -0
  662. package/bundle/hkp-node/src/services/hold.ts +275 -0
  663. package/bundle/hkp-node/src/services/http-client.ts +525 -0
  664. package/bundle/hkp-node/src/services/http-server.ts +1222 -0
  665. package/bundle/hkp-node/src/services/imap-email.ts +627 -0
  666. package/bundle/hkp-node/src/services/injector.ts +135 -0
  667. package/bundle/hkp-node/src/services/iterator.ts +203 -0
  668. package/bundle/hkp-node/src/services/join.ts +226 -0
  669. package/bundle/hkp-node/src/services/map.ts +632 -0
  670. package/bundle/hkp-node/src/services/monitor.ts +128 -0
  671. package/bundle/hkp-node/src/services/nested-pipeline.ts +405 -0
  672. package/bundle/hkp-node/src/services/peer-server.ts +268 -0
  673. package/bundle/hkp-node/src/services/queue-store.ts +301 -0
  674. package/bundle/hkp-node/src/services/queue.ts +316 -0
  675. package/bundle/hkp-node/src/services/recordStore.ts +341 -0
  676. package/bundle/hkp-node/src/services/rss-parse.ts +281 -0
  677. package/bundle/hkp-node/src/services/rss.ts +392 -0
  678. package/bundle/hkp-node/src/services/smtp-email.ts +398 -0
  679. package/bundle/hkp-node/src/services/spread.ts +153 -0
  680. package/bundle/hkp-node/src/services/sql-dump.ts +192 -0
  681. package/bundle/hkp-node/src/services/sql.ts +429 -0
  682. package/bundle/hkp-node/src/services/stopper.ts +69 -0
  683. package/bundle/hkp-node/src/services/storage.ts +332 -0
  684. package/bundle/hkp-node/src/services/store.ts +609 -0
  685. package/bundle/hkp-node/src/services/stream.ts +367 -0
  686. package/bundle/hkp-node/src/services/sub-service.ts +600 -0
  687. package/bundle/hkp-node/src/services/telegram-listener.ts +263 -0
  688. package/bundle/hkp-node/src/services/telegram-sender.ts +136 -0
  689. package/bundle/hkp-node/src/services/text-generation.ts +1249 -0
  690. package/bundle/hkp-node/src/services/timer.ts +273 -0
  691. package/bundle/hkp-node/src/services/tracks.ts +381 -0
  692. package/bundle/hkp-node/src/services/websocket-reader.ts +364 -0
  693. package/bundle/hkp-node/src/types.ts +465 -0
  694. package/bundle/hkp-node/src/wsTickets.ts +77 -0
  695. package/bundle/hkp-node/src/yas.ts +95 -0
  696. package/bundle/hkp-python/src/hkp/__init__.py +1 -0
  697. package/bundle/hkp-python/src/hkp/__main__.py +241 -0
  698. package/bundle/hkp-python/src/hkp/address.py +56 -0
  699. package/bundle/hkp-python/src/hkp/assets.py +439 -0
  700. package/bundle/hkp-python/src/hkp/auth.py +252 -0
  701. package/bundle/hkp-python/src/hkp/binary_frame.py +100 -0
  702. package/bundle/hkp-python/src/hkp/call_report.py +201 -0
  703. package/bundle/hkp-python/src/hkp/coordinator_links.py +667 -0
  704. package/bundle/hkp-python/src/hkp/data.py +118 -0
  705. package/bundle/hkp-python/src/hkp/egress.py +310 -0
  706. package/bundle/hkp-python/src/hkp/mount.py +46 -0
  707. package/bundle/hkp-python/src/hkp/mounts.py +288 -0
  708. package/bundle/hkp-python/src/hkp/origins.py +169 -0
  709. package/bundle/hkp-python/src/hkp/runtime.py +1131 -0
  710. package/bundle/hkp-python/src/hkp/secrets.py +261 -0
  711. package/bundle/hkp-python/src/hkp/server.py +1714 -0
  712. package/bundle/hkp-python/src/hkp/services/__init__.py +0 -0
  713. package/bundle/hkp-python/src/hkp/services/asset.py +123 -0
  714. package/bundle/hkp-python/src/hkp/services/audio_encode.py +190 -0
  715. package/bundle/hkp-python/src/hkp/services/expression.py +942 -0
  716. package/bundle/hkp-python/src/hkp/services/gather.py +266 -0
  717. package/bundle/hkp-python/src/hkp/services/hold.py +246 -0
  718. package/bundle/hkp-python/src/hkp/services/http_client.py +444 -0
  719. package/bundle/hkp-python/src/hkp/services/http_server.py +790 -0
  720. package/bundle/hkp-python/src/hkp/services/iterator.py +141 -0
  721. package/bundle/hkp-python/src/hkp/services/join.py +173 -0
  722. package/bundle/hkp-python/src/hkp/services/map_service.py +452 -0
  723. package/bundle/hkp-python/src/hkp/services/monitor.py +99 -0
  724. package/bundle/hkp-python/src/hkp/services/nested_pipeline.py +278 -0
  725. package/bundle/hkp-python/src/hkp/services/skill_router.py +388 -0
  726. package/bundle/hkp-python/src/hkp/services/speech_to_text.py +186 -0
  727. package/bundle/hkp-python/src/hkp/services/spread.py +114 -0
  728. package/bundle/hkp-python/src/hkp/services/stopper.py +59 -0
  729. package/bundle/hkp-python/src/hkp/services/sub_service.py +417 -0
  730. package/bundle/hkp-python/src/hkp/services/text_generation.py +498 -0
  731. package/bundle/hkp-python/src/hkp/services/text_to_speech.py +237 -0
  732. package/bundle/hkp-python/src/hkp/services/timer.py +267 -0
  733. package/bundle/hkp-python/src/hkp/services/tracks.py +251 -0
  734. package/bundle/hkp-python/src/hkp/types.py +412 -0
  735. package/bundle/hkp-python/src/hkp/ws_tickets.py +54 -0
  736. package/bundle/hkp-python/src/hkp/yas.py +192 -0
  737. package/bundle/hkp-rt/lib/CMakeLists.txt +193 -0
  738. package/bundle/hkp-rt/lib/hkp-bundles/CMakeLists.txt +55 -0
  739. package/bundle/hkp-rt/lib/hkp-bundles/plugin_register.cpp +14 -0
  740. package/bundle/hkp-rt/lib/include/app.h +201 -0
  741. package/bundle/hkp-rt/lib/include/assets.h +239 -0
  742. package/bundle/hkp-rt/lib/include/auth.h +167 -0
  743. package/bundle/hkp-rt/lib/include/call_report.h +197 -0
  744. package/bundle/hkp-rt/lib/include/coordinator_links.h +127 -0
  745. package/bundle/hkp-rt/lib/include/discovery_hooks.h +29 -0
  746. package/bundle/hkp-rt/lib/include/egress.h +379 -0
  747. package/bundle/hkp-rt/lib/include/files.h +275 -0
  748. package/bundle/hkp-rt/lib/include/log_entry.h +131 -0
  749. package/bundle/hkp-rt/lib/include/mounts.h +139 -0
  750. package/bundle/hkp-rt/lib/include/origins.h +363 -0
  751. package/bundle/hkp-rt/lib/include/registry.h +119 -0
  752. package/bundle/hkp-rt/lib/include/secrets.h +414 -0
  753. package/bundle/hkp-rt/lib/include/server.h +122 -0
  754. package/bundle/hkp-rt/lib/include/service.h +190 -0
  755. package/bundle/hkp-rt/lib/include/slot_store.h +41 -0
  756. package/bundle/hkp-rt/lib/include/standalone_config.h +253 -0
  757. package/bundle/hkp-rt/lib/include/types/audio.h +3 -0
  758. package/bundle/hkp-rt/lib/include/types/data.h +152 -0
  759. package/bundle/hkp-rt/lib/include/types/message.h +56 -0
  760. package/bundle/hkp-rt/lib/include/types/models.h +154 -0
  761. package/bundle/hkp-rt/lib/include/types/owns_me.h +19 -0
  762. package/bundle/hkp-rt/lib/include/types/ringbuffer.h +86 -0
  763. package/bundle/hkp-rt/lib/include/types/types.h +33 -0
  764. package/bundle/hkp-rt/lib/include/types/validation.h +205 -0
  765. package/bundle/hkp-rt/lib/src/address.h +88 -0
  766. package/bundle/hkp-rt/lib/src/app.cpp +591 -0
  767. package/bundle/hkp-rt/lib/src/assets.cpp +686 -0
  768. package/bundle/hkp-rt/lib/src/binary_frame.h +154 -0
  769. package/bundle/hkp-rt/lib/src/common/expression.h +1481 -0
  770. package/bundle/hkp-rt/lib/src/common/inja.h +36 -0
  771. package/bundle/hkp-rt/lib/src/common/link_socket.cpp +437 -0
  772. package/bundle/hkp-rt/lib/src/common/link_socket.h +97 -0
  773. package/bundle/hkp-rt/lib/src/common/realtime_wakeup.h +120 -0
  774. package/bundle/hkp-rt/lib/src/common/stream_broadcast.h +250 -0
  775. package/bundle/hkp-rt/lib/src/common/string_util.h +31 -0
  776. package/bundle/hkp-rt/lib/src/common/websocket_client_session.cpp +217 -0
  777. package/bundle/hkp-rt/lib/src/common/websocket_client_session.h +37 -0
  778. package/bundle/hkp-rt/lib/src/common/websocket_config.h +119 -0
  779. package/bundle/hkp-rt/lib/src/common/websocket_protocol.h +32 -0
  780. package/bundle/hkp-rt/lib/src/common/websocket_server.cpp +82 -0
  781. package/bundle/hkp-rt/lib/src/common/websocket_server.h +57 -0
  782. package/bundle/hkp-rt/lib/src/common/websocket_server_listener.cpp +115 -0
  783. package/bundle/hkp-rt/lib/src/common/websocket_server_listener.h +35 -0
  784. package/bundle/hkp-rt/lib/src/common/websocket_server_session.cpp +172 -0
  785. package/bundle/hkp-rt/lib/src/common/websocket_server_session.h +53 -0
  786. package/bundle/hkp-rt/lib/src/common.h +13 -0
  787. package/bundle/hkp-rt/lib/src/coordinator_links.cpp +1142 -0
  788. package/bundle/hkp-rt/lib/src/discovery/discovery.cpp +707 -0
  789. package/bundle/hkp-rt/lib/src/discovery/discovery.h +89 -0
  790. package/bundle/hkp-rt/lib/src/http/auth.cpp +569 -0
  791. package/bundle/hkp-rt/lib/src/http/front_door.cpp +518 -0
  792. package/bundle/hkp-rt/lib/src/http/front_door.h +131 -0
  793. package/bundle/hkp-rt/lib/src/http/server.cpp +1539 -0
  794. package/bundle/hkp-rt/lib/src/http/ws_tickets.h +125 -0
  795. package/bundle/hkp-rt/lib/src/mount.h +63 -0
  796. package/bundle/hkp-rt/lib/src/process_context.h +335 -0
  797. package/bundle/hkp-rt/lib/src/process_depth.h +20 -0
  798. package/bundle/hkp-rt/lib/src/registry.cpp +233 -0
  799. package/bundle/hkp-rt/lib/src/runtime.cpp +986 -0
  800. package/bundle/hkp-rt/lib/src/runtime.h +225 -0
  801. package/bundle/hkp-rt/lib/src/runtime_host.h +180 -0
  802. package/bundle/hkp-rt/lib/src/service.cpp +315 -0
  803. package/bundle/hkp-rt/lib/src/services/asset.h +185 -0
  804. package/bundle/hkp-rt/lib/src/services/audio_encode.cpp +372 -0
  805. package/bundle/hkp-rt/lib/src/services/audio_encode.h +97 -0
  806. package/bundle/hkp-rt/lib/src/services/cache.h +362 -0
  807. package/bundle/hkp-rt/lib/src/services/cache_subservices.h +463 -0
  808. package/bundle/hkp-rt/lib/src/services/core_audio.cpp +384 -0
  809. package/bundle/hkp-rt/lib/src/services/core_audio.h +22 -0
  810. package/bundle/hkp-rt/lib/src/services/core_input.h +337 -0
  811. package/bundle/hkp-rt/lib/src/services/core_output.h +370 -0
  812. package/bundle/hkp-rt/lib/src/services/ffmpeg/detail.cpp +749 -0
  813. package/bundle/hkp-rt/lib/src/services/ffmpeg/detail.h +29 -0
  814. package/bundle/hkp-rt/lib/src/services/ffmpeg/ffmpeg.cpp +193 -0
  815. package/bundle/hkp-rt/lib/src/services/ffmpeg/ffmpeg.h +40 -0
  816. package/bundle/hkp-rt/lib/src/services/ffmpeg/types.h +120 -0
  817. package/bundle/hkp-rt/lib/src/services/fft_service.h +190 -0
  818. package/bundle/hkp-rt/lib/src/services/filesystem.h +309 -0
  819. package/bundle/hkp-rt/lib/src/services/filter.h +208 -0
  820. package/bundle/hkp-rt/lib/src/services/gather.h +272 -0
  821. package/bundle/hkp-rt/lib/src/services/hold.h +304 -0
  822. package/bundle/hkp-rt/lib/src/services/http_client.cpp +347 -0
  823. package/bundle/hkp-rt/lib/src/services/http_client.h +67 -0
  824. package/bundle/hkp-rt/lib/src/services/http_client_impl.h +45 -0
  825. package/bundle/hkp-rt/lib/src/services/http_client_template_examples.md +202 -0
  826. package/bundle/hkp-rt/lib/src/services/http_server/http_listener.cpp +130 -0
  827. package/bundle/hkp-rt/lib/src/services/http_server/http_listener.h +33 -0
  828. package/bundle/hkp-rt/lib/src/services/http_server/http_server.cpp +351 -0
  829. package/bundle/hkp-rt/lib/src/services/http_server/http_server.h +79 -0
  830. package/bundle/hkp-rt/lib/src/services/http_server/http_server_impl.cpp +230 -0
  831. package/bundle/hkp-rt/lib/src/services/http_server/http_server_impl.h +64 -0
  832. package/bundle/hkp-rt/lib/src/services/http_server/http_server_subservices.cpp +950 -0
  833. package/bundle/hkp-rt/lib/src/services/http_server/http_server_subservices.h +253 -0
  834. package/bundle/hkp-rt/lib/src/services/http_server/http_session.cpp +495 -0
  835. package/bundle/hkp-rt/lib/src/services/http_server/http_session.h +169 -0
  836. package/bundle/hkp-rt/lib/src/services/http_server/http_stream_listener.cpp +270 -0
  837. package/bundle/hkp-rt/lib/src/services/http_server/http_stream_listener.h +104 -0
  838. package/bundle/hkp-rt/lib/src/services/http_server/request_decode.h +164 -0
  839. package/bundle/hkp-rt/lib/src/services/if_service.h +210 -0
  840. package/bundle/hkp-rt/lib/src/services/ifft_service.h +198 -0
  841. package/bundle/hkp-rt/lib/src/services/injector.h +162 -0
  842. package/bundle/hkp-rt/lib/src/services/iterator.h +201 -0
  843. package/bundle/hkp-rt/lib/src/services/map.h +641 -0
  844. package/bundle/hkp-rt/lib/src/services/monitor.h +161 -0
  845. package/bundle/hkp-rt/lib/src/services/mp3_frames.h +119 -0
  846. package/bundle/hkp-rt/lib/src/services/mp4_to_wav.cpp +281 -0
  847. package/bundle/hkp-rt/lib/src/services/mp4_to_wav.h +263 -0
  848. package/bundle/hkp-rt/lib/src/services/peer_server/peer_registry.cpp +88 -0
  849. package/bundle/hkp-rt/lib/src/services/peer_server/peer_registry.h +43 -0
  850. package/bundle/hkp-rt/lib/src/services/peer_server/peer_server.h +163 -0
  851. package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_listener.cpp +133 -0
  852. package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_listener.h +46 -0
  853. package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_session.cpp +165 -0
  854. package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_session.h +44 -0
  855. package/bundle/hkp-rt/lib/src/services/peer_server/peer_ws_session.cpp +184 -0
  856. package/bundle/hkp-rt/lib/src/services/peer_server/peer_ws_session.h +55 -0
  857. package/bundle/hkp-rt/lib/src/services/receiver_session.h +172 -0
  858. package/bundle/hkp-rt/lib/src/services/root_certificates.h +3959 -0
  859. package/bundle/hkp-rt/lib/src/services/sha256_openssl.h +49 -0
  860. package/bundle/hkp-rt/lib/src/services/speech_http.h +306 -0
  861. package/bundle/hkp-rt/lib/src/services/speech_to_text.cpp +595 -0
  862. package/bundle/hkp-rt/lib/src/services/speech_to_text.h +69 -0
  863. package/bundle/hkp-rt/lib/src/services/static.cpp +45 -0
  864. package/bundle/hkp-rt/lib/src/services/static.h +41 -0
  865. package/bundle/hkp-rt/lib/src/services/stopper.h +72 -0
  866. package/bundle/hkp-rt/lib/src/services/sub_service.h +286 -0
  867. package/bundle/hkp-rt/lib/src/services/text_generation.cpp +1132 -0
  868. package/bundle/hkp-rt/lib/src/services/text_generation.h +71 -0
  869. package/bundle/hkp-rt/lib/src/services/text_to_speech.cpp +853 -0
  870. package/bundle/hkp-rt/lib/src/services/text_to_speech.h +97 -0
  871. package/bundle/hkp-rt/lib/src/services/timer.h +300 -0
  872. package/bundle/hkp-rt/lib/src/services/tracks.h +397 -0
  873. package/bundle/hkp-rt/lib/src/services/tts_inflect.cpp +149 -0
  874. package/bundle/hkp-rt/lib/src/services/tts_inflect.h +82 -0
  875. package/bundle/hkp-rt/lib/src/services/wav_reader.h +213 -0
  876. package/bundle/hkp-rt/lib/src/services/websocket_client_service.h +182 -0
  877. package/bundle/hkp-rt/lib/src/services/websocket_reader.h +148 -0
  878. package/bundle/hkp-rt/lib/src/services/websocket_server_service.h +196 -0
  879. package/bundle/hkp-rt/lib/src/services/websocket_writer.cpp +641 -0
  880. package/bundle/hkp-rt/lib/src/services/websocket_writer.h +117 -0
  881. package/bundle/hkp-rt/lib/src/sub_runtime.cpp +234 -0
  882. package/bundle/hkp-rt/lib/src/sub_runtime.h +160 -0
  883. package/bundle/hkp-rt/lib/src/types/data.cpp +237 -0
  884. package/bundle/hkp-rt/lib/src/types/message.cpp +148 -0
  885. package/bundle/hkp-rt/lib/src/types/models.cpp +83 -0
  886. package/bundle/hkp-rt/lib/src/types/ringbuffer.cpp +336 -0
  887. package/bundle/hkp-rt/lib/src/uuid.h +12 -0
  888. package/bundle/readymade-mcp-commits.json +5 -0
  889. package/dist/cli.d.ts +3 -0
  890. package/dist/cli.d.ts.map +1 -0
  891. package/dist/cli.js +79 -0
  892. package/dist/git.d.ts +8 -0
  893. package/dist/git.d.ts.map +1 -0
  894. package/dist/git.js +53 -0
  895. package/dist/http.d.ts +17 -0
  896. package/dist/http.d.ts.map +1 -0
  897. package/dist/http.js +130 -0
  898. package/dist/index.d.ts +7 -0
  899. package/dist/index.d.ts.map +1 -0
  900. package/dist/index.js +6 -0
  901. package/dist/repository.d.ts +49 -0
  902. package/dist/repository.d.ts.map +1 -0
  903. package/dist/repository.js +452 -0
  904. package/dist/schemas.d.ts +156 -0
  905. package/dist/schemas.d.ts.map +1 -0
  906. package/dist/schemas.js +43 -0
  907. package/dist/server.d.ts +4 -0
  908. package/dist/server.d.ts.map +1 -0
  909. package/dist/server.js +83 -0
  910. package/dist/types.d.ts +63 -0
  911. package/dist/types.d.ts.map +1 -0
  912. package/dist/types.js +20 -0
  913. package/dist/validation.d.ts +9 -0
  914. package/dist/validation.d.ts.map +1 -0
  915. package/dist/validation.js +101 -0
  916. package/package.json +52 -4
@@ -0,0 +1,1249 @@
1
+ /**
2
+ * Service Documentation
3
+ * Service ID: text-generation
4
+ * Service Name: Text Generation
5
+ * Runtime: hkp-node
6
+ * Modes: none (the backend is configuration, not a mode)
7
+ * Key Config: backend (anthropic|server), apiKey (write-only), serverUrl
8
+ * (empty = the backend's own address), model, systemPrompt,
9
+ * temperature, topP, topK, maxTokens, timeoutSec, stream,
10
+ * thinking, thinkingBudgetTokens, effort, jsonSchema
11
+ * IO: in=String (the prompt) or JSON ({prompt} | {text} | {messages: [...]}),
12
+ * optionally carrying images ({meta, binary} | {images: [...]})
13
+ * out=the result once the model has answered, shaped
14
+ * { text, json?, thinking?, model, durationMs,
15
+ * usage: { promptTokens, completionTokens } }
16
+ *
17
+ * The node implementation of the `text-generation` concept hkp-python and
18
+ * hkp-rt also provide, sharing their state contract and output shape so a board
19
+ * can move the service between runtimes and keep its UI panel.
20
+ *
21
+ * Two backends, and which one a board wants is a question of where the model
22
+ * is:
23
+ *
24
+ * - `anthropic` calls a hosted API. The one that works on a board deployed to
25
+ * a coordinator with nobody watching, since there is no model to run.
26
+ * - `server` talks to an OpenAI-compatible server on the same machine
27
+ * (llama-server, Ollama, vLLM, LM Studio), the same backend hkp-python and
28
+ * hkp-rt spell `server` — so a board can be developed against a local model
29
+ * and deployed against a hosted one by changing two fields.
30
+ *
31
+ * Node cannot load a GGUF in-process the way those two can, which is why there
32
+ * is no `local` here: on this runtime, local means a server next door.
33
+ *
34
+ * Three things `anthropic` adds, which the local backends have no equivalent
35
+ * for — except `jsonSchema`, which `server` carries too where the server
36
+ * implements it (llama.cpp does, by turning the schema into a grammar):
37
+ *
38
+ * - `jsonSchema` constrains the answer to a shape. Sent as a single forced
39
+ * tool, or as structured outputs to the models that refuse a forced tool,
40
+ * and the parsed object is emitted as `json` beside the `text`.
41
+ * - Images in the input become vision parts, so a scan that arrives from
42
+ * `http-client` or an attachment can be read directly.
43
+ * - `thinking` and `effort` say how much the model reasons, reported in the
44
+ * `thinking` field the shared output contract already carries.
45
+ *
46
+ * The Claude models disagree on how each of those is said, so the request is
47
+ * built from what the model accepts (`claudeModels.ts`): the board states what
48
+ * it wants and keeps working when its model changes.
49
+ *
50
+ * The API key is write-only in the same way `smtp-email` treats its password:
51
+ * `configure` accepts it, `getState` never gives it back. It is a
52
+ * `{{secret.…}}` reference, resolved from the vault of whoever runs the board,
53
+ * and never read from the runtime's environment: on a server several people
54
+ * share, a key in the environment would be the operator's, spent by everyone.
55
+ */
56
+ import { resolveCredential } from "../secrets";
57
+ import { refusalIn } from "../egress";
58
+ import {
59
+ ClaudeModel,
60
+ EFFORTS,
61
+ Effort,
62
+ atMost,
63
+ claudeModel,
64
+ knownClaudeModel,
65
+ structuredSchema,
66
+ } from "./claudeModels";
67
+ import {
68
+ HostedService,
69
+ JsonRecord,
70
+ ProcessContext,
71
+ RuntimeHost,
72
+ RuntimeNotification,
73
+ ServiceConfiguration,
74
+ ServiceRegistryEntry,
75
+ } from "../types";
76
+
77
+ export const textGenerationDescriptor: ServiceRegistryEntry = {
78
+ serviceId: "text-generation",
79
+ serviceName: "Text Generation",
80
+ version: "v1",
81
+ capabilities: [],
82
+ };
83
+
84
+ /** Backends this runtime can talk to. */
85
+ const BACKENDS = ["anthropic", "server"];
86
+
87
+ /**
88
+ * Where each backend goes when a board does not say.
89
+ *
90
+ * The address is one field, `serverUrl`, because only one is ever in use —
91
+ * two would mean a board carrying the address of a service it is not talking
92
+ * to, which reads like configuration and is not. Left empty it means "wherever
93
+ * this backend lives", which is what makes switching backends work: a default
94
+ * is resolved when the request is made rather than written into the board, so
95
+ * nothing stale is carried across the switch.
96
+ */
97
+ const DEFAULT_URLS: Record<string, string> = {
98
+ anthropic: "https://api.anthropic.com",
99
+ // Where llama-server and friends listen; the port hkp-python uses too.
100
+ server: "http://127.0.0.1:8081",
101
+ };
102
+ const DEFAULT_MODEL = "claude-sonnet-5";
103
+ const DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant";
104
+ const DEFAULT_TEMPERATURE = 0.7;
105
+ const DEFAULT_TOP_P = 0.95;
106
+ const DEFAULT_TOP_K = 20;
107
+ const DEFAULT_MAX_TOKENS = 1024;
108
+ const DEFAULT_TIMEOUT_SEC = 300;
109
+ const DEFAULT_THINKING_BUDGET_TOKENS = 1024;
110
+
111
+ /** Pinned: the API is versioned by header, and an unpinned client breaks. */
112
+ const ANTHROPIC_VERSION = "2023-06-01";
113
+ /** The Claude API's stop reasons that mean the answer did not simply end. */
114
+ const ANTHROPIC_FINISH_REASONS: Record<string, string> = {
115
+ max_tokens: "length",
116
+ refusal: "refusal",
117
+ };
118
+
119
+ /**
120
+ * The tool a schema-constrained answer is asked for through.
121
+ *
122
+ * A forced tool call is the API's mechanism for constraining output to a shape:
123
+ * the schema becomes the tool's parameters, and the model's arguments are the
124
+ * answer. The name is arbitrary but reaches the model, so it reads as an
125
+ * instruction rather than as an internal identifier.
126
+ */
127
+ const SCHEMA_TOOL_NAME = "respond";
128
+
129
+ /** Reasoning inline in the answer, which is how some chat templates emit it. */
130
+ const THINK_OPEN = "<think>";
131
+ const THINK_CLOSE = "</think>";
132
+
133
+ /** Notifications are throttled to this, which is plenty for a live view. */
134
+ const STREAM_NOTIFY_INTERVAL_MS = 50;
135
+
136
+ type Message = { role: string; content: unknown };
137
+
138
+ type Result = {
139
+ text: string;
140
+ json?: unknown;
141
+ thinking?: string;
142
+ /** Only when the answer did not simply end: "length", "content_filter", … */
143
+ finishReason?: string;
144
+ model: string;
145
+ durationMs: number;
146
+ usage: { promptTokens: number; completionTokens: number };
147
+ };
148
+
149
+ type Notify = (payload: unknown, instanceId?: string) => void;
150
+
151
+ function isNumber(value: unknown): value is number {
152
+ return typeof value === "number" && Number.isFinite(value);
153
+ }
154
+
155
+ /** An image part the API accepts, from bytes plus what they are. */
156
+ function imagePart(bytes: Uint8Array, mediaType: string): JsonRecord {
157
+ return {
158
+ type: "image",
159
+ source: {
160
+ type: "base64",
161
+ media_type: mediaType,
162
+ data: Buffer.from(bytes).toString("base64"),
163
+ },
164
+ };
165
+ }
166
+
167
+ export class TextGenerationService implements HostedService {
168
+ readonly serviceId = textGenerationDescriptor.serviceId;
169
+ readonly serviceName = textGenerationDescriptor.serviceName;
170
+ readonly version = textGenerationDescriptor.version;
171
+ readonly capabilities = textGenerationDescriptor.capabilities;
172
+ readonly uuid: string;
173
+
174
+ private host: RuntimeHost | null = null;
175
+ private backend = "anthropic";
176
+ /**
177
+ * A `{{secret.<alias>}}` reference, or a literal key for a runtime
178
+ * configured from a file. Never a value resolved from a reference: it is
179
+ * reported as it stands, so a board saved from this service names its
180
+ * credential rather than carrying it.
181
+ */
182
+ private apiKey = "";
183
+ /** Empty means the backend's own address; see DEFAULT_URLS. */
184
+ private serverUrl = "";
185
+ private model = DEFAULT_MODEL;
186
+ private systemPrompt = DEFAULT_SYSTEM_PROMPT;
187
+ private temperature = DEFAULT_TEMPERATURE;
188
+ private topP = DEFAULT_TOP_P;
189
+ private topK = DEFAULT_TOP_K;
190
+ private maxTokens = DEFAULT_MAX_TOKENS;
191
+ private timeoutSec = DEFAULT_TIMEOUT_SEC;
192
+ private stream = true;
193
+ // null = off and unsaid, matching the local backends' three-state field.
194
+ private thinking: boolean | null = null;
195
+ private thinkingBudgetTokens = DEFAULT_THINKING_BUDGET_TOKENS;
196
+ /** Empty means the model's own default. Claude only. */
197
+ private effort: Effort | "" = "";
198
+ private jsonSchema: JsonRecord | null = null;
199
+ private status = "idle";
200
+ /**
201
+ * Why the last attempt failed, kept rather than only notified.
202
+ *
203
+ * A notification is gone the moment nobody is looking, and the board this
204
+ * runs on is meant to run with nobody looking. Keeping it in state means the
205
+ * reason is still there when somebody comes back and opens the panel.
206
+ */
207
+ private lastError = "";
208
+
209
+ constructor(config: ServiceConfiguration) {
210
+ this.uuid = config.uuid;
211
+ if (config.state) {
212
+ this.configure(config.state);
213
+ }
214
+ }
215
+
216
+ setHost(host: RuntimeHost): void {
217
+ this.host = host;
218
+ }
219
+
220
+ getState(): JsonRecord {
221
+ const state: JsonRecord = {
222
+ backend: this.backend,
223
+ // Reported as configured. Where that is a reference it names a secret
224
+ // and holds nothing, and where it is a literal key it is one the board
225
+ // already carried; either way there is nothing here to hide.
226
+ apiKey: this.apiKey,
227
+ apiKeyConfigured: this.hasKey(),
228
+ serverUrl: this.serverUrl,
229
+ // Read-only, and the point of it: which address this configuration
230
+ // actually reaches, without having to know how a backend spells one.
231
+ endpoint: this.endpoint(),
232
+ model: this.model,
233
+ systemPrompt: this.systemPrompt,
234
+ temperature: this.temperature,
235
+ topP: this.topP,
236
+ topK: this.topK,
237
+ maxTokens: this.maxTokens,
238
+ timeoutSec: this.timeoutSec,
239
+ stream: this.stream,
240
+ thinking: this.thinking,
241
+ thinkingBudgetTokens: this.thinkingBudgetTokens,
242
+ effort: this.effort,
243
+ jsonSchema: this.jsonSchema,
244
+ status: this.status,
245
+ error: this.lastError,
246
+ };
247
+
248
+ // A setting this service overrides has to say so. The values stay whatever
249
+ // the board set — rewriting them would lose the board author's intent, and
250
+ // saving would then persist a value they never chose — but a field reading
251
+ // one way over a request that says another is the UI telling a lie the
252
+ // service is the only thing in a position to correct.
253
+ const meta: JsonRecord = {};
254
+ if (this.jsonSchema) {
255
+ meta.stream = {
256
+ type: "boolean",
257
+ data: {
258
+ note:
259
+ "Off while a JSON schema is set: half a JSON object is of no use " +
260
+ "to anything, so the answer is waited for whole.",
261
+ },
262
+ };
263
+ }
264
+ if (this.backend === "anthropic") {
265
+ // What this model does with the settings, as far as is known without
266
+ // asking — the last lookup's answer, or the table's.
267
+ const model = knownClaudeModel(this.base(), this.model);
268
+
269
+ // Kept for the `server` backend, which uses them, and for the older
270
+ // Claude models that still take temperature and top_k.
271
+ const unsent = (note: string) => ({ type: "number", data: { note } });
272
+ if (model.sampling) {
273
+ meta.topP = unsent(
274
+ "Not sent to this backend, which rejects a request carrying both " +
275
+ "temperature and top_p. Temperature is the one sent.",
276
+ );
277
+ } else {
278
+ const refused = unsent(
279
+ `Not sent: ${this.model} uses its own sampling and refuses a ` +
280
+ "request that sets it.",
281
+ );
282
+ meta.temperature = refused;
283
+ meta.topP = refused;
284
+ meta.topK = refused;
285
+ }
286
+
287
+ const effortNote = !this.effort
288
+ ? undefined
289
+ : model.efforts.includes(this.effort)
290
+ ? undefined
291
+ : `Not sent: ${this.model} takes no effort level "${this.effort}".`;
292
+ meta.effort = {
293
+ type: "enum",
294
+ data: {
295
+ options: ["", ...model.efforts],
296
+ ...(effortNote ? { note: effortNote } : {}),
297
+ },
298
+ };
299
+ if (model.thinking === "adaptive") {
300
+ meta.thinkingBudgetTokens = {
301
+ type: "number",
302
+ data: {
303
+ note:
304
+ "Not used by this model: it decides how long to think, and " +
305
+ "effort sets how much. Its thinking counts against maxTokens.",
306
+ },
307
+ };
308
+ if (this.thinking === false && !this.thinkingFor(model)) {
309
+ meta.thinking = {
310
+ type: "boolean",
311
+ data: {
312
+ note: model.thinkingOff
313
+ ? `Not sent at effort "${this.effort}": this model thinks at ` +
314
+ "that effort regardless."
315
+ : "Not sent: this model always thinks. Lower effort to have " +
316
+ "it think less.",
317
+ },
318
+ };
319
+ }
320
+ }
321
+ } else if (this.effort) {
322
+ meta.effort = {
323
+ type: "text",
324
+ data: { note: "Not sent to this backend: effort is a Claude setting." },
325
+ };
326
+ }
327
+ if (Object.keys(meta).length > 0) {
328
+ state.__meta__ = meta;
329
+ }
330
+
331
+ return state;
332
+ }
333
+
334
+ configure(config: JsonRecord): JsonRecord {
335
+ if (typeof config.backend === "string" && BACKENDS.includes(config.backend)) {
336
+ this.backend = config.backend;
337
+ }
338
+ // Nothing masks this any more, so an empty string is not something a UI
339
+ // round-trips back — it means what it says, and clears the key.
340
+ if (typeof config.apiKey === "string") {
341
+ this.apiKey = config.apiKey;
342
+ }
343
+ if (typeof config.serverUrl === "string") {
344
+ // An empty string is meaningful — it hands the address back to the
345
+ // backend — so unlike the other strings it is not ignored.
346
+ this.serverUrl = config.serverUrl.replace(/\/+$/, "");
347
+ }
348
+ if (typeof config.model === "string" && config.model) {
349
+ this.model = config.model;
350
+ }
351
+ if (typeof config.systemPrompt === "string") {
352
+ this.systemPrompt = config.systemPrompt;
353
+ }
354
+ if (isNumber(config.temperature)) {
355
+ this.temperature = config.temperature;
356
+ }
357
+ if (isNumber(config.topP)) {
358
+ this.topP = config.topP;
359
+ }
360
+ if (isNumber(config.topK)) {
361
+ this.topK = Math.trunc(config.topK);
362
+ }
363
+ if (isNumber(config.maxTokens) && config.maxTokens > 0) {
364
+ this.maxTokens = Math.trunc(config.maxTokens);
365
+ }
366
+ if (isNumber(config.timeoutSec) && config.timeoutSec > 0) {
367
+ this.timeoutSec = config.timeoutSec;
368
+ }
369
+ if (typeof config.stream === "boolean") {
370
+ this.stream = config.stream;
371
+ }
372
+ if ("thinking" in config) {
373
+ const value = config.thinking;
374
+ if (typeof value === "boolean" || value === null) {
375
+ this.thinking = value;
376
+ }
377
+ }
378
+ if (isNumber(config.thinkingBudgetTokens) && config.thinkingBudgetTokens > 0) {
379
+ this.thinkingBudgetTokens = Math.trunc(config.thinkingBudgetTokens);
380
+ }
381
+ if (
382
+ config.effort === "" ||
383
+ (typeof config.effort === "string" && (EFFORTS as readonly string[]).includes(config.effort))
384
+ ) {
385
+ this.effort = config.effort as Effort | "";
386
+ }
387
+ if ("jsonSchema" in config) {
388
+ const value = config.jsonSchema;
389
+ if (value === null || value === "") {
390
+ this.jsonSchema = null;
391
+ } else if (typeof value === "object" && !Array.isArray(value)) {
392
+ this.jsonSchema = value as JsonRecord;
393
+ } else if (typeof value === "string") {
394
+ // Boards written by hand tend to carry the schema as a JSON string.
395
+ try {
396
+ const parsed = JSON.parse(value);
397
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
398
+ this.jsonSchema = parsed as JsonRecord;
399
+ }
400
+ } catch {
401
+ // Left as it was: a schema that does not parse is a board bug worth
402
+ // reporting when it runs, not a reason to silently drop the old one.
403
+ }
404
+ }
405
+ }
406
+ return this.getState();
407
+ }
408
+
409
+ /**
410
+ * Answers with what the model said.
411
+ *
412
+ * The runtime awaits each service, so generation taking seconds to minutes is
413
+ * a reason to wait rather than a reason to leave. Returning the answer is
414
+ * what lets the services after this one be ordinary services — a `join`
415
+ * merging the answer with the question, a `put-artifact` filing it — instead
416
+ * of each having to be told which run it belongs to.
417
+ */
418
+ async process(input: unknown, notify: Notify): Promise<unknown> {
419
+ if (input === null || input === undefined) {
420
+ return null;
421
+ }
422
+
423
+ const messages = this.toMessages(input);
424
+ if (!messages) {
425
+ return this.fail(
426
+ notify,
427
+ "text-generation expects String input or JSON with 'prompt', 'text', or 'messages'",
428
+ );
429
+ }
430
+
431
+ const { value: key, problem } = resolveCredential(
432
+ this.host?.secrets?.(),
433
+ this.apiKey,
434
+ this.endpoint(),
435
+ );
436
+ // A credential that was named and could not be produced is a failure
437
+ // whichever backend asked for it: the request would go out unauthenticated
438
+ // and fail somewhere far away instead.
439
+ if (problem) {
440
+ return this.fail(notify, problem);
441
+ }
442
+ // A server next door is reached by address, not by credential — only the
443
+ // hosted API has one to be missing.
444
+ if (!key && this.backend === "anthropic") {
445
+ return this.fail(
446
+ notify,
447
+ "no API key — set apiKey to a secret, such as {{secret.anthropic.key}}",
448
+ );
449
+ }
450
+
451
+ return this.generate(messages, key, notify);
452
+ }
453
+
454
+ destroy(): void {
455
+ this.host = null;
456
+ }
457
+
458
+ // ── Private ───────────────────────────────────────────────────────────────
459
+
460
+ /**
461
+ * Whether a key is available at all, without producing one.
462
+ *
463
+ * Answering "is this configured" must not resolve a secret: it is asked
464
+ * every time state is reported, and a value produced to compute a boolean is
465
+ * a value that existed for no reason.
466
+ */
467
+ private hasKey(): boolean {
468
+ return !!this.apiKey;
469
+ }
470
+
471
+ /** The backend's address, before any API path. */
472
+ private base(): string {
473
+ return this.serverUrl || DEFAULT_URLS[this.backend] || "";
474
+ }
475
+
476
+ /**
477
+ * Where the request goes.
478
+ *
479
+ * A local server is given as a bare origin and serves the API at its root,
480
+ * so the version belongs in the path. A hosted provider is given the way its
481
+ * documentation gives it — `https://…/api/v1` — and appending another `/v1`
482
+ * to that reaches nothing. Both forms are accepted rather than one being
483
+ * declared correct, because both are what the two kinds of server hand out.
484
+ */
485
+ private endpoint(): string {
486
+ const base = this.base();
487
+ if (this.backend !== "server") {
488
+ return `${base}/v1/messages`;
489
+ }
490
+ return /\/v\d+$/.test(base)
491
+ ? `${base}/chat/completions`
492
+ : `${base}/v1/chat/completions`;
493
+ }
494
+
495
+ private headers(apiKey: string): Record<string, string> {
496
+ if (this.backend === "server") {
497
+ // A local server usually wants no credential at all; the ones that do
498
+ // take a bearer token.
499
+ return {
500
+ "content-type": "application/json",
501
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
502
+ };
503
+ }
504
+ return {
505
+ "content-type": "application/json",
506
+ "x-api-key": apiKey,
507
+ "anthropic-version": ANTHROPIC_VERSION,
508
+ };
509
+ }
510
+
511
+ private async generate(
512
+ messages: Message[],
513
+ apiKey: string,
514
+ notify: Notify,
515
+ ): Promise<unknown> {
516
+ const controller = new AbortController();
517
+ const timer = setTimeout(
518
+ () => controller.abort(),
519
+ Math.round(this.timeoutSec * 1000),
520
+ );
521
+ this.setStatus(notify, "generating");
522
+ const started = Date.now();
523
+
524
+ const server = this.backend === "server";
525
+
526
+ try {
527
+ const model = server
528
+ ? null
529
+ : await claudeModel(this.base(), this.model, this.headers(apiKey));
530
+ const structured = !!model && this.usesStructuredOutputs(model);
531
+ const response = await fetch(this.endpoint(), {
532
+ method: "POST",
533
+ headers: this.headers(apiKey),
534
+ body: JSON.stringify(
535
+ model ? this.requestBody(messages, model) : this.serverRequestBody(messages),
536
+ ),
537
+ signal: controller.signal,
538
+ });
539
+
540
+ if (!response.ok) {
541
+ const detail = (await response.text()).slice(0, 400);
542
+ return this.fail(
543
+ notify,
544
+ `${server ? "server" : "API"} returned HTTP ${response.status}: ${detail}`,
545
+ );
546
+ }
547
+
548
+ const message = this.streaming()
549
+ ? await (server
550
+ ? this.readServerStream(response, notify)
551
+ : this.readStream(response, notify))
552
+ : ((await response.json()) as JsonRecord);
553
+
554
+ const result = server
555
+ ? this.toServerResult(message, started)
556
+ : this.toResult(message, started, structured);
557
+
558
+ // Handing an empty answer onward is worse than failing: the services
559
+ // after this one cannot tell it from a real one, so a board that settles
560
+ // its work at the end would mark the item done having produced nothing.
561
+ // Stopping here leaves it exactly where a failed run should leave it.
562
+ if (!result.text && result.json === undefined) {
563
+ return this.fail(notify, this.emptyAnswer(result.finishReason, server));
564
+ }
565
+
566
+ this.setStatus(notify, "idle");
567
+ notify(result);
568
+ return result;
569
+ } catch (err) {
570
+ // Refused here, not unreachable: telling the person to start a server
571
+ // would be advice about the wrong problem.
572
+ const refused = refusalIn(err);
573
+ if (refused) {
574
+ return this.fail(notify, refused.message);
575
+ }
576
+ // A connection that never opened, as distinct from anything else that
577
+ // went wrong in here: fetch reports those as a TypeError carrying the
578
+ // socket error as its cause. Claiming a wider set would answer "the
579
+ // server is not running" to a question that was never asked.
580
+ if (server && err instanceof TypeError && (err as Error).cause) {
581
+ // "fetch failed" says nothing about what to do; a board pointed at a
582
+ // server nobody started is by far the likeliest way to get here.
583
+ return this.fail(
584
+ notify,
585
+ `no OpenAI-compatible server reachable at ${this.endpoint()} — start one, e.g.: ` +
586
+ `llama-server -m model.gguf --port ${port(this.endpoint())}`,
587
+ );
588
+ }
589
+ const reason =
590
+ err instanceof Error && err.name === "AbortError"
591
+ ? `no answer within ${this.timeoutSec}s`
592
+ : err instanceof Error
593
+ ? err.message
594
+ : String(err);
595
+ return this.fail(notify, `generation failed: ${reason}`);
596
+ } finally {
597
+ clearTimeout(timer);
598
+ }
599
+ }
600
+
601
+ /**
602
+ * Whether this call streams.
603
+ *
604
+ * A schema-constrained answer does not: half of a JSON object is of no use
605
+ * to anything, so streaming would buy partial output nobody can render.
606
+ */
607
+ private streaming(): boolean {
608
+ return this.stream && !this.jsonSchema;
609
+ }
610
+
611
+ /** Whether a schema goes as structured outputs rather than a forced tool. */
612
+ private usesStructuredOutputs(model: ClaudeModel): boolean {
613
+ return !!this.jsonSchema && !model.forcedTool && model.structuredOutputs;
614
+ }
615
+
616
+ /** The effort sent: the board's, where the model takes that level. */
617
+ private effortFor(model: ClaudeModel): Effort | undefined {
618
+ return this.effort && model.efforts.includes(this.effort) ? this.effort : undefined;
619
+ }
620
+
621
+ /**
622
+ * How the board's `thinking` is said to this model, if at all.
623
+ *
624
+ * `true` asks for reasoning, `false` asks for none, and null leaves the
625
+ * model to its default — which on current models is to think. Saying
626
+ * nothing is always the fallback, because it is the one form every model
627
+ * accepts: a model that cannot be told not to think is not sent the word.
628
+ */
629
+ private thinkingFor(model: ClaudeModel): JsonRecord | undefined {
630
+ if (model.thinking === "budget") {
631
+ return this.thinking === true
632
+ ? { type: "enabled", budget_tokens: this.thinkingBudgetTokens }
633
+ : undefined;
634
+ }
635
+ if (model.thinking !== "adaptive") {
636
+ return undefined;
637
+ }
638
+ if (this.thinking === true) {
639
+ return { type: "adaptive" };
640
+ }
641
+ if (this.thinking === false && model.thinkingOff) {
642
+ const effort = this.effortFor(model) ?? model.defaultEffort;
643
+ if (!model.thinkingOffUpTo || atMost(effort, model.thinkingOffUpTo)) {
644
+ return { type: model.thinkingOff };
645
+ }
646
+ }
647
+ return undefined;
648
+ }
649
+
650
+ private requestBody(messages: Message[], model: ClaudeModel): JsonRecord {
651
+ const thinking = this.thinkingFor(model);
652
+ // A budget is drawn from the same tokens as the answer, so one that meets
653
+ // or exceeds them leaves nothing to answer with — the API rejects that
654
+ // outright. Lift the ceiling rather than fail the call. Adaptive thinking
655
+ // has no budget to compare, which is why a board's maxTokens has to leave
656
+ // room for it.
657
+ const maxTokens =
658
+ thinking?.type === "enabled"
659
+ ? Math.max(this.maxTokens, this.thinkingBudgetTokens + 1)
660
+ : this.maxTokens;
661
+
662
+ const body: JsonRecord = {
663
+ model: this.model,
664
+ max_tokens: maxTokens,
665
+ messages,
666
+ stream: this.streaming(),
667
+ };
668
+ if (this.systemPrompt) {
669
+ // A system prompt is a parameter here, not a message with a role.
670
+ body.system = this.systemPrompt;
671
+ }
672
+ if (thinking) {
673
+ body.thinking = thinking;
674
+ } else if (model.sampling) {
675
+ // Only to the models that still take them: current Claude models accept
676
+ // only their defaults and refuse any other value. Sampling is fixed
677
+ // while reasoning, so a model that thinks is sent none. Not `top_p` as
678
+ // well: these models refuse a request carrying both it and
679
+ // `temperature`, and temperature is the one a board that touched either
680
+ // is far more likely to have meant.
681
+ body.temperature = this.temperature;
682
+ body.top_k = this.topK;
683
+ }
684
+ const outputConfig: JsonRecord = {};
685
+ const effort = this.effortFor(model);
686
+ if (effort) {
687
+ outputConfig.effort = effort;
688
+ }
689
+ if (this.jsonSchema) {
690
+ if (this.usesStructuredOutputs(model)) {
691
+ // The models that refuse a forced tool take the schema as the answer's
692
+ // format instead, in the stricter dialect that requires.
693
+ outputConfig.format = {
694
+ type: "json_schema",
695
+ schema: structuredSchema(this.jsonSchema),
696
+ };
697
+ } else {
698
+ body.tools = [
699
+ {
700
+ name: SCHEMA_TOOL_NAME,
701
+ description: "Return the answer in the required shape.",
702
+ input_schema: this.jsonSchema,
703
+ },
704
+ ];
705
+ body.tool_choice = { type: "tool", name: SCHEMA_TOOL_NAME };
706
+ }
707
+ }
708
+ if (Object.keys(outputConfig).length) {
709
+ body.output_config = outputConfig;
710
+ }
711
+ return body;
712
+ }
713
+
714
+ /**
715
+ * The same call, in the shape an OpenAI-compatible server expects.
716
+ *
717
+ * The differences from `requestBody` are all vocabulary rather than meaning:
718
+ * the system prompt is a message rather than a parameter, sampling is
719
+ * snake_case, and a schema is asked for as a response format rather than as
720
+ * a forced tool. `top_k` and `chat_template_kwargs` are llama-server
721
+ * extensions, sent the way hkp-python sends them so one board configuration
722
+ * drives either runtime.
723
+ */
724
+ private serverRequestBody(messages: Message[]): JsonRecord {
725
+ const body: JsonRecord = {
726
+ messages: this.withSystemPrompt(messages).map(openAiMessage),
727
+ temperature: this.temperature,
728
+ top_p: this.topP,
729
+ top_k: this.topK,
730
+ max_tokens: this.maxTokens,
731
+ stream: this.streaming(),
732
+ };
733
+ if (this.model) {
734
+ body.model = this.model;
735
+ }
736
+ if (this.thinking !== null) {
737
+ // Only sent when a board said something, so a server whose template does
738
+ // not know the flag never sees it.
739
+ body.chat_template_kwargs = { enable_thinking: this.thinking };
740
+ }
741
+ if (this.jsonSchema) {
742
+ // No `strict`: under its OpenAI meaning it additionally requires every
743
+ // property to be listed in `required` and `additionalProperties: false`,
744
+ // which contradicts the schemas boards actually write — a shape whose
745
+ // fields are filled in only when the source says them. The schema is
746
+ // still enforced; `strict` governs a stricter reading of the schema
747
+ // itself, not whether it applies.
748
+ body.response_format = {
749
+ type: "json_schema",
750
+ json_schema: {
751
+ name: SCHEMA_TOOL_NAME,
752
+ schema: this.jsonSchema,
753
+ },
754
+ };
755
+ }
756
+ return body;
757
+ }
758
+
759
+ /** A system prompt travels as a message here, and only if there isn't one. */
760
+ private withSystemPrompt(messages: Message[]): Message[] {
761
+ if (!this.systemPrompt || messages.some((m) => m?.role === "system")) {
762
+ return messages;
763
+ }
764
+ return [{ role: "system", content: this.systemPrompt }, ...messages];
765
+ }
766
+
767
+ /** Reduces a chat-completions response to the shared output contract. */
768
+ private toServerResult(response: JsonRecord, started: number): Result {
769
+ const choices = Array.isArray(response.choices)
770
+ ? (response.choices as JsonRecord[])
771
+ : [];
772
+ const message = (choices[0]?.message ?? {}) as JsonRecord;
773
+ const finishReason =
774
+ typeof choices[0]?.finish_reason === "string"
775
+ ? choices[0].finish_reason
776
+ : "";
777
+ const { text, thinking } = splitThinking(
778
+ typeof message.content === "string" ? message.content : "",
779
+ typeof message.reasoning_content === "string"
780
+ ? message.reasoning_content
781
+ : "",
782
+ );
783
+
784
+ const usage = (response.usage ?? {}) as JsonRecord;
785
+ const result: Result = {
786
+ text,
787
+ model: typeof response.model === "string" ? response.model : this.model,
788
+ durationMs: Date.now() - started,
789
+ usage: {
790
+ promptTokens: isNumber(usage.prompt_tokens) ? usage.prompt_tokens : 0,
791
+ completionTokens: isNumber(usage.completion_tokens)
792
+ ? usage.completion_tokens
793
+ : 0,
794
+ },
795
+ };
796
+ if (thinking) {
797
+ result.thinking = thinking;
798
+ }
799
+ // "stop" is the answer ending because it was finished; anything else is
800
+ // the answer ending for a reason a board should be able to see.
801
+ if (finishReason && finishReason !== "stop") {
802
+ result.finishReason = finishReason;
803
+ }
804
+ // Only worth reporting when there is an answer to hand on anyway: an
805
+ // empty one is a failure, and `generate` says so rather than warning here
806
+ // and passing it down as well.
807
+ if (this.jsonSchema && text) {
808
+ try {
809
+ result.json = JSON.parse(text);
810
+ } catch {
811
+ // Two very different causes, and saying the wrong one sends whoever
812
+ // reads this looking in the wrong place.
813
+ this.host?.log("warn", "service.degraded", {
814
+ message:
815
+ finishReason === "length"
816
+ ? `answer was cut off at maxTokens (${this.maxTokens}) before it ` +
817
+ "was valid JSON — raise maxTokens, or set thinking: false if " +
818
+ "the model reasons before answering"
819
+ : "answer did not parse as JSON — this server may not implement " +
820
+ "response_format: json_schema",
821
+ });
822
+ }
823
+ }
824
+ return result;
825
+ }
826
+
827
+ /**
828
+ * Accumulates a streamed chat-completions response into the shape the
829
+ * non-streamed path produces, notifying `{streamText}` on the way — the same
830
+ * shape the anthropic path and hkp-python both use, so one UI renders any of
831
+ * them.
832
+ */
833
+ private async readServerStream(
834
+ response: Response,
835
+ notify: Notify,
836
+ ): Promise<JsonRecord> {
837
+ let text = "";
838
+ let thinking = "";
839
+ let model = this.model;
840
+ let promptTokens = 0;
841
+ let completionTokens = 0;
842
+ let finishReason = "";
843
+ let lastNotifiedAt = 0;
844
+
845
+ for await (const event of readEvents(response)) {
846
+ if (typeof event.model === "string") {
847
+ model = event.model;
848
+ }
849
+ // Sent on the final chunk when the server was asked for usage, and
850
+ // absent otherwise; either way the last one seen is the total.
851
+ const usage = (event.usage ?? {}) as JsonRecord;
852
+ if (isNumber(usage.prompt_tokens)) {
853
+ promptTokens = usage.prompt_tokens;
854
+ }
855
+ if (isNumber(usage.completion_tokens)) {
856
+ completionTokens = usage.completion_tokens;
857
+ }
858
+
859
+ const choices = Array.isArray(event.choices)
860
+ ? (event.choices as JsonRecord[])
861
+ : [];
862
+ if (typeof choices[0]?.finish_reason === "string") {
863
+ finishReason = choices[0].finish_reason;
864
+ }
865
+ const delta = (choices[0]?.delta ?? {}) as JsonRecord;
866
+ if (typeof delta.reasoning_content === "string") {
867
+ thinking += delta.reasoning_content;
868
+ }
869
+ if (typeof delta.content === "string" && delta.content) {
870
+ text += delta.content;
871
+ const now = Date.now();
872
+ if (now - lastNotifiedAt >= STREAM_NOTIFY_INTERVAL_MS) {
873
+ lastNotifiedAt = now;
874
+ notify({ streamText: text });
875
+ }
876
+ }
877
+ }
878
+
879
+ notify({ streamText: text, streamDone: true });
880
+
881
+ return {
882
+ model,
883
+ choices: [
884
+ {
885
+ message: { content: text, reasoning_content: thinking },
886
+ finish_reason: finishReason,
887
+ },
888
+ ],
889
+ usage: {
890
+ prompt_tokens: promptTokens,
891
+ completion_tokens: completionTokens,
892
+ },
893
+ };
894
+ }
895
+
896
+ /**
897
+ * Accumulates a streamed response into the same shape a non-streamed one
898
+ * has, notifying the growing text as `{streamText}` on the way — the shape
899
+ * hkp-python's service established, so one UI renders either runtime's.
900
+ */
901
+ private async readStream(
902
+ response: Response,
903
+ notify: Notify,
904
+ ): Promise<JsonRecord> {
905
+ let text = "";
906
+ let thinking = "";
907
+ let model = this.model;
908
+ let inputTokens = 0;
909
+ let outputTokens = 0;
910
+ let stopReason: string | undefined;
911
+ let lastNotifiedAt = 0;
912
+
913
+ for await (const event of readEvents(response)) {
914
+ switch (event.type) {
915
+ case "message_start": {
916
+ const message = (event.message ?? {}) as JsonRecord;
917
+ if (typeof message.model === "string") {
918
+ model = message.model;
919
+ }
920
+ const usage = (message.usage ?? {}) as JsonRecord;
921
+ if (isNumber(usage.input_tokens)) {
922
+ inputTokens = usage.input_tokens;
923
+ }
924
+ break;
925
+ }
926
+ case "content_block_delta": {
927
+ const delta = (event.delta ?? {}) as JsonRecord;
928
+ if (typeof delta.text === "string") {
929
+ text += delta.text;
930
+ const now = Date.now();
931
+ if (now - lastNotifiedAt >= STREAM_NOTIFY_INTERVAL_MS) {
932
+ lastNotifiedAt = now;
933
+ notify({ streamText: text });
934
+ }
935
+ }
936
+ if (typeof delta.thinking === "string") {
937
+ thinking += delta.thinking;
938
+ }
939
+ break;
940
+ }
941
+ case "message_delta": {
942
+ const usage = (event.usage ?? {}) as JsonRecord;
943
+ if (isNumber(usage.output_tokens)) {
944
+ outputTokens = usage.output_tokens;
945
+ }
946
+ const delta = (event.delta ?? {}) as JsonRecord;
947
+ if (typeof delta.stop_reason === "string") {
948
+ stopReason = delta.stop_reason;
949
+ }
950
+ break;
951
+ }
952
+ default:
953
+ break;
954
+ }
955
+ }
956
+
957
+ notify({ streamText: text, streamDone: true });
958
+
959
+ const content: JsonRecord[] = [];
960
+ if (thinking) {
961
+ content.push({ type: "thinking", thinking });
962
+ }
963
+ content.push({ type: "text", text });
964
+ return {
965
+ model,
966
+ content,
967
+ stop_reason: stopReason,
968
+ usage: { input_tokens: inputTokens, output_tokens: outputTokens },
969
+ };
970
+ }
971
+
972
+ /** Reduces a response message to the output contract shared across runtimes. */
973
+ private toResult(message: JsonRecord, started: number, structured = false): Result {
974
+ const blocks = Array.isArray(message.content)
975
+ ? (message.content as JsonRecord[])
976
+ : [];
977
+ const texts: string[] = [];
978
+ const thoughts: string[] = [];
979
+ let json: unknown;
980
+
981
+ for (const block of blocks) {
982
+ if (!block || typeof block !== "object") {
983
+ continue;
984
+ }
985
+ if (block.type === "text" && typeof block.text === "string") {
986
+ texts.push(block.text);
987
+ } else if (block.type === "thinking" && typeof block.thinking === "string") {
988
+ thoughts.push(block.thinking);
989
+ } else if (block.type === "tool_use" && block.name === SCHEMA_TOOL_NAME) {
990
+ json = block.input;
991
+ }
992
+ }
993
+
994
+ if (structured && json === undefined) {
995
+ // Structured outputs answer in text that is the object. Left as text
996
+ // when it is not — an answer cut off by max_tokens — so the failure that
997
+ // follows says why instead of claiming the model said nothing.
998
+ try {
999
+ json = JSON.parse(texts.join("").trim());
1000
+ } catch {
1001
+ // Not an object; reported through finishReason.
1002
+ }
1003
+ }
1004
+
1005
+ const usage = (message.usage ?? {}) as JsonRecord;
1006
+ const result: Result = {
1007
+ // With a schema the answer *is* the object, so `text` carries it
1008
+ // serialized — a board that only knows the shared contract still has
1009
+ // something to route, log, or show.
1010
+ text: json !== undefined ? JSON.stringify(json) : texts.join("").trim(),
1011
+ model: typeof message.model === "string" ? message.model : this.model,
1012
+ durationMs: Date.now() - started,
1013
+ usage: {
1014
+ promptTokens: isNumber(usage.input_tokens) ? usage.input_tokens : 0,
1015
+ completionTokens: isNumber(usage.output_tokens) ? usage.output_tokens : 0,
1016
+ },
1017
+ };
1018
+ if (json !== undefined) {
1019
+ result.json = json;
1020
+ }
1021
+ const thinking = thoughts.join("").trim();
1022
+ if (thinking) {
1023
+ result.thinking = thinking;
1024
+ }
1025
+ // In the server backend's vocabulary, so one check reads either: an
1026
+ // answer cut off by `max_tokens` and one the model declined both arrive
1027
+ // with no text, and only this says which.
1028
+ const finishReason = ANTHROPIC_FINISH_REASONS[String(message.stop_reason)];
1029
+ if (finishReason) {
1030
+ result.finishReason = finishReason;
1031
+ }
1032
+ return result;
1033
+ }
1034
+
1035
+ /**
1036
+ * Turns pipeline input into the messages to send.
1037
+ *
1038
+ * Accepts what the local backends accept, plus the two shapes that carry
1039
+ * images: a single `{meta, binary}` as `http-client` and
1040
+ * `http-server-subservices` produce, and an explicit `images` list.
1041
+ */
1042
+ private toMessages(input: unknown): Message[] | null {
1043
+ if (typeof input === "string") {
1044
+ return input.trim() ? [{ role: "user", content: input }] : null;
1045
+ }
1046
+ if (input instanceof Uint8Array) {
1047
+ return null;
1048
+ }
1049
+ if (typeof input !== "object" || input === null) {
1050
+ return null;
1051
+ }
1052
+
1053
+ const record = input as JsonRecord;
1054
+ if (Array.isArray(record.messages)) {
1055
+ return record.messages as Message[];
1056
+ }
1057
+
1058
+ const prompt =
1059
+ typeof record.prompt === "string" && record.prompt
1060
+ ? record.prompt
1061
+ : typeof record.text === "string" && record.text
1062
+ ? record.text
1063
+ : "";
1064
+
1065
+ const images = this.imageParts(record);
1066
+ if (!images.length) {
1067
+ return prompt.trim() ? [{ role: "user", content: prompt }] : null;
1068
+ }
1069
+
1070
+ // Images first: the API reads a question about them better when it has
1071
+ // already seen them.
1072
+ const content: JsonRecord[] = [...images];
1073
+ if (prompt.trim()) {
1074
+ content.push({ type: "text", text: prompt });
1075
+ }
1076
+ return [{ role: "user", content }];
1077
+ }
1078
+
1079
+ /** The image parts an input carries, in whichever of the shapes it uses. */
1080
+ private imageParts(record: JsonRecord): JsonRecord[] {
1081
+ const parts: JsonRecord[] = [];
1082
+
1083
+ const meta = (record.meta ?? {}) as JsonRecord;
1084
+ const declared =
1085
+ typeof meta.contentType === "string" ? meta.contentType.split(";")[0].trim() : "";
1086
+ if (record.binary instanceof Uint8Array && declared.startsWith("image/")) {
1087
+ parts.push(imagePart(record.binary, declared));
1088
+ }
1089
+
1090
+ if (Array.isArray(record.images)) {
1091
+ for (const image of record.images) {
1092
+ if (image instanceof Uint8Array) {
1093
+ // No type given; PNG is the safe assumption for a bare buffer and
1094
+ // the API sniffs the bytes anyway.
1095
+ parts.push(imagePart(image, "image/png"));
1096
+ continue;
1097
+ }
1098
+ if (!image || typeof image !== "object") {
1099
+ continue;
1100
+ }
1101
+ const entry = image as JsonRecord;
1102
+ const type =
1103
+ typeof entry.contentType === "string" ? entry.contentType : "image/png";
1104
+ if (entry.data instanceof Uint8Array) {
1105
+ parts.push(imagePart(entry.data, type));
1106
+ } else if (typeof entry.data === "string") {
1107
+ // Already base64: passed through rather than decoded and re-encoded.
1108
+ parts.push({
1109
+ type: "image",
1110
+ source: { type: "base64", media_type: type, data: entry.data },
1111
+ });
1112
+ }
1113
+ }
1114
+ }
1115
+
1116
+ return parts;
1117
+ }
1118
+
1119
+ private setStatus(notify: Notify, status: string, detail?: string): void {
1120
+ this.status = status;
1121
+ if (status !== "error") {
1122
+ this.lastError = "";
1123
+ }
1124
+ notify(detail === undefined ? { status } : { status, detail });
1125
+ }
1126
+
1127
+ /** Why an answer came back with nothing in it, as far as the backend said. */
1128
+ private emptyAnswer(finishReason: string | undefined, server: boolean): string {
1129
+ if (finishReason === "length") {
1130
+ // Current Claude models think before answering whatever the board says,
1131
+ // and that thinking is drawn from the same budget; only a local server
1132
+ // can be told not to.
1133
+ return server
1134
+ ? `the model used all ${this.maxTokens} tokens without answering — ` +
1135
+ "raise maxTokens, or set thinking: false if it reasons first"
1136
+ : `the model used all ${this.maxTokens} tokens without answering — ` +
1137
+ "raise maxTokens: its thinking counts against them";
1138
+ }
1139
+ if (finishReason === "refusal") {
1140
+ return "the model declined to answer this input";
1141
+ }
1142
+ return "the model returned an empty answer";
1143
+ }
1144
+
1145
+ /** Reports a failure and produces nothing, so the pipeline stops here. */
1146
+ private fail(notify: Notify, error: string): null {
1147
+ this.lastError = error;
1148
+ this.setStatus(notify, "error");
1149
+ // Also to the board log, which is what a board running unattended has
1150
+ // instead of somebody watching a panel.
1151
+ this.host?.log("error", "service.failed", { message: error });
1152
+ notify({ error });
1153
+ return null;
1154
+ }
1155
+ }
1156
+
1157
+ /**
1158
+ * One message, in OpenAI vocabulary.
1159
+ *
1160
+ * Text and role are the same on both sides; only images differ — a base64
1161
+ * source becomes a data URI, which is how that API carries the same bytes.
1162
+ */
1163
+ function openAiMessage(message: Message): JsonRecord {
1164
+ if (!Array.isArray(message.content)) {
1165
+ return { role: message.role, content: message.content };
1166
+ }
1167
+ const parts = (message.content as JsonRecord[]).map((part) => {
1168
+ if (part?.type !== "image") {
1169
+ return part;
1170
+ }
1171
+ const source = (part.source ?? {}) as JsonRecord;
1172
+ return {
1173
+ type: "image_url",
1174
+ image_url: { url: `data:${source.media_type};base64,${source.data}` },
1175
+ };
1176
+ });
1177
+ return { role: message.role, content: parts };
1178
+ }
1179
+
1180
+ /**
1181
+ * Separates reasoning from the answer.
1182
+ *
1183
+ * A server reports it either as its own field or inline in the content as
1184
+ * `<think>…</think>`, depending on how it handles the model's chat template —
1185
+ * so both are read, and a board sees the same `thinking` field either way.
1186
+ */
1187
+ function splitThinking(
1188
+ content: string,
1189
+ declared: string,
1190
+ ): { text: string; thinking: string } {
1191
+ const close = content.indexOf(THINK_CLOSE);
1192
+ if (close === -1) {
1193
+ return { text: content.trim(), thinking: declared.trim() };
1194
+ }
1195
+ const head = content.slice(0, close);
1196
+ const inline = head.startsWith(THINK_OPEN)
1197
+ ? head.slice(THINK_OPEN.length)
1198
+ : head;
1199
+ return {
1200
+ text: content.slice(close + THINK_CLOSE.length).trim(),
1201
+ thinking: `${declared}${inline}`.trim(),
1202
+ };
1203
+ }
1204
+
1205
+ /** The port a hint should name, so the suggestion matches the configuration. */
1206
+ function port(url: string): string {
1207
+ try {
1208
+ return new URL(url).port || "8081";
1209
+ } catch {
1210
+ return "8081";
1211
+ }
1212
+ }
1213
+
1214
+ /**
1215
+ * Reads a server-sent-event stream as the objects it carries.
1216
+ *
1217
+ * Only `data:` lines matter here; the `event:` line names the same type that
1218
+ * the payload itself carries, so the payload alone is enough.
1219
+ */
1220
+ async function* readEvents(response: Response): AsyncGenerator<JsonRecord> {
1221
+ const body = response.body;
1222
+ if (!body) {
1223
+ return;
1224
+ }
1225
+ const decoder = new TextDecoder();
1226
+ let buffer = "";
1227
+
1228
+ for await (const chunk of body as unknown as AsyncIterable<Uint8Array>) {
1229
+ buffer += decoder.decode(chunk, { stream: true });
1230
+ let newline = buffer.indexOf("\n");
1231
+ while (newline !== -1) {
1232
+ const line = buffer.slice(0, newline).trim();
1233
+ buffer = buffer.slice(newline + 1);
1234
+ newline = buffer.indexOf("\n");
1235
+ if (!line.startsWith("data:")) {
1236
+ continue;
1237
+ }
1238
+ const payload = line.slice(5).trim();
1239
+ if (!payload) {
1240
+ continue;
1241
+ }
1242
+ try {
1243
+ yield JSON.parse(payload) as JsonRecord;
1244
+ } catch {
1245
+ // A line that is not JSON is not an event; the stream continues.
1246
+ }
1247
+ }
1248
+ }
1249
+ }