Skip to content

refactor: extract speech stack into standalone voice module [skip-tag]#37

Merged
lIang70 merged 11 commits into
mainfrom
refactor/voice-module
Apr 23, 2026
Merged

refactor: extract speech stack into standalone voice module [skip-tag]#37
lIang70 merged 11 commits into
mainfrom
refactor/voice-module

Conversation

@lIang70

@lIang70 lIang70 commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Carve sdk/speech and the WebRTC + provider stack out of sdk into a
new top-level voice/ module so the heavy media dependencies (pion,
codecs, signalling) no longer bleed into the core SDK.

sdk/speech/*               -> voice/*
sdkx/stt/bytedance/*       -> voice/stt/bytedance/*
sdkx/tts/minimax/*         -> voice/tts/minimax/*

After this PR:

  • sdk no longer requires pion/webrtc/v4 (or its 16 indirect deps),
    golang.org/x/{crypto,net,time}, etc.
  • sdkx no longer requires coder/websocket, rs/xid,
    dlclark/regexp2 — the only consumers were the relocated providers.
  • voice is 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.

sdk and sdkx keep their require github.com/GizClaw/flowcraft/sdk v0.1.12 pin (same as main); the workspace resolves to local source
so in-tree development sees the latest changes immediately.

Commit-by-commit

# Commit What
1 refactor(voice): scaffold standalone voice module New voice/go.mod, .gitignore allowlist, go.work updated to use ./voice
2 refactor(voice): relocate sdk/speech/* to voice/ git mv only, no edits
3 refactor(voice): relocate sdkx STT/TTS providers under voice/{stt,tts}/ git mv only
4 refactor(voice): rewrite imports … sdk/speech/* -> voice/*, sdkx/{stt,tts}/* -> voice/{stt,tts}/* inside the moved tree (and nowhere else — examples/voice-pipeline is intentionally untouched)
5 refactor(voice): rename root package speech -> voice Root package decl + identifier renames; *_test.go external packages too. Subpackages (audio, webrtc, vad, …) keep their existing names
6 refactor(voice): tidy go.mod and add go.sum go mod tidy for voice/
7 chore(sdk): drop pion/* and webrtc-only deps after voice extraction sdk/go.mod + go.sum slimmed down
8 chore(sdkx): tidy after voice extraction (keep sdk v0.1.12 pin) sdkx/go.mod + go.sum re-tidy; sdk pin unchanged
9 refactor(voice): keep examples/voice-pipeline pinned to published sdk/sdkx Restore the example that commit 4 had wrongly rewritten — it consumes published sdk@v0.1.12 + sdkx@v0.1.14 from the proxy, which still ship the original speech packages
10 build(make): include voice module in MODULES_WORK make vet/test/fmt/tidy now cover voice
11 ci: cover voice module and drop sdk->sdkx/voice cascade automation New test-voice job (Go 1.25 + 1.26, -race); auto-tag learns about voice/; automated cascade PR (sdk -> sdkx) removed in favour of manual downstream bumps

Notable design points

  • Module layout choice. voice sits next to sdk / sdkx, not
    under 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 /
    minimax inside voice/{stt,tts}/.

  • Why keep sdk v0.1.12 in sdkx. This matches main. Tightening
    the 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.yml no longer opens a
    chore/bump-sdkx-sdk-vX.Y.Z PR after every sdk tag. With three
    modules in play the fan-out gets noisy, and in practice we already
    prefer to bump require sdk vX.Y.Z lines by hand inside the same
    PR that motivated the bump. Each module is still tagged
    independently when its */ paths change.

  • [skip-tag] rationale. This PR moves files between modules; the
    patch-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/speech directly. Tag manually as part of the
    v0.2.0 release coordination instead.

  • voice/v0.1.0 not auto-tagged. No prior voice/v* tag exists,
    so auto-tag will warn and skip until a release coordinator pushes
    the 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
    voice in a follow-up after voice/v0.1.0 is published.

Test plan

  • make ci (vet + test, all 5 module groups) green locally
  • cd voice && go test ./... -race -count=1 green
  • cd sdk && go test ./... -race -count=1 green (no speech imports leak)
  • cd sdkx && go test ./... -race -count=1 green (no speech imports leak)
  • cd examples/voice-pipeline && GOWORK=off go vet ./... green
  • CI (Go 1.25 + 1.26 matrix on sdk/sdkx/voice) green on PR
  • After merge, manually tag sdk/v0.2.0, sdkx/v0.2.0, voice/v0.1.0
  • Follow-up PR to migrate examples/voice-pipeline onto voice

Made with Cursor

lIang70 added 11 commits April 24, 2026 00:52
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
@lIang70
lIang70 merged commit 65b6707 into main Apr 23, 2026
9 checks passed
@lIang70
lIang70 deleted the refactor/voice-module branch April 23, 2026 17:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant