refactor: extract speech stack into standalone voice module [skip-tag]#37
Merged
Conversation
Creates the voice/ module skeleton ahead of the speech-stack split: - voice/go.mod declares module github.com/GizClaw/flowcraft/voice and pins sdk v0.1.12 as the published-version fallback (the workspace resolves to local source for development). - go.work adds ./voice so local edits across sdk, sdkx, and voice are picked up without a release cycle. - .gitignore allowlists voice/** to match the existing whitelist-mode layout used for sdk/sdkx. No source files are moved in this commit; subsequent commits relocate sdk/speech/* and the sdkx STT/TTS providers in atomic steps. Made-with: Cursor
Pure git mv with no content edits — every file is staged as a rename
so blame history follows the move. The original sdk/speech/ directory
is now empty and removed.
Subpackage layout is preserved:
sdk/speech/{audio,detect,endpoint,metrics,preprocess,provider,
stt,tts,vad,webrtc} -> voice/{...}
sdk/speech/{pipeline,session,session_loop,source,sink,error,
voice_profile,provider_observability}.go -> voice/
Imports are NOT yet updated; the tree intentionally fails to build at
this commit so the rename stands alone in history. The next commit
relocates the sdkx STT/TTS providers, after which a single import-
rewrite commit makes the tree compile again.
Made-with: Cursor
Move the two existing provider implementations from sdkx/ to voice/,
nested directly under the interface package they satisfy:
sdkx/stt/bytedance -> voice/stt/bytedance (implements voice/stt.STT)
sdkx/tts/minimax -> voice/tts/minimax (implements voice/tts.TTS)
This collapses the previous sdk/speech (interfaces) + sdkx/{stt,tts}
(impls) split into a single voice module — provider implementations
now live next to the interfaces they implement, mirroring the
sdkx/llm/{openai,anthropic,...} layout where each provider sits next
to the llm.Provider interface.
The empty sdkx/stt/ and sdkx/tts/ directories are removed. Imports
are still stale (sdk/speech/* and sdkx/{stt,tts}/* paths point to
deleted locations); the next commit performs the global import
rewrite that makes the tree compile again.
Made-with: Cursor
… voice/* Mechanical, single-pass perl rewrite across voice/ and examples/: github.com/GizClaw/flowcraft/sdk/speech -> .../voice github.com/GizClaw/flowcraft/sdkx/stt/bytedance -> .../voice/stt/bytedance github.com/GizClaw/flowcraft/sdkx/tts/minimax -> .../voice/tts/minimax 43 files touched, only import paths changed. Identifier-level usage of the speech.* root package symbols (NewPipeline, STT, TTS, etc.) is addressed in the next commit, which also bumps the package declaration from `package speech` to `package voice`. The voice/go.mod still pins sdk v0.1.12 published; go.work resolves all flowcraft.* paths to local source, so the tree begins to compile again from this commit onward (modulo the speech -> voice symbol rename, handled next). Made-with: Cursor
- Bump every `package speech` / `package speech_test` declaration in
the voice/ root directory to `package voice` / `package voice_test`.
- Mechanically rewrite all `speech.Identifier` references to
`voice.Identifier` across voice/ and examples/voice-pipeline/. Match
is anchored to `\bspeech\.[A-Z]` so the Extra-map string literals
("speech.language", "speech.emotion", "speech.volume", "speech.scene")
are preserved — those are wire-format keys consumed by external
agents and must stay byte-for-byte identical.
- Update one stale godoc in voice/stt/bytedance/bytedance.go that still
pointed at `sdk/speech.STT`; correct path is `voice/stt.STT`.
- Update one README path reference in examples/voice-pipeline.
After this commit the workspace builds: `go vet ./...` is green for
sdk, sdkx, and voice. Test runs and go.mod tidies happen in the next
commits.
Made-with: Cursor
Run `go mod tidy` inside voice/ to materialize the full dependency
graph the relocated source pulls in:
Direct requires (other than sdk):
github.com/coder/websocket v1.8.14 (bytedance STT WebSocket)
github.com/pion/webrtc/v4 v4.2.9 (webrtc transport)
github.com/rs/xid v1.6.0 (session IDs)
go.opentelemetry.io/otel/metric v1.40.0 (provider metrics hook)
Indirect closure picks up the full pion/* chain (datachannel, dtls/v3,
ice/v4, interceptor, logging, mdns/v2, randutil, rtcp, rtp, sctp,
sdp/v3, srtp/v3, stun/v3, transport/v4, turn/v4, plus wlynxg/anet) and
the OTel + golang.org/x/{crypto,net,sys,time} closure pion drags in.
`go test ./...` is green for all 13 packages in the voice module
(audio, detect, endpoint, metrics, preprocess, provider, stt,
stt/bytedance, tts, tts/minimax, vad, webrtc, voice root).
gofmt also reordered a handful of import blocks in files touched by
the previous commit to keep `sdk/...` grouped before `voice/...`; that
cosmetic shuffle is folded in here so the prior commit stays focused
on the package rename.
Made-with: Cursor
Now that sdk/speech/* lives in the standalone voice/ module, sdk no longer pulls the pion/* WebRTC stack. `go mod tidy` removes: - direct: github.com/pion/webrtc/v4 - indirect (pion closure): datachannel, dtls/v3, ice/v4, interceptor, logging, mdns/v2, randutil, rtcp, rtp, sctp, sdp/v3, srtp/v3, stun/v3, transport/v4, turn/v4, plus wlynxg/anet - indirect (transitive, no other consumer): golang.org/x/crypto, golang.org/x/net, golang.org/x/time Net: 17 indirect deps and 1 direct dep removed from sdk/go.mod, with the corresponding 21 go.sum lines deleted. `go vet` and `go test ./...` remain green for every sdk subpackage. Downstream impact: anyone importing only sdk/* now gets a much slimmer dependency graph and no longer transitively pays for the WebRTC stack. Made-with: Cursor
Run `go mod tidy` inside sdkx/ now that the bytedance STT and minimax TTS providers live in voice/. Net changes: Direct deps removed (only used by the relocated providers): github.com/coder/websocket (bytedance STT WebSocket transport) github.com/rs/xid (only producer was bytedance) Indirect deps removed: github.com/dlclark/regexp2 Indirect -> direct (already used by sdkx itself, now surfaced correctly): github.com/jackc/pgx/v5 (sdkx/retrieval/postgres) modernc.org/sqlite (sdkx/retrieval/sqlite + recall queue) Indirect closure shifts: jackc/puddle/v2 + oklog/ulid/v2 picked up, pkoukk/tiktoken-go dropped (was indirect via the relocated providers). The sdk dependency is held at v0.1.12 — same pin main has used since the recall/knowledge subpackages landed. The workspace resolves the import to local source for development; production builds rely on the go.work pin until the next sdk tag (and corresponding sdkx bump) is cut. `go vet` and `go test ./...` are green for every sdkx subpackage. Made-with: Cursor
…/sdkx
The previous import-rewrite commit also touched examples/voice-pipeline,
which was a mistake. The example is not part of go.work and consumes
sdk@v0.1.12 + sdkx@v0.1.14 from the module proxy — those tags still
contain sdk/speech and sdkx/{stt,tts} unchanged. Local relocation of
those packages into voice/ has no effect on the proxy artifacts.
Restore the example to its pre-rewrite state so it keeps building
against the published modules. The example will be migrated to the
voice module in a follow-up once voice/v0.1.x is tagged.
Made-with: Cursor
voice/ is part of the in-tree core (depends on local sdk source via go.work), so it joins sdk + sdkx in the workspace pass for vet/test/ fmt/tidy. examples/voice-pipeline stays in MODULES_OFFWORK since it keeps consuming published sdk/sdkx versions from the proxy. Made-with: Cursor
ci.yml
* detect filter: add voice/** path output
* lint: vet + gofmt now include voice
* new test-voice job (Go 1.25 + 1.26, race detector), gated on
sdk/voice/ci paths since voice imports sdk
* ci-pass aggregator waits on test-voice as well
auto-tag.yml
* trigger paths: add voice/**
* per-module skip loop now covers voice (so [skip-tag:voice] works)
* drop the sdk -> sdkx cascade PR step. The previous behaviour was
"sdk tagged -> open chore/bump-sdkx-sdk-vX.Y.Z PR -> on merge
auto-tag sdkx". With voice in the picture, that fan-out gets
noisier, and in practice we already prefer to bump downstream
`require sdk vX.Y.Z` lines by hand inside the same PR that
motivated the sdk bump. Removing the automation also drops the
`pull-requests: write` permission requirement.
* collapse sdkx/voice independent-tag steps into the shared
tag_module helper - they were duplicating its logic
* tag_module no longer writes TAGGED_<MOD> env vars (only consumer
was the cascade step that's now gone)
Made-with: Cursor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Carve
sdk/speechand the WebRTC + provider stack out ofsdkinto anew top-level
voice/module so the heavy media dependencies (pion,codecs, signalling) no longer bleed into the core SDK.
After this PR:
sdkno longer requirespion/webrtc/v4(or its 16 indirect deps),golang.org/x/{crypto,net,time}, etc.sdkxno longer requirescoder/websocket,rs/xid,dlclark/regexp2— the only consumers were the relocated providers.voiceis its own module with a tidy dep tree: pion + protobuf +webrtc bits live here only, and consumers opt in by importing
github.com/GizClaw/flowcraft/voice.sdkandsdkxkeep theirrequire github.com/GizClaw/flowcraft/sdk v0.1.12pin (same asmain); the workspace resolves to local sourceso in-tree development sees the latest changes immediately.
Commit-by-commit
refactor(voice): scaffold standalone voice modulevoice/go.mod,.gitignoreallowlist,go.workupdated touse ./voicerefactor(voice): relocate sdk/speech/* to voice/git mvonly, no editsrefactor(voice): relocate sdkx STT/TTS providers under voice/{stt,tts}/git mvonlyrefactor(voice): rewrite imports …sdk/speech/*->voice/*,sdkx/{stt,tts}/*->voice/{stt,tts}/*inside the moved tree (and nowhere else —examples/voice-pipelineis intentionally untouched)refactor(voice): rename root package speech -> voice*_test.goexternal packages too. Subpackages (audio,webrtc,vad, …) keep their existing namesrefactor(voice): tidy go.mod and add go.sumgo mod tidyforvoice/chore(sdk): drop pion/* and webrtc-only deps after voice extractionsdk/go.mod+go.sumslimmed downchore(sdkx): tidy after voice extraction (keep sdk v0.1.12 pin)sdkx/go.mod+go.sumre-tidy;sdkpin unchangedrefactor(voice): keep examples/voice-pipeline pinned to published sdk/sdkxsdk@v0.1.12 + sdkx@v0.1.14from the proxy, which still ship the originalspeechpackagesbuild(make): include voice module in MODULES_WORKmake vet/test/fmt/tidynow cover voiceci: cover voice module and drop sdk->sdkx/voice cascade automationtest-voicejob (Go 1.25 + 1.26,-race);auto-taglearns aboutvoice/; automated cascade PR (sdk -> sdkx) removed in favour of manual downstream bumpsNotable design points
Module layout choice.
voicesits next tosdk/sdkx, notunder
sdkx. The unified speech surface (interfaces + pipeline +WebRTC transport) and the cloud providers are coupled tightly enough
that splitting them across two modules just to mirror the
sdk/sdkx interface-vs-impl convention would have hurt more than it
helped. Future provider plugins live alongside
bytedance/minimaxinsidevoice/{stt,tts}/.Why keep
sdk v0.1.12in sdkx. This matchesmain. Tighteningthe pin would force a fresh sdk tag right after the pion drop; that
is best done in a follow-up PR (paired with downstream
go gets)rather than mixed into the refactor.
Cascade automation removed.
auto-tag.ymlno longer opens achore/bump-sdkx-sdk-vX.Y.ZPR after every sdk tag. With threemodules in play the fan-out gets noisy, and in practice we already
prefer to bump
require sdk vX.Y.Zlines by hand inside the samePR that motivated the bump. Each module is still tagged
independently when its
*/paths change.[skip-tag]rationale. This PR moves files between modules; thepatch-only auto-tag bump (sdk v0.1.12 -> v0.1.13, sdkx v0.1.14 ->
v0.1.15) would understate the SemVer impact for downstream consumers
who imported
sdk/speechdirectly. Tag manually as part of thev0.2.0 release coordination instead.
voice/v0.1.0not auto-tagged. No priorvoice/v*tag exists,so
auto-tagwill warn and skip until a release coordinator pushesthe first tag manually.
examples/voice-pipeline. Out of
go.work, no replace directive,consumes published sdk/sdkx from the proxy. It will be migrated to
voicein a follow-up aftervoice/v0.1.0is published.Test plan
make ci(vet + test, all 5 module groups) green locallycd voice && go test ./... -race -count=1greencd sdk && go test ./... -race -count=1green (no speech imports leak)cd sdkx && go test ./... -race -count=1green (no speech imports leak)cd examples/voice-pipeline && GOWORK=off go vet ./...greensdk/v0.2.0,sdkx/v0.2.0,voice/v0.1.0examples/voice-pipelineontovoiceMade with Cursor