Skip to content

feat(voice): pin remote voice + reach Cloudflare fish-speech [spec 02]#22

Open
wine-fall wants to merge 3 commits into
mainfrom
zachg-0710--remote-tts-harden
Open

feat(voice): pin remote voice + reach Cloudflare fish-speech [spec 02]#22
wine-fall wants to merge 3 commits into
mainfrom
zachg-0710--remote-tts-harden

Conversation

@wine-fall

Copy link
Copy Markdown
Owner

What & why

The remote off-machine TTS backend (PR #21) was correct against a bare
fish-speech server, but did not work against the hosted, Cloudflare-fronted
endpoint (fish-speech.opuslab.ai), and every call sampled a fresh voice.
This hardens it for real use.

Implements specs/spec02/02-voice-provider.md (§3.6, amended).

Changes

  • Reach the Cloudflare-fronted server (fix): urllib's default
    Python-urllib/* User-Agent is 403'd by the server's bot rule. Send a named
    User-Agent: murmur instead. Verified end-to-end against the live endpoint
    (was 403 → now 200 wav).
  • Pin the voice (feat): fish-speech has no preset voices, so without a
    reference each /v1/tts call samples a new timbre. MURMUR_TTS_SEED fixes
    it; threaded config → build_voice → provider → payload, mirroring the
    existing reference_id/api_key path. Confirmed deterministic (identical
    bytes across calls) live.
  • make dev-remote (chore): loads a gitignored .env and runs the dev
    flow forcing the off-machine voice; exits early with a helpful .env
    template when no endpoint is configured (before the slow install/preflight).
    .env is gitignored.

Review fixes (in this change)

Closing peer review surfaced three real issues, each fixed + regression-tested:

  • A non-numeric MURMUR_TTS_SEED warned + degrades to unpinned instead of
    aborting every Config() (it would have crashed spark/stub too).
  • The endpoint URL is strip()ed so a CRLF/whitespace .env value can't
    corrupt the host.
  • Dropped the inert VOICE=remote from the .env template (the target forces
    it via CLI; leaving it would hijack a plain make dev after a manual source).

Testing

  • uv run pytest — all green (216), incl. new regression tests for the seed
    parse, URL strip, User-Agent, and seed threading.
  • pre-commit run --all-files — source-language + pyright pass.
  • Live acceptance against fish-speech.opuslab.ai over WARP: real /v1/tts
    returns wav; seed pinning deterministic; dev-remote missing-.env guard.

AI coding brief

  • Original request: "Read the fish-speech.opuslab.ai doc, analyze how to
    wire the interface in, look at the latest PR first." The goal was to get
    murmur's companion voice onto a heavier, higher-quality off-machine TTS.
  • Manual interventions: the user chose WARP (not a service token) for
    Cloudflare Access; asked for a fixed seed rather than a registered reference;
    asked for a make dev-remote convenience target; and explicitly pushed for
    the missing-.env case to be handled thoroughly (which surfaced the guard
    and, via review, the crash-all seed bug).
  • Retro: the doc was behind Cloudflare Access — stating up front "this
    endpoint is WARP/Access-gated; here's a service token or I'll enable WARP"
    would have saved a round of auth debugging. When adding a config knob that
    only one code path uses, decide its failure mode (ignore vs abort) at design
    time, not in review.

Peer review (codex gpt-5.5): skipped — workspace out of credits; fell back to
the code-review skill (high effort): 3 findings applied, 5 dismissed.

🤖 Generated with Claude Code

wine-fall and others added 3 commits July 10, 2026 14:48
Make the remote fish-speech backend usable against the hosted, Cloudflare-
fronted endpoint and give it a stable voice:

- Named User-Agent: urllib's default "Python-urllib/*" UA is 403'd by the
  server's bot rule; send "murmur" instead.
- MURMUR_TTS_SEED pins the sampled timbre (fish-speech has no preset voices, so
  each /v1/tts call otherwise samples a new voice). Threaded config ->
  build_voice -> provider -> payload, mirroring reference_id/api_key.
- Robustness (from review): a non-numeric seed warns + degrades to unpinned
  instead of aborting every Config() (incl. spark/stub); the endpoint URL is
  stripped so a CRLF/whitespace .env value cannot corrupt the host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- `make dev-remote` loads a gitignored .env and runs the dev flow forcing the
  off-machine HTTP voice; when no endpoint is configured it exits early with a
  helpful .env template (before the slow install/preflight).
- .gitignore: .env (machine-local endpoint config, never committed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the real Cloudflare-fronted host with a placeholder in the
dev-remote template and drop the operator name from a code comment;
the actual endpoint lives only in the gitignored .env.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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