@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.
- package/README.md +114 -2
- package/bundle/LICENSE +201 -0
- package/bundle/boards/aggregator-demo-board.json +54 -0
- package/bundle/boards/alpaca-board.json +316 -0
- package/bundle/boards/animate-board.json +159 -0
- package/bundle/boards/ascii-art-demo-board.json +33 -0
- package/bundle/boards/ascii-cam-board.json +184 -0
- package/bundle/boards/asset-demo-board.json +54 -0
- package/bundle/boards/audio-encode-demo-board.json +153 -0
- package/bundle/boards/audiolize-board.json +152 -0
- package/bundle/boards/batcher-demo-board.json +43 -0
- package/bundle/boards/buffer-demo-board.json +53 -0
- package/bundle/boards/cache-demo-board.json +59 -0
- package/bundle/boards/camera-demo-board.json +28 -0
- package/bundle/boards/canvas-demo-board.json +55 -0
- package/bundle/boards/changes-demo-board.json +60 -0
- package/bundle/boards/court-booking-browser-demo-board.json +221 -0
- package/bundle/boards/court-booking-demo-board.json +218 -0
- package/bundle/boards/current-time-board.json +32 -0
- package/bundle/boards/debounce-demo-board.json +43 -0
- package/bundle/boards/decrypt-demo-board.json +47 -0
- package/bundle/boards/delay-demo-board.json +28 -0
- package/bundle/boards/detect-demo-board.json +64 -0
- package/bundle/boards/document-extract-demo-board.json +55 -0
- package/bundle/boards/dropitapp-board.json +138 -0
- package/bundle/boards/encode-demo-board.json +39 -0
- package/bundle/boards/encrypt-board.json +185 -0
- package/bundle/boards/encrypt-demo-board.json +39 -0
- package/bundle/boards/face-alert-board.json +181 -0
- package/bundle/boards/fetcher-demo-board.json +40 -0
- package/bundle/boards/fft-demo-board.json +28 -0
- package/bundle/boards/filter-demo-board.json +55 -0
- package/bundle/boards/flat-map-demo-board.json +42 -0
- package/bundle/boards/frame-limit-test-board.json +130 -0
- package/bundle/boards/game-board.json +413 -0
- package/bundle/boards/game-of-life-board.json +139 -0
- package/bundle/boards/game-of-life-demo-board.json +34 -0
- package/bundle/boards/gather-demo-board.json +62 -0
- package/bundle/boards/group-by-demo-board.json +62 -0
- package/bundle/boards/hacker-demo-board.json +45 -0
- package/bundle/boards/hacker-news-board.json +741 -0
- package/bundle/boards/hash-demo-board.json +40 -0
- package/bundle/boards/health-log-browser-demo-board.json +537 -0
- package/bundle/boards/health-log-demo-board.json +498 -0
- package/bundle/boards/helloworld-board.json +57 -0
- package/bundle/boards/hold-demo-board.json +67 -0
- package/bundle/boards/hold-scopes-demo-board.json +141 -0
- package/bundle/boards/http-client-browser-demo-board.json +379 -0
- package/bundle/boards/http-client-demo-board.json +380 -0
- package/bundle/boards/image-picker-demo-board.json +22 -0
- package/bundle/boards/imap-email-demo-board.json +35 -0
- package/bundle/boards/injector-demo-board.json +55 -0
- package/bundle/boards/iterator-demo-board.json +55 -0
- package/bundle/boards/limit-demo-board.json +66 -0
- package/bundle/boards/link-debugger.json +32 -0
- package/bundle/boards/live-location-demo-board.json +93 -0
- package/bundle/boards/live-radio-cloud-demo-board.json +216 -0
- package/bundle/boards/live-radio-cloud-direct-demo-board.json +311 -0
- package/bundle/boards/live-radio-demo-board.json +211 -0
- package/bundle/boards/live-radio-members-demo-board.json +314 -0
- package/bundle/boards/local-storage-demo-board.json +30 -0
- package/bundle/boards/lz-compress-demo-board.json +52 -0
- package/bundle/boards/map-demo-board.json +46 -0
- package/bundle/boards/match-filter-demo-board.json +43 -0
- package/bundle/boards/meeting-poll-browser-demo-board.json +649 -0
- package/bundle/boards/meeting-poll-demo-board.json +650 -0
- package/bundle/boards/microphone-monitor-demo-board.json +28 -0
- package/bundle/boards/microphone-speaker-demo-board.json +86 -0
- package/bundle/boards/monitor-demo-board.json +52 -0
- package/bundle/boards/mounted-endpoint-demo-board-hkp-rt.json +84 -0
- package/bundle/boards/mounted-endpoint-demo-board.json +84 -0
- package/bundle/boards/nested-rhythm-demo-board.json +1160 -0
- package/bundle/boards/next-bus-board.json +52 -0
- package/bundle/boards/next-bus-switch-board.json +79 -0
- package/bundle/boards/night-groove-demo-board.json +742 -0
- package/bundle/boards/noise-alert-board.json +146 -0
- package/bundle/boards/offer-followup-demo-board.json +291 -0
- package/bundle/boards/offer-intake-demo-board.json +289 -0
- package/bundle/boards/offer-intake-hetzner-demo-board.json +286 -0
- package/bundle/boards/offer-intake-imap-demo-board.json +289 -0
- package/bundle/boards/offer-intake-imap-hetzner-demo-board.json +354 -0
- package/bundle/boards/p2p-receiver-demo-board.json +65 -0
- package/bundle/boards/p2p-sender-demo-board.json +94 -0
- package/bundle/boards/pad-groove-demo-board.json +644 -0
- package/bundle/boards/peer-chat-board.json +109 -0
- package/bundle/boards/peer-chat-node-board.json +97 -0
- package/bundle/boards/qr-code-demo-board.json +22 -0
- package/bundle/boards/queue-demo-board.json +239 -0
- package/bundle/boards/reduce-demo-board.json +54 -0
- package/bundle/boards/remote-named-demo-board-hkp-rt.json +61 -0
- package/bundle/boards/remote-named-demo-board.json +61 -0
- package/bundle/boards/rss-demo-board.json +530 -0
- package/bundle/boards/rss-radio-board.json +515 -0
- package/bundle/boards/rss-radio-brief-one-board.json +237 -0
- package/bundle/boards/rss-radio-editor-unit-board.json +555 -0
- package/bundle/boards/rss-radio-library-unit-board.json +293 -0
- package/bundle/boards/rss-radio-unit-board.json +809 -0
- package/bundle/boards/rss-radio-voice-unit-board.json +146 -0
- package/bundle/boards/runtime-configs/hold-example.json +41 -0
- package/bundle/boards/runtime-configs/injector-example.json +23 -0
- package/bundle/boards/runtime-configs/speech-to-text-example.json +35 -0
- package/bundle/boards/runtime-configs/text-generation-example.json +31 -0
- package/bundle/boards/runtime-configs/text-to-speech-example.json +28 -0
- package/bundle/boards/runtime-configs/text-to-speech-inflect-example.json +28 -0
- package/bundle/boards/runtime-configs/ws-audio-playback-example.json +23 -0
- package/bundle/boards/select-demo-board.json +32 -0
- package/bundle/boards/send-ntfy-board.json +52 -0
- package/bundle/boards/shared-costs-demo-board.json +361 -0
- package/bundle/boards/sign-demo-board.json +41 -0
- package/bundle/boards/skill-router-demo-board.json +210 -0
- package/bundle/boards/smooth-demo-board.json +91 -0
- package/bundle/boards/smtp-email-demo-board.json +34 -0
- package/bundle/boards/sort-demo-board.json +55 -0
- package/bundle/boards/sound-demo-board.json +65 -0
- package/bundle/boards/spectral-modifier-board.json +98 -0
- package/bundle/boards/speech-synth-demo-board.json +37 -0
- package/bundle/boards/speech-to-text-demo-board.json +146 -0
- package/bundle/boards/spotify-board.json +75 -0
- package/bundle/boards/spread-demo-board.json +35 -0
- package/bundle/boards/sql-demo-board.json +56 -0
- package/bundle/boards/sql-explorer-board.json +447 -0
- package/bundle/boards/sql-explorer-node-board.json +474 -0
- package/bundle/boards/sql-import-board.json +156 -0
- package/bundle/boards/state-demo-board.json +65 -0
- package/bundle/boards/stopper-demo-board.json +40 -0
- package/bundle/boards/storage-demo-board.json +193 -0
- package/bundle/boards/store-demo-board.json +63 -0
- package/bundle/boards/swimlanes-demo-board.json +197 -0
- package/bundle/boards/switch-demo-board.json +51 -0
- package/bundle/boards/syn-board.json +19 -0
- package/bundle/boards/syn-booking-unit-board.json +998 -0
- package/bundle/boards/syn-conversations-anthropic-demo-board.json +925 -0
- package/bundle/boards/syn-conversations-demo-board.json +925 -0
- package/bundle/boards/syn-hotels-unit-board.json +287 -0
- package/bundle/boards/syn-mail-desk-board.json +25 -0
- package/bundle/boards/syn-mail-desk-unit-board.json +254 -0
- package/bundle/boards/telegram-listener-demo-board.json +31 -0
- package/bundle/boards/text-generation-anthropic-demo-board.json +66 -0
- package/bundle/boards/text-generation-demo-board.json +51 -0
- package/bundle/boards/text-generation-local-demo-board.json +80 -0
- package/bundle/boards/text-to-speech-demo-board.json +58 -0
- package/bundle/boards/timeline-demo-board.json +477 -0
- package/bundle/boards/timer-demo-board.json +35 -0
- package/bundle/boards/tracks-demo-board.json +95 -0
- package/bundle/boards/trigger-pad-demo-board.json +47 -0
- package/bundle/boards/triggerpad-board.json +31 -0
- package/bundle/boards/triggerpad-recorder-board.json +56 -0
- package/bundle/boards/uuid-generator-demo-board.json +166 -0
- package/bundle/boards/voice-assistant-demo-board.json +230 -0
- package/bundle/boards/voice-assistant-hkp-rt-demo-board.json +239 -0
- package/bundle/boards/voice-assistant-local-demo-board.json +232 -0
- package/bundle/boards/voice-assistant-skills-demo-board.json +316 -0
- package/bundle/boards/voice-assistant-skills-local-demo-board.json +316 -0
- package/bundle/boards/xy-pad-demo-board.json +36 -0
- package/bundle/docs/content/board-json.md +277 -0
- package/bundle/docs/content/boards/alpaca-board.md +56 -0
- package/bundle/docs/content/boards/animate-board.md +40 -0
- package/bundle/docs/content/boards/ascii-cam-board.md +37 -0
- package/bundle/docs/content/boards/audiolize-board.md +47 -0
- package/bundle/docs/content/boards/court-booking-browser-demo-board.md +43 -0
- package/bundle/docs/content/boards/court-booking-demo-board.md +120 -0
- package/bundle/docs/content/boards/current-time-board.md +32 -0
- package/bundle/docs/content/boards/dropitapp-board.md +57 -0
- package/bundle/docs/content/boards/encrypt-board.md +36 -0
- package/bundle/docs/content/boards/face-alert-board.md +50 -0
- package/bundle/docs/content/boards/game-board.md +69 -0
- package/bundle/docs/content/boards/game-of-life-board.md +47 -0
- package/bundle/docs/content/boards/hacker-news-board.md +76 -0
- package/bundle/docs/content/boards/health-log-browser-demo-board.md +56 -0
- package/bundle/docs/content/boards/health-log-demo-board.md +138 -0
- package/bundle/docs/content/boards/helloworld-board.md +40 -0
- package/bundle/docs/content/boards/link-debugger.md +31 -0
- package/bundle/docs/content/boards/live-radio-cloud-demo-board.md +70 -0
- package/bundle/docs/content/boards/live-radio-cloud-direct-demo-board.md +89 -0
- package/bundle/docs/content/boards/live-radio-demo-board.md +102 -0
- package/bundle/docs/content/boards/live-radio-members-demo-board.md +91 -0
- package/bundle/docs/content/boards/meeting-poll-browser-demo-board.md +45 -0
- package/bundle/docs/content/boards/meeting-poll-demo-board.md +121 -0
- package/bundle/docs/content/boards/mounted-endpoint-demo-board-hkp-rt.md +50 -0
- package/bundle/docs/content/boards/nested-rhythm-demo-board.md +165 -0
- package/bundle/docs/content/boards/next-bus-board.md +38 -0
- package/bundle/docs/content/boards/next-bus-switch-board.md +44 -0
- package/bundle/docs/content/boards/night-groove-demo-board.md +110 -0
- package/bundle/docs/content/boards/noise-alert-board.md +44 -0
- package/bundle/docs/content/boards/pad-groove-demo-board.md +93 -0
- package/bundle/docs/content/boards/peer-chat-board.md +49 -0
- package/bundle/docs/content/boards/peer-chat-node-board.md +47 -0
- package/bundle/docs/content/boards/rss-radio-board.md +221 -0
- package/bundle/docs/content/boards/send-ntfy-board.md +33 -0
- package/bundle/docs/content/boards/shared-costs-demo-board.md +65 -0
- package/bundle/docs/content/boards/spectral-modifier-board.md +55 -0
- package/bundle/docs/content/boards/spotify-board.md +46 -0
- package/bundle/docs/content/boards/sql-explorer-board.md +70 -0
- package/bundle/docs/content/boards/sql-explorer-node-board.md +76 -0
- package/bundle/docs/content/boards/sql-import-board.md +37 -0
- package/bundle/docs/content/boards/swimlanes-demo-board.md +148 -0
- package/bundle/docs/content/boards/syn-mail-desk-board.md +52 -0
- package/bundle/docs/content/boards/timeline-demo-board.md +115 -0
- package/bundle/docs/content/boards/triggerpad-board.md +32 -0
- package/bundle/docs/content/boards/triggerpad-recorder-board.md +52 -0
- package/bundle/docs/content/boards/voice-assistant-demo-board.md +51 -0
- package/bundle/docs/content/concepts/assets.md +245 -0
- package/bundle/docs/content/concepts/blocks.md +300 -0
- package/bundle/docs/content/concepts/board-assistant.md +307 -0
- package/bundle/docs/content/concepts/board.md +265 -0
- package/bundle/docs/content/concepts/cloud-boards.md +803 -0
- package/bundle/docs/content/concepts/coordinator.md +297 -0
- package/bundle/docs/content/concepts/logging.md +293 -0
- package/bundle/docs/content/concepts/mounts.md +314 -0
- package/bundle/docs/content/concepts/presets.md +360 -0
- package/bundle/docs/content/concepts/remotes.md +423 -0
- package/bundle/docs/content/concepts/runtime.md +664 -0
- package/bundle/docs/content/concepts/scopes.md +256 -0
- package/bundle/docs/content/concepts/service.md +227 -0
- package/bundle/docs/content/concepts/units.md +321 -0
- package/bundle/docs/content/introduction.md +163 -0
- package/bundle/docs/content/llm/circle-text-board.json +55 -0
- package/bundle/docs/content/llm/howto-workflow.md +369 -0
- package/bundle/docs/content/repository.md +161 -0
- package/bundle/docs/content/services/aggregator.md +76 -0
- package/bundle/docs/content/services/analyzer.md +44 -0
- package/bundle/docs/content/services/ascii-art.md +58 -0
- package/bundle/docs/content/services/asset.md +88 -0
- package/bundle/docs/content/services/audio-editor.md +57 -0
- package/bundle/docs/content/services/audio-encode.md +115 -0
- package/bundle/docs/content/services/audio-io.md +173 -0
- package/bundle/docs/content/services/batcher.md +49 -0
- package/bundle/docs/content/services/board-service.md +48 -0
- package/bundle/docs/content/services/browser-sub-service.md +75 -0
- package/bundle/docs/content/services/buffer.md +70 -0
- package/bundle/docs/content/services/cache.md +92 -0
- package/bundle/docs/content/services/camera.md +85 -0
- package/bundle/docs/content/services/canvas.md +196 -0
- package/bundle/docs/content/services/changes.md +87 -0
- package/bundle/docs/content/services/chunked-file-provider.md +63 -0
- package/bundle/docs/content/services/communication-dispatcher.md +190 -0
- package/bundle/docs/content/services/configurator.md +66 -0
- package/bundle/docs/content/services/conversations.md +244 -0
- package/bundle/docs/content/services/debounce.md +45 -0
- package/bundle/docs/content/services/decrypt.md +53 -0
- package/bundle/docs/content/services/delay.md +55 -0
- package/bundle/docs/content/services/detect.md +101 -0
- package/bundle/docs/content/services/document-extract.md +174 -0
- package/bundle/docs/content/services/download.md +67 -0
- package/bundle/docs/content/services/encode.md +63 -0
- package/bundle/docs/content/services/encrypt.md +53 -0
- package/bundle/docs/content/services/fetcher.md +122 -0
- package/bundle/docs/content/services/fft.md +96 -0
- package/bundle/docs/content/services/file-source.md +67 -0
- package/bundle/docs/content/services/filesystem.md +157 -0
- package/bundle/docs/content/services/filter.md +133 -0
- package/bundle/docs/content/services/flat-map.md +62 -0
- package/bundle/docs/content/services/game-of-life.md +93 -0
- package/bundle/docs/content/services/gather.md +135 -0
- package/bundle/docs/content/services/gif-encoder.md +56 -0
- package/bundle/docs/content/services/github.md +85 -0
- package/bundle/docs/content/services/group-by.md +54 -0
- package/bundle/docs/content/services/hacker.md +79 -0
- package/bundle/docs/content/services/hash.md +59 -0
- package/bundle/docs/content/services/hold.md +182 -0
- package/bundle/docs/content/services/http-relay-client.md +48 -0
- package/bundle/docs/content/services/http-uploader.md +62 -0
- package/bundle/docs/content/services/http.md +424 -0
- package/bundle/docs/content/services/if.md +79 -0
- package/bundle/docs/content/services/ifft.md +46 -0
- package/bundle/docs/content/services/image-picker.md +50 -0
- package/bundle/docs/content/services/imap-email.md +104 -0
- package/bundle/docs/content/services/injector.md +129 -0
- package/bundle/docs/content/services/input.md +90 -0
- package/bundle/docs/content/services/iterator.md +186 -0
- package/bundle/docs/content/services/join.md +153 -0
- package/bundle/docs/content/services/limit.md +47 -0
- package/bundle/docs/content/services/local-storage.md +47 -0
- package/bundle/docs/content/services/looper.md +62 -0
- package/bundle/docs/content/services/lz-compress.md +52 -0
- package/bundle/docs/content/services/map.md +257 -0
- package/bundle/docs/content/services/match-filter.md +38 -0
- package/bundle/docs/content/services/microphone-monitor.md +66 -0
- package/bundle/docs/content/services/monitor.md +67 -0
- package/bundle/docs/content/services/ollama.md +116 -0
- package/bundle/docs/content/services/openai-prompt.md +74 -0
- package/bundle/docs/content/services/output.md +107 -0
- package/bundle/docs/content/services/peer-socket.md +61 -0
- package/bundle/docs/content/services/qr-code.md +48 -0
- package/bundle/docs/content/services/queue.md +201 -0
- package/bundle/docs/content/services/reduce.md +51 -0
- package/bundle/docs/content/services/rss.md +232 -0
- package/bundle/docs/content/services/select.md +47 -0
- package/bundle/docs/content/services/sign.md +53 -0
- package/bundle/docs/content/services/skill-router.md +154 -0
- package/bundle/docs/content/services/smooth.md +59 -0
- package/bundle/docs/content/services/smtp-email.md +129 -0
- package/bundle/docs/content/services/sort.md +48 -0
- package/bundle/docs/content/services/sound.md +87 -0
- package/bundle/docs/content/services/speech-synth.md +46 -0
- package/bundle/docs/content/services/speech-to-text.md +172 -0
- package/bundle/docs/content/services/spotify.md +77 -0
- package/bundle/docs/content/services/spread.md +111 -0
- package/bundle/docs/content/services/sql.md +491 -0
- package/bundle/docs/content/services/state.md +57 -0
- package/bundle/docs/content/services/static.md +47 -0
- package/bundle/docs/content/services/stopper.md +55 -0
- package/bundle/docs/content/services/storage.md +155 -0
- package/bundle/docs/content/services/store.md +255 -0
- package/bundle/docs/content/services/sub-service.md +152 -0
- package/bundle/docs/content/services/switch.md +67 -0
- package/bundle/docs/content/services/telegram-listener.md +76 -0
- package/bundle/docs/content/services/text-generation.md +390 -0
- package/bundle/docs/content/services/text-to-speech.md +130 -0
- package/bundle/docs/content/services/timeline.md +333 -0
- package/bundle/docs/content/services/timer.md +113 -0
- package/bundle/docs/content/services/tracks.md +207 -0
- package/bundle/docs/content/services/trigger-pad.md +68 -0
- package/bundle/docs/content/services/uuid-generator.md +48 -0
- package/bundle/docs/content/services/wav-reader.md +66 -0
- package/bundle/docs/content/services/websocket.md +189 -0
- package/bundle/docs/content/services/workflow-board-builder.md +65 -0
- package/bundle/docs/content/services/xy-pad.md +60 -0
- package/bundle/docs/content/targets.md +434 -0
- package/bundle/docs/content/testing.md +263 -0
- package/bundle/docs/content/vocabulary.md +171 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRegistry.tsx +141 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntime.tsx +202 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeApi.ts +306 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeApp.ts +310 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeScope.ts +500 -0
- package/bundle/hkp-frontend/src/runtime/browser/UIRegistry.ts +150 -0
- package/bundle/hkp-frontend/src/runtime/browser/assetResolver.ts +123 -0
- package/bundle/hkp-frontend/src/runtime/browser/pipelineNames.ts +65 -0
- package/bundle/hkp-frontend/src/runtime/browser/registry/Default.ts +237 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Aggregator.ts +127 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AggregatorUI.tsx +83 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Analyzer.tsx +43 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/Bars.tsx +33 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/HorizontalSpectrogram.tsx +53 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/VerticalSpectogram.tsx +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/Waveform.tsx +109 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/index.tsx +99 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AsciiArt.ts +119 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AsciiArtUI.tsx +131 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Asset.ts +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AssetUI.tsx +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioDeviceSettings.tsx +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioEditor.ts +129 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioEditorUI.tsx +549 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioInput.ts +298 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioInputUI.tsx +257 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioOutput.ts +189 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioOutputUI.tsx +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Batcher.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BoardService.tsx +86 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BoardServiceUI.tsx +410 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserGather.ts +311 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserIterator.ts +167 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserSubService.tsx +583 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserSubServiceUI.tsx +187 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserTracks.tsx +477 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Buffer.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Cache.ts +134 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CacheUI.tsx +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Camera.ts +90 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CameraUI.tsx +321 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Canvas.ts +139 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/canvasDraw.ts +543 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/canvasUtils.ts +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/index.tsx +287 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/canvasDraw.test.ts +355 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/canvasUtils.test.ts +89 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/fullscreen.test.tsx +26 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Changes.ts +201 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ChangesUI.tsx +85 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ChunkedFileProvider.tsx +183 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Configurator.ts +258 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ConfiguratorUI.tsx +83 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ConsideredHacker.tsx +28 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Debounce.ts +75 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DebounceUI.tsx +70 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Decrypt.ts +127 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DecryptUI.tsx +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Delay.tsx +110 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Detect.ts +214 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DetectUI.tsx +86 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Download.ts +120 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DownloadUI.tsx +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Encode.ts +138 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/EncodeUI.tsx +52 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Encrypt.ts +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/EncryptUI.tsx +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FFT.tsx +238 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FFTUI.tsx +112 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FeedbackService.ts +247 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FeedbackServiceUI.tsx +35 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Fetcher.tsx +232 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FetcherUI.tsx +154 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FileSource.tsx +171 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Filter.tsx +18 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FilterUI.tsx +89 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FlatMap.ts +68 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FlatMapUI.tsx +38 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLife.ts +210 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeRenderer.ts +182 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeRendererUI.tsx +48 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeUI.tsx +58 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GatherUI.tsx +84 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GifEncoder.tsx +651 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubAPI.ts +294 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubComponents.tsx +389 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSink.tsx +109 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSinkUI.tsx +143 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSource.tsx +86 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSourceUI.tsx +162 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GroupBy.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HTTPUploader.ts +102 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Hacker.ts +73 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HackerUI.tsx +134 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Hash.ts +134 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HashUI.tsx +45 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Hold.ts +288 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HoldUI.tsx +43 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HttpClient.ts +558 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HttpClientUI.tsx +212 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HttpUploaderTokenInjector.ts +149 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/IfService.ts +231 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/IfServiceUI.tsx +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ImagePicker.tsx +118 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Injector.ts +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/InjectorUI.tsx +389 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Input.ts +214 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/InputUI.tsx +71 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/IteratorUI.tsx +59 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LZCompress.ts +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Limit.ts +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LimitUI.tsx +41 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LocalStorage.ts +65 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LocalStorageUI.tsx +72 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Map.ts +14 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MapUI.tsx +334 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MapUIModeAssist.tsx +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MatchFilter.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MicrophoneMonitor.ts +190 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MicrophoneMonitorUI.tsx +94 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Monitor.tsx +15 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MonitorUI.tsx +281 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPrompt.ts +237 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/BusyIndicator.tsx +18 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/PromptInput.tsx +71 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/StreamingPreview.tsx +24 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/index.tsx +156 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OpenAIPrompt.ts +254 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OpenAIPromptUI.tsx +189 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Output.tsx +145 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OutputUI.tsx +118 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerChatTemplate.json +43 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerConnection.ts +260 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocket.tsx +268 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocketReceiverTemplate.json +29 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocketUI.tsx +341 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PlaceholderUI.tsx +72 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/QrCode.ts +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/QrCodeUI.tsx +197 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Select.ts +40 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SelectUI.tsx +41 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ServiceBase.ts +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SetRuntimeVariable.ts +65 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SetRuntimeVariableUI.tsx +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sign.ts +182 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SignUI.tsx +55 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Smooth.ts +101 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SmoothUI.tsx +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sort.ts +59 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SortUI.tsx +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sound.ts +541 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SoundUI.tsx +183 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpeechSynth.ts +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpeechSynthUI.tsx +108 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Spotify.tsx +111 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyAPI.ts +113 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyComponents.tsx +104 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyUI.tsx +186 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Spread.ts +117 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpreadUI.tsx +38 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sql.ts +448 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SqlInspector.tsx +332 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SqlUI.tsx +226 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/State.ts +149 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Stopper.ts +29 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Switch.tsx +419 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SwitchUI.tsx +171 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TickRuleDelta.ts +101 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TickRuleDeltaUI.tsx +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Timeline.ts +709 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/TimelinePanel.tsx +79 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/TimelineRows.tsx +574 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/Transport.tsx +141 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/index.tsx +119 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/model.ts +425 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/parts.tsx +708 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Timer.ts +15 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimerUI.tsx +213 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TracksUI.tsx +81 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TriggerPad.ts +108 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TriggerPadUI.tsx +160 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/UuidGenerator.ts +74 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/UuidGeneratorUI.tsx +59 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WaveformDisplay.tsx +104 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WebsocketClient.ts +114 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WebsocketClientUI.tsx +42 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WorkflowBoardBuilder.ts +499 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WorkflowBoardBuilderUI.tsx +521 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/XYPad.ts +68 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/XYPadUI.tsx +274 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/audio/audioHelper.ts +36 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Batcher.ts +76 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Buffer.ts +142 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Filter.ts +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/GroupBy.ts +101 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Map.ts +543 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/MatchFilter.ts +49 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Monitor.ts +75 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Reduce.ts +74 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Timer.ts +342 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/bundle.js +7375 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/eval.ts +208 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/expressionEval.ts +223 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/helpers.ts +10 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/index.ts +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/camera/videoCapture.ts +78 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/changes-modes.ts +9 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/colorpalette.ts +26 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/DetectionPreview.tsx +76 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/mediapipe.ts +34 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/models/README.md +14 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/models/blaze_face_short_range.tflite +0 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect-modes.ts +7 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/encode-modes.ts +10 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/helpers.ts +300 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/http-client-methods.ts +10 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-database.ts +594 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-dump.ts +192 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-inspect.ts +130 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-inspector.css +155 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-modes.ts +27 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/TESTING_APPROACH.md +169 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/__snapshots__/service-ui.future-enhancements.test.tsx.snap +5 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/audio-input-permissions.test.ts +42 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/audiolizeLanes.test.ts +70 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/browser-scopes.test.ts +305 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/cache-service.test.ts +53 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/camera-video-capture.test.ts +24 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/changes-service.test.ts +133 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/crypto-secrets.test.ts +196 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/detect-service.test.ts +119 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/download.test.ts +100 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/expressionEval.test.ts +117 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/fetcher-panel.test.tsx +35 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/fetcher-service.test.ts +80 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/filter-service.test.ts +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/github-secrets.test.ts +108 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/group-by-board.test.ts +42 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/hold-service.test.ts +199 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/http-client-service.test.ts +474 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/http-uploader-token-injector.test.ts +152 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/iterator-gather-service.test.ts +277 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/map-service.test.ts +1047 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/map-ui.test.tsx +70 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/nested-rhythm-board.test.ts +311 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/night-groove-board.test.ts +290 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/pad-groove-board.test.ts +230 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/peer-host-discovery.test.ts +77 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/peer-socket-discovery.test.ts +69 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/pipeline-names.test.ts +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/secret-canary.test.ts +121 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-modules.contract.test.ts +229 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.advanced.test.tsx +670 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.future-enhancements.test.tsx +219 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.integration.test.tsx +432 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.smoke.test.tsx +140 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sound-service.test.ts +293 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/spread-service.test.ts +137 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-dump.test.ts +172 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-inspect.test.ts +163 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-inspector.test.tsx +121 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql.test.ts +702 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sub-service-in-block.test.tsx +84 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/switch-service.test.ts +254 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-board.test.ts +218 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-service.test.ts +1002 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-ui-model.test.ts +230 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-ui.test.tsx +371 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timer-service.test.ts +902 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/tracks-service.test.ts +275 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/tracks-ui.test.tsx +126 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/triggerpad-recorder-board.test.ts +100 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/uuid-generator-service.test.ts +214 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/timeline-core.ts +410 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/DefaultWorkflowBoard.ts +90 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/RefinementSession.ts +121 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/SystemPromptCatalog.ts +245 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/catalog/browser-service-specs.json +110 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/catalog/runtime-capabilities.json +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/asset-service.test.ts +73 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/board-persistence.integration.test.tsx +391 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/browser-runtime.identity-binding.test.tsx +81 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/demo-boards.regression.test.tsx +386 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/failed-call-lifecycle.test.ts +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/http-client.integration.test.ts +146 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/nested-run.integration.test.ts +415 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/replay-next.test.ts +391 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/rerun.test.ts +71 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/run-no-input.test.ts +72 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-add.integration.test.tsx +256 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-operations.integration.test.tsx +310 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-processing.integration.test.ts +311 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-removal.integration.test.tsx +454 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-update.identity.integration.test.tsx +145 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/service-operations.integration.test.tsx +511 -0
- package/bundle/hkp-node/src/address.ts +72 -0
- package/bundle/hkp-node/src/assets.ts +582 -0
- package/bundle/hkp-node/src/auth.ts +369 -0
- package/bundle/hkp-node/src/callReport.ts +281 -0
- package/bundle/hkp-node/src/coordinator/auth.ts +85 -0
- package/bundle/hkp-node/src/coordinator/binaryFrame.ts +189 -0
- package/bundle/hkp-node/src/coordinator/boardStore.ts +0 -0
- package/bundle/hkp-node/src/coordinator/bridge.ts +242 -0
- package/bundle/hkp-node/src/coordinator/bridgeProtocol.ts +200 -0
- package/bundle/hkp-node/src/coordinator/coordinator.ts +594 -0
- package/bundle/hkp-node/src/coordinator/facadeAccess.ts +426 -0
- package/bundle/hkp-node/src/coordinator/fileBoardStore.ts +159 -0
- package/bundle/hkp-node/src/coordinator/frameLimits.ts +41 -0
- package/bundle/hkp-node/src/coordinator/index.ts +12 -0
- package/bundle/hkp-node/src/coordinator/join.ts +62 -0
- package/bundle/hkp-node/src/coordinator/logStore.ts +335 -0
- package/bundle/hkp-node/src/coordinator/members.ts +133 -0
- package/bundle/hkp-node/src/coordinator/mount.ts +124 -0
- package/bundle/hkp-node/src/coordinator/participantProtocol.ts +169 -0
- package/bundle/hkp-node/src/coordinator/participants.ts +666 -0
- package/bundle/hkp-node/src/coordinator/router.ts +416 -0
- package/bundle/hkp-node/src/coordinator/session.ts +1610 -0
- package/bundle/hkp-node/src/coordinator/types.ts +95 -0
- package/bundle/hkp-node/src/coordinatorLinks.ts +712 -0
- package/bundle/hkp-node/src/credentialedFetch.ts +122 -0
- package/bundle/hkp-node/src/egress.ts +344 -0
- package/bundle/hkp-node/src/expressionEval.ts +223 -0
- package/bundle/hkp-node/src/index.ts +470 -0
- package/bundle/hkp-node/src/instance.ts +172 -0
- package/bundle/hkp-node/src/mounts.ts +296 -0
- package/bundle/hkp-node/src/origins.ts +201 -0
- package/bundle/hkp-node/src/reset.ts +187 -0
- package/bundle/hkp-node/src/runtime.ts +1345 -0
- package/bundle/hkp-node/src/secrets.ts +292 -0
- package/bundle/hkp-node/src/server.ts +2120 -0
- package/bundle/hkp-node/src/services/asset.ts +150 -0
- package/bundle/hkp-node/src/services/claudeModels.ts +370 -0
- package/bundle/hkp-node/src/services/communication-dispatcher.ts +735 -0
- package/bundle/hkp-node/src/services/conversations.ts +914 -0
- package/bundle/hkp-node/src/services/database.ts +386 -0
- package/bundle/hkp-node/src/services/document-extract.ts +575 -0
- package/bundle/hkp-node/src/services/expression.ts +417 -0
- package/bundle/hkp-node/src/services/fileStore.ts +369 -0
- package/bundle/hkp-node/src/services/filesystem.ts +280 -0
- package/bundle/hkp-node/src/services/gather.ts +335 -0
- package/bundle/hkp-node/src/services/hold.ts +275 -0
- package/bundle/hkp-node/src/services/http-client.ts +525 -0
- package/bundle/hkp-node/src/services/http-server.ts +1222 -0
- package/bundle/hkp-node/src/services/imap-email.ts +627 -0
- package/bundle/hkp-node/src/services/injector.ts +135 -0
- package/bundle/hkp-node/src/services/iterator.ts +203 -0
- package/bundle/hkp-node/src/services/join.ts +226 -0
- package/bundle/hkp-node/src/services/map.ts +632 -0
- package/bundle/hkp-node/src/services/monitor.ts +128 -0
- package/bundle/hkp-node/src/services/nested-pipeline.ts +405 -0
- package/bundle/hkp-node/src/services/peer-server.ts +268 -0
- package/bundle/hkp-node/src/services/queue-store.ts +301 -0
- package/bundle/hkp-node/src/services/queue.ts +316 -0
- package/bundle/hkp-node/src/services/recordStore.ts +341 -0
- package/bundle/hkp-node/src/services/rss-parse.ts +281 -0
- package/bundle/hkp-node/src/services/rss.ts +392 -0
- package/bundle/hkp-node/src/services/smtp-email.ts +398 -0
- package/bundle/hkp-node/src/services/spread.ts +153 -0
- package/bundle/hkp-node/src/services/sql-dump.ts +192 -0
- package/bundle/hkp-node/src/services/sql.ts +429 -0
- package/bundle/hkp-node/src/services/stopper.ts +69 -0
- package/bundle/hkp-node/src/services/storage.ts +332 -0
- package/bundle/hkp-node/src/services/store.ts +609 -0
- package/bundle/hkp-node/src/services/stream.ts +367 -0
- package/bundle/hkp-node/src/services/sub-service.ts +600 -0
- package/bundle/hkp-node/src/services/telegram-listener.ts +263 -0
- package/bundle/hkp-node/src/services/telegram-sender.ts +136 -0
- package/bundle/hkp-node/src/services/text-generation.ts +1249 -0
- package/bundle/hkp-node/src/services/timer.ts +273 -0
- package/bundle/hkp-node/src/services/tracks.ts +381 -0
- package/bundle/hkp-node/src/services/websocket-reader.ts +364 -0
- package/bundle/hkp-node/src/types.ts +465 -0
- package/bundle/hkp-node/src/wsTickets.ts +77 -0
- package/bundle/hkp-node/src/yas.ts +95 -0
- package/bundle/hkp-python/src/hkp/__init__.py +1 -0
- package/bundle/hkp-python/src/hkp/__main__.py +241 -0
- package/bundle/hkp-python/src/hkp/address.py +56 -0
- package/bundle/hkp-python/src/hkp/assets.py +439 -0
- package/bundle/hkp-python/src/hkp/auth.py +252 -0
- package/bundle/hkp-python/src/hkp/binary_frame.py +100 -0
- package/bundle/hkp-python/src/hkp/call_report.py +201 -0
- package/bundle/hkp-python/src/hkp/coordinator_links.py +667 -0
- package/bundle/hkp-python/src/hkp/data.py +118 -0
- package/bundle/hkp-python/src/hkp/egress.py +310 -0
- package/bundle/hkp-python/src/hkp/mount.py +46 -0
- package/bundle/hkp-python/src/hkp/mounts.py +288 -0
- package/bundle/hkp-python/src/hkp/origins.py +169 -0
- package/bundle/hkp-python/src/hkp/runtime.py +1131 -0
- package/bundle/hkp-python/src/hkp/secrets.py +261 -0
- package/bundle/hkp-python/src/hkp/server.py +1714 -0
- package/bundle/hkp-python/src/hkp/services/__init__.py +0 -0
- package/bundle/hkp-python/src/hkp/services/asset.py +123 -0
- package/bundle/hkp-python/src/hkp/services/audio_encode.py +190 -0
- package/bundle/hkp-python/src/hkp/services/expression.py +942 -0
- package/bundle/hkp-python/src/hkp/services/gather.py +266 -0
- package/bundle/hkp-python/src/hkp/services/hold.py +246 -0
- package/bundle/hkp-python/src/hkp/services/http_client.py +444 -0
- package/bundle/hkp-python/src/hkp/services/http_server.py +790 -0
- package/bundle/hkp-python/src/hkp/services/iterator.py +141 -0
- package/bundle/hkp-python/src/hkp/services/join.py +173 -0
- package/bundle/hkp-python/src/hkp/services/map_service.py +452 -0
- package/bundle/hkp-python/src/hkp/services/monitor.py +99 -0
- package/bundle/hkp-python/src/hkp/services/nested_pipeline.py +278 -0
- package/bundle/hkp-python/src/hkp/services/skill_router.py +388 -0
- package/bundle/hkp-python/src/hkp/services/speech_to_text.py +186 -0
- package/bundle/hkp-python/src/hkp/services/spread.py +114 -0
- package/bundle/hkp-python/src/hkp/services/stopper.py +59 -0
- package/bundle/hkp-python/src/hkp/services/sub_service.py +417 -0
- package/bundle/hkp-python/src/hkp/services/text_generation.py +498 -0
- package/bundle/hkp-python/src/hkp/services/text_to_speech.py +237 -0
- package/bundle/hkp-python/src/hkp/services/timer.py +267 -0
- package/bundle/hkp-python/src/hkp/services/tracks.py +251 -0
- package/bundle/hkp-python/src/hkp/types.py +412 -0
- package/bundle/hkp-python/src/hkp/ws_tickets.py +54 -0
- package/bundle/hkp-python/src/hkp/yas.py +192 -0
- package/bundle/hkp-rt/lib/CMakeLists.txt +193 -0
- package/bundle/hkp-rt/lib/hkp-bundles/CMakeLists.txt +55 -0
- package/bundle/hkp-rt/lib/hkp-bundles/plugin_register.cpp +14 -0
- package/bundle/hkp-rt/lib/include/app.h +201 -0
- package/bundle/hkp-rt/lib/include/assets.h +239 -0
- package/bundle/hkp-rt/lib/include/auth.h +167 -0
- package/bundle/hkp-rt/lib/include/call_report.h +197 -0
- package/bundle/hkp-rt/lib/include/coordinator_links.h +127 -0
- package/bundle/hkp-rt/lib/include/discovery_hooks.h +29 -0
- package/bundle/hkp-rt/lib/include/egress.h +379 -0
- package/bundle/hkp-rt/lib/include/files.h +275 -0
- package/bundle/hkp-rt/lib/include/log_entry.h +131 -0
- package/bundle/hkp-rt/lib/include/mounts.h +139 -0
- package/bundle/hkp-rt/lib/include/origins.h +363 -0
- package/bundle/hkp-rt/lib/include/registry.h +119 -0
- package/bundle/hkp-rt/lib/include/secrets.h +414 -0
- package/bundle/hkp-rt/lib/include/server.h +122 -0
- package/bundle/hkp-rt/lib/include/service.h +190 -0
- package/bundle/hkp-rt/lib/include/slot_store.h +41 -0
- package/bundle/hkp-rt/lib/include/standalone_config.h +253 -0
- package/bundle/hkp-rt/lib/include/types/audio.h +3 -0
- package/bundle/hkp-rt/lib/include/types/data.h +152 -0
- package/bundle/hkp-rt/lib/include/types/message.h +56 -0
- package/bundle/hkp-rt/lib/include/types/models.h +154 -0
- package/bundle/hkp-rt/lib/include/types/owns_me.h +19 -0
- package/bundle/hkp-rt/lib/include/types/ringbuffer.h +86 -0
- package/bundle/hkp-rt/lib/include/types/types.h +33 -0
- package/bundle/hkp-rt/lib/include/types/validation.h +205 -0
- package/bundle/hkp-rt/lib/src/address.h +88 -0
- package/bundle/hkp-rt/lib/src/app.cpp +591 -0
- package/bundle/hkp-rt/lib/src/assets.cpp +686 -0
- package/bundle/hkp-rt/lib/src/binary_frame.h +154 -0
- package/bundle/hkp-rt/lib/src/common/expression.h +1481 -0
- package/bundle/hkp-rt/lib/src/common/inja.h +36 -0
- package/bundle/hkp-rt/lib/src/common/link_socket.cpp +437 -0
- package/bundle/hkp-rt/lib/src/common/link_socket.h +97 -0
- package/bundle/hkp-rt/lib/src/common/realtime_wakeup.h +120 -0
- package/bundle/hkp-rt/lib/src/common/stream_broadcast.h +250 -0
- package/bundle/hkp-rt/lib/src/common/string_util.h +31 -0
- package/bundle/hkp-rt/lib/src/common/websocket_client_session.cpp +217 -0
- package/bundle/hkp-rt/lib/src/common/websocket_client_session.h +37 -0
- package/bundle/hkp-rt/lib/src/common/websocket_config.h +119 -0
- package/bundle/hkp-rt/lib/src/common/websocket_protocol.h +32 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server.cpp +82 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server.h +57 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_listener.cpp +115 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_listener.h +35 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_session.cpp +172 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_session.h +53 -0
- package/bundle/hkp-rt/lib/src/common.h +13 -0
- package/bundle/hkp-rt/lib/src/coordinator_links.cpp +1142 -0
- package/bundle/hkp-rt/lib/src/discovery/discovery.cpp +707 -0
- package/bundle/hkp-rt/lib/src/discovery/discovery.h +89 -0
- package/bundle/hkp-rt/lib/src/http/auth.cpp +569 -0
- package/bundle/hkp-rt/lib/src/http/front_door.cpp +518 -0
- package/bundle/hkp-rt/lib/src/http/front_door.h +131 -0
- package/bundle/hkp-rt/lib/src/http/server.cpp +1539 -0
- package/bundle/hkp-rt/lib/src/http/ws_tickets.h +125 -0
- package/bundle/hkp-rt/lib/src/mount.h +63 -0
- package/bundle/hkp-rt/lib/src/process_context.h +335 -0
- package/bundle/hkp-rt/lib/src/process_depth.h +20 -0
- package/bundle/hkp-rt/lib/src/registry.cpp +233 -0
- package/bundle/hkp-rt/lib/src/runtime.cpp +986 -0
- package/bundle/hkp-rt/lib/src/runtime.h +225 -0
- package/bundle/hkp-rt/lib/src/runtime_host.h +180 -0
- package/bundle/hkp-rt/lib/src/service.cpp +315 -0
- package/bundle/hkp-rt/lib/src/services/asset.h +185 -0
- package/bundle/hkp-rt/lib/src/services/audio_encode.cpp +372 -0
- package/bundle/hkp-rt/lib/src/services/audio_encode.h +97 -0
- package/bundle/hkp-rt/lib/src/services/cache.h +362 -0
- package/bundle/hkp-rt/lib/src/services/cache_subservices.h +463 -0
- package/bundle/hkp-rt/lib/src/services/core_audio.cpp +384 -0
- package/bundle/hkp-rt/lib/src/services/core_audio.h +22 -0
- package/bundle/hkp-rt/lib/src/services/core_input.h +337 -0
- package/bundle/hkp-rt/lib/src/services/core_output.h +370 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/detail.cpp +749 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/detail.h +29 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/ffmpeg.cpp +193 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/ffmpeg.h +40 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/types.h +120 -0
- package/bundle/hkp-rt/lib/src/services/fft_service.h +190 -0
- package/bundle/hkp-rt/lib/src/services/filesystem.h +309 -0
- package/bundle/hkp-rt/lib/src/services/filter.h +208 -0
- package/bundle/hkp-rt/lib/src/services/gather.h +272 -0
- package/bundle/hkp-rt/lib/src/services/hold.h +304 -0
- package/bundle/hkp-rt/lib/src/services/http_client.cpp +347 -0
- package/bundle/hkp-rt/lib/src/services/http_client.h +67 -0
- package/bundle/hkp-rt/lib/src/services/http_client_impl.h +45 -0
- package/bundle/hkp-rt/lib/src/services/http_client_template_examples.md +202 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_listener.cpp +130 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_listener.h +33 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server.cpp +351 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server.h +79 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_impl.cpp +230 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_impl.h +64 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_subservices.cpp +950 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_subservices.h +253 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_session.cpp +495 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_session.h +169 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_stream_listener.cpp +270 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_stream_listener.h +104 -0
- package/bundle/hkp-rt/lib/src/services/http_server/request_decode.h +164 -0
- package/bundle/hkp-rt/lib/src/services/if_service.h +210 -0
- package/bundle/hkp-rt/lib/src/services/ifft_service.h +198 -0
- package/bundle/hkp-rt/lib/src/services/injector.h +162 -0
- package/bundle/hkp-rt/lib/src/services/iterator.h +201 -0
- package/bundle/hkp-rt/lib/src/services/map.h +641 -0
- package/bundle/hkp-rt/lib/src/services/monitor.h +161 -0
- package/bundle/hkp-rt/lib/src/services/mp3_frames.h +119 -0
- package/bundle/hkp-rt/lib/src/services/mp4_to_wav.cpp +281 -0
- package/bundle/hkp-rt/lib/src/services/mp4_to_wav.h +263 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_registry.cpp +88 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_registry.h +43 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server.h +163 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_listener.cpp +133 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_listener.h +46 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_session.cpp +165 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_session.h +44 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_ws_session.cpp +184 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_ws_session.h +55 -0
- package/bundle/hkp-rt/lib/src/services/receiver_session.h +172 -0
- package/bundle/hkp-rt/lib/src/services/root_certificates.h +3959 -0
- package/bundle/hkp-rt/lib/src/services/sha256_openssl.h +49 -0
- package/bundle/hkp-rt/lib/src/services/speech_http.h +306 -0
- package/bundle/hkp-rt/lib/src/services/speech_to_text.cpp +595 -0
- package/bundle/hkp-rt/lib/src/services/speech_to_text.h +69 -0
- package/bundle/hkp-rt/lib/src/services/static.cpp +45 -0
- package/bundle/hkp-rt/lib/src/services/static.h +41 -0
- package/bundle/hkp-rt/lib/src/services/stopper.h +72 -0
- package/bundle/hkp-rt/lib/src/services/sub_service.h +286 -0
- package/bundle/hkp-rt/lib/src/services/text_generation.cpp +1132 -0
- package/bundle/hkp-rt/lib/src/services/text_generation.h +71 -0
- package/bundle/hkp-rt/lib/src/services/text_to_speech.cpp +853 -0
- package/bundle/hkp-rt/lib/src/services/text_to_speech.h +97 -0
- package/bundle/hkp-rt/lib/src/services/timer.h +300 -0
- package/bundle/hkp-rt/lib/src/services/tracks.h +397 -0
- package/bundle/hkp-rt/lib/src/services/tts_inflect.cpp +149 -0
- package/bundle/hkp-rt/lib/src/services/tts_inflect.h +82 -0
- package/bundle/hkp-rt/lib/src/services/wav_reader.h +213 -0
- package/bundle/hkp-rt/lib/src/services/websocket_client_service.h +182 -0
- package/bundle/hkp-rt/lib/src/services/websocket_reader.h +148 -0
- package/bundle/hkp-rt/lib/src/services/websocket_server_service.h +196 -0
- package/bundle/hkp-rt/lib/src/services/websocket_writer.cpp +641 -0
- package/bundle/hkp-rt/lib/src/services/websocket_writer.h +117 -0
- package/bundle/hkp-rt/lib/src/sub_runtime.cpp +234 -0
- package/bundle/hkp-rt/lib/src/sub_runtime.h +160 -0
- package/bundle/hkp-rt/lib/src/types/data.cpp +237 -0
- package/bundle/hkp-rt/lib/src/types/message.cpp +148 -0
- package/bundle/hkp-rt/lib/src/types/models.cpp +83 -0
- package/bundle/hkp-rt/lib/src/types/ringbuffer.cpp +336 -0
- package/bundle/hkp-rt/lib/src/uuid.h +12 -0
- package/bundle/readymade-mcp-commits.json +5 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +79 -0
- package/dist/git.d.ts +8 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +53 -0
- package/dist/http.d.ts +17 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +130 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/repository.d.ts +49 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +452 -0
- package/dist/schemas.d.ts +156 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +43 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +83 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/validation.d.ts +9 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +101 -0
- 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
|
+
}
|