fix(docker): ship demo-image plugins as directory bundles#567
fix(docker): ship demo-image plugins as directory bundles#567staging-devin-ai-integration[bot] wants to merge 1 commit into
Conversation
The -demo images copied bare plugin .so files into plugins/native/, but the loader only scans directory bundles (plugins/native/<id>/ with a plugin.yml + the library), so 'skit serve' found no plugins and TTS/STT requests failed with 'node kind not found'. Each plugin builder stage now emits a bundle directory (plugin.yml + .so) and the runtime stage copies bundles instead of bare files. The observability sample's entrypoint shim now passes through bundles shipped by fixed images while still reassembling the layout for older ones, and the speech gateway's Whisper model is configurable via GATEWAY_STT_MODEL/--stt-model with a default matching the model the demo image actually ships (ggml-tiny-q5_1.bin). Fixes #553 Signed-off-by: streamkit-devin <devin@streamkit.dev>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| - kind: plugin::native::whisper | ||
| params: | ||
| model_path: models/ggml-base.en-q5_1.bin | ||
| model_path: %s |
There was a problem hiding this comment.
📝 Info: Unquoted fmt.Sprintf substitution into YAML template is safe for file paths but fragile for arbitrary strings
The model_path: %s template at examples/speech-gateway/cmd/gateway/main.go:58 is substituted via fmt.Sprintf at line 137. Because the value is unquoted YAML, a model path containing YAML-special characters (#, : , [, {, etc.) would produce invalid or misinterpreted YAML. This is not a bug because: (1) the old hardcoded value was also unquoted (model_path: models/ggml-base.en-q5_1.bin), so no regression; (2) the value comes from operator configuration (env var / CLI flag), not HTTP request input; (3) realistic model file paths never contain these characters. Wrapping in quotes (model_path: "%s") would make it more robust but would require escaping embedded quotes. The current approach is consistent with how all other YAML values in both pipeline templates are written.
Was this helpful? React with 👍 or 👎 to provide feedback.
Debug
| defaultSkitURL = "http://127.0.0.1:4545" | ||
| defaultListenAddr = ":8080" | ||
| // Matches the Whisper model bundled in the -demo images. | ||
| defaultSTTModel = "models/ggml-tiny-q5_1.bin" |
There was a problem hiding this comment.
📝 Info: Default STT model changed from base.en to tiny — intentional fix, not a regression
The old hardcoded model path was models/ggml-base.en-q5_1.bin, but the demo Dockerfile (line 173) only ships ggml-tiny-q5_1.bin. The old default would silently fail at pipeline execution time on the demo image. The new default models/ggml-tiny-q5_1.bin correctly matches the bundled model. This is a bug fix for users running the gateway against the demo image.
Was this helpful? React with 👍 or 👎 to provide feedback.
Debug
| for dir in "$SRC"/*/; do | ||
| [ -d "$dir" ] || continue | ||
| id=$(basename "$dir") | ||
| if [ -f "$dir/plugin.yml" ] && ls "$dir"/*.so > /dev/null 2>&1; then | ||
| cp -r "$SRC/$id" "$DST/$id" | ||
| echo "copied plugin bundle: $id" | ||
| fi | ||
| done | ||
|
|
||
| for manifest in /repo-manifests/*/plugin.yml; do | ||
| [ -f "$manifest" ] || continue | ||
| id=$(basename "$(dirname "$manifest")") | ||
| [ -d "$DST/$id" ] && continue |
There was a problem hiding this comment.
📝 Info: entrypoint.sh correctly handles both old and new plugin layouts without conflict
The updated entrypoint.sh first copies any directory bundles from $SRC (lines 21-28), then falls back to assembling bundles from repo manifests for any remaining bare .so files (lines 30-41). The [ -d "$DST/$id" ] && continue guard at line 33 prevents the second loop from overwriting bundles already copied by the first loop. Since $DST is freshly created at line 19, there are no pre-existing directory issues. The cp -r at line 25 creates the destination directory (not copies into it) because $DST/$id doesn't exist yet at that point. For old images with only bare .so files, the first loop finds no directories with plugin.yml, so the second loop handles everything — preserving backward compatibility.
Was this helpful? React with 👍 or 👎 to provide feedback.
Debug
| # IMPORTANT: Use --chown=app:app on every COPY to avoid a bulk `chown -R` later | ||
| # which would duplicate every file in a new layer (~12GB waste). | ||
| COPY --chown=app:app --from=whisper-builder /build/plugins /opt/streamkit/plugins | ||
| COPY --chown=app:app --from=whisper-builder /build/dist /opt/streamkit/plugins |
There was a problem hiding this comment.
📝 Info: First COPY uses /build/dist (full tree) while subsequent COPYs use specific subdirectories
The whisper plugin is copied with COPY --from=whisper-builder /build/dist /opt/streamkit/plugins (line 647), which bootstraps the entire plugins/native/ tree structure. All subsequent plugins use targeted paths like COPY --from=kokoro-builder /build/dist/native/kokoro /opt/streamkit/plugins/native/kokoro (line 651). This asymmetry is intentional: the first COPY creates the native/ directory hierarchy, and subsequent COPYs merge individual plugin directories into it. If whisper were ever removed, the first plugin in the list would need to use the full-tree COPY pattern instead to create the directory structure.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Dockerfile.democopied bare plugin.sofiles intoplugins/native/, but the loader only scans directory bundles (plugins/native/<id>/with aplugin.yml+ the library) — soskit serveon the-demoimage logged "no plugins found" and any TTS/STT request failed with HTTP 500 "node kind not found". Each plugin builder stage now emits a bundle dir (/build/dist/native/<id>/withplugin.yml+.so) and the runtime stage copies bundles. Side benefit: the image no longer ships the whisper plugin's full source tree (the oldCOPY /build/pluginsswept it in).entrypoint.shshim (from docs: local observability stack sample, guide, and skill #552) now passes through bundles shipped by fixed images while still reassembling the layout for the pinnedv0.5.0-demo, so the sample works with both old and fixed images. It can be deleted once the stack pins a fixed tag.ggml-base.en-q5_1.bin(not present in the demo image) is now configurable viaGATEWAY_STT_MODEL/--stt-model, defaulting tomodels/ggml-tiny-q5_1.bin— the model the demo image actually ships. The bundled sample pipelines were already rewritten to that model inside the image by an existing sed step.latest-demo— it lags released versions and predates theplugin.call.*metrics; publishing a new release/demo tag after this merge resolves it naturally.Review & Validation
Local validation (image built from this branch,
docker run -p 4545:4545):skit serveloaded all 11 plugins, each loggingLoaded native plugin from directory bundle(aac-encoder, helsinki, kokoro, matcha, piper, pocket-tts, sensevoice, slint, supertonic, vad, whisper); no "no plugins found" / "Bare plugin file" warnings.POST /api/v1/processwith the bundledkokoro-tts.ymlreturned HTTP 200 with a valid WebM/Opus file.speech_to_text.yml(fed the TTS output re-muxed to Ogg/Opus) returned HTTP 200 with the correct transcription: "Hello from the fixed stream kit demo image."samples/observability/skit/entrypoint.sh+SK_PLUGINS__DIRECTORY=/opt/streamkit/np— all 11 bundles copied through and loaded. The old-image path is unchanged (the pass-through loop only fires for dirs containing bothplugin.ymland a.so, which the old image's stray whisper source dir does not).just lintandjust testpass.Fixes #553
Link to Devin session: https://staging.itsdev.in/sessions/c11dd028bf294fe5a7584109304f3fc0
Requested by: @streamer45
Devin Review
466647e