Skip to content

Codex/audio cpp tts provider#2599

Open
rmusser01 wants to merge 8 commits into
devfrom
codex/audio-cpp-tts-provider
Open

Codex/audio cpp tts provider#2599
rmusser01 wants to merge 8 commits into
devfrom
codex/audio-cpp-tts-provider

Conversation

@rmusser01

@rmusser01 rmusser01 commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • What changed:
    Add initial support for audio.cpp

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d1be626c-a38b-4037-9aaf-33d136ac9227

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/audio-cpp-tts-provider

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add audio.cpp TTS provider with optional managed sidecar, config, and tests

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Register audio_cpp as a first-class TTS provider with namespaced model routing.
• Add audio.cpp adapter/client/config + optional managed sidecar for local audiocpp_server.
• Document setup and provide an installer helper that patches YAML without auto-downloading models.
Diagram

graph TD
  req["/api/v1/audio/speech request"] --> svc["TTSServiceV2"] --> factory["TTSAdapterFactory"] --> adapter["AudioCppTTSAdapter"] --> client["AudioCppClient"] --> server["audiocpp_server"]
  adapter --> sidecar["AudioCppSidecarSupervisor"] --> server
  cfg[("tts_providers_config.yaml")] --> factory
  installer["install_tts_audio_cpp.py"] --> cfg
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Generic OpenAI-compatible HTTP TTS adapter
  • ➕ Less bespoke code if multiple providers share the same OpenAI Speech API surface
  • ➕ Easier to add more OpenAI-style backends via configuration
  • ➖ audio.cpp has provider-specific constraints (loopback-only default, shared-path voice_ref, managed sidecar) that would still require custom policy hooks
  • ➖ Harder to keep security invariants obvious if generalized too early
2. Direct subprocess invocation (no HTTP server)
  • ➕ Avoids needing to manage ports/health checks and reduces network surface area
  • ➕ Could simplify deployment for purely local usage
  • ➖ Tighter coupling to audio.cpp CLI semantics and output formats
  • ➖ Harder to support long-running model residency, /v1/models discovery, and consistent error mapping across backends
3. Containerized sidecar orchestration (Docker/Compose)
  • ➕ More reproducible runtime with clearer GPU/runtime dependencies
  • ➕ Isolates binaries and models more cleanly
  • ➖ Adds operational requirements and complexity (Docker, GPU runtime)
  • ➖ Less suitable for environments that already manage sidecars natively

Recommendation: The PR’s approach (dedicated adapter + small HTTP client + optional managed loopback sidecar, disabled by default) is the best fit for integrating audio.cpp safely without vendoring binaries/models. The custom policy checks (loopback URL validation, request-option allowlisting, reference-audio staging constraints, and env allowlist for the sidecar) are security-critical and would be harder to enforce reliably in a generic adapter at this stage.

Files changed (21) +3887 / -1

Enhancement (4) +880 / -1
adapter_registry.pyRegister 'audio_cpp' provider and namespaced model aliases +11/-1

Register 'audio_cpp' provider and namespaced model aliases

• Adds 'TTSProvider.AUDIO_CPP', wires the default adapter import path, and introduces namespaced model aliases so audio.cpp models don’t hijack existing PocketTTS routing. Also replaces a dict expansion with 'dict.fromkeys' for the existing Chatterbox alias mapping.

tldw_Server_API/app/core/TTS/adapter_registry.py

audio_cpp_adapter.pyImplement AudioCpp TTS adapter behavior and safety checks +439/-0

Implement AudioCpp TTS adapter behavior and safety checks

• Implements a 'TTSAdapter' for audio.cpp with initialization health/model discovery, request validation, allowlisted option passthrough, and response format resolution. Adds safe handling for reference-audio staging (managed/shared-path only) and cleans up artifacts unless configured to retain them.

tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py

audio_cpp_client.pyAdd async HTTP client for audiocpp_server routes +214/-0

Add async HTTP client for audiocpp_server routes

• Adds an 'httpx'-based client for '/health', '/v1/models', and '/v1/audio/speech', including decoding both raw-audio and JSON base64 responses. Maps transport/timeouts into TTS-specific exceptions and sanitizes error bodies to avoid leaking paths/secrets/request text.

tldw_Server_API/app/core/TTS/adapters/audio_cpp_client.py

audio_cpp_config.pyAdd validated config parsing and server-config rendering helpers +216/-0

Add validated config parsing and server-config rendering helpers

• Adds validation for loopback-only base URLs by default, loopback-only managed bind hosts, and request option allowlisting. Renders a constrained server JSON config for managed mode and generates scratch reference-audio paths without using user-provided filenames; enforces path containment under the audio.cpp runtime root.

tldw_Server_API/app/core/TTS/adapters/audio_cpp_config.py

Tests (9) +1099 / -0
empty_config.txtAdd empty config fixture for TTS config manager tests +1/-0

Add empty config fixture for TTS config manager tests

• Adds a minimal '[TTS-Settings]' config fixture used by unit tests that read YAML-only provider configuration.

tldw_Server_API/tests/TTS_NEW/fixtures/empty_config.txt

test_audio_cpp_tts_service.pyIntegration coverage for routing audio.cpp via TTSServiceV2 +172/-0

Integration coverage for routing audio.cpp via TTSServiceV2

• Adds integration tests that ensure the factory routes namespaced audio.cpp models to the correct adapter, respects disabled providers, and generates a streaming response with expected metadata using a fake client.

tldw_Server_API/tests/TTS_NEW/integration/test_audio_cpp_tts_service.py

test_audio_cpp_adapter.pyUnit tests for audio.cpp adapter request/response behavior +228/-0

Unit tests for audio.cpp adapter request/response behavior

• Adds unit tests for adapter capabilities, allowlisted option handling, model namespace stripping, voice catalog behavior, and reference-audio staging constraints.

tldw_Server_API/tests/TTS_NEW/unit/adapters/test_audio_cpp_adapter.py

test_audio_cpp_client.pyUnit tests for audio.cpp HTTP client routes and error sanitation +103/-0

Unit tests for audio.cpp HTTP client routes and error sanitation

• Adds tests verifying route usage, raw audio handling, JSON base64 decoding, sanitized upstream HTTP error content, and transport error mapping.

tldw_Server_API/tests/TTS_NEW/unit/adapters/test_audio_cpp_client.py

test_audio_cpp_config.pyUnit tests for audio.cpp config validation and rendering +136/-0

Unit tests for audio.cpp config validation and rendering

• Adds tests for loopback URL/host validation, request option filtering, managed server-config rendering, path containment enforcement, and scratch reference path generation.

tldw_Server_API/tests/TTS_NEW/unit/adapters/test_audio_cpp_config.py

test_audio_cpp_sidecar_supervisor.pyUnit tests for sidecar supervisor port selection and hardening +215/-0

Unit tests for sidecar supervisor port selection and hardening

• Adds tests for loopback-only binding, port autoselection/config rendering, fixed spawn command shape, and behavior under readiness failures/idle shutdown.

tldw_Server_API/tests/TTS_NEW/unit/adapters/test_audio_cpp_sidecar_supervisor.py

test_audio_cpp_installer.pyUnit tests for audio.cpp installer helper script +156/-0

Unit tests for audio.cpp installer helper script

• Adds tests for repo-local runtime layout generation, YAML patching behavior (disabled-by-default vs enabled), and command builders for clone/configure/build/model install steps.

tldw_Server_API/tests/TTS_NEW/unit/test_audio_cpp_installer.py

test_audio_cpp_registry.pyUnit tests for provider/model alias resolution and registration +32/-0

Unit tests for provider/model alias resolution and registration

• Adds tests ensuring 'audio_cpp' provider aliases resolve correctly, model aliases are namespaced (without stealing 'pocket-tts'), and the default adapter mapping is registered lazily.

tldw_Server_API/tests/TTS_NEW/unit/test_audio_cpp_registry.py

test_audio_cpp_tts_config.pyUnit tests for YAML defaults and format preferences for audio_cpp +56/-0

Unit tests for YAML defaults and format preferences for audio_cpp

• Verifies the new YAML provider block is present and disabled by default, preserves runtime settings under 'extra_params', and restricts format preferences to verified conversion targets.

tldw_Server_API/tests/TTS_NEW/unit/test_audio_cpp_tts_config.py

Documentation (5) +1175 / -0
TTS-SETUP-GUIDE.mdDocument audio.cpp provider setup (external vs managed sidecar) +102/-0

Document audio.cpp provider setup (external vs managed sidecar)

• Adds an 'audio_cpp' setup section describing external-server mode, managed sidecar mode, and safety defaults (loopback URL, explicit model install). Includes usage examples for the new installer helper script.

Docs/STT-TTS/TTS-SETUP-GUIDE.md

2026-07-03-audio-cpp-tts-provider-implementation-plan.mdAdd implementation plan for audio.cpp TTS provider rollout +574/-0

Add implementation plan for audio.cpp TTS provider rollout

• Introduces a detailed step-by-step plan for implementing and verifying the audio.cpp provider integration, including testing and security/ops considerations.

Docs/superpowers/plans/2026-07-03-audio-cpp-tts-provider-implementation-plan.md

2026-07-03-audio-cpp-tts-integration-design.mdAdd design spec for audio.cpp TTS provider integration +312/-0

Add design spec for audio.cpp TTS provider integration

• Adds a design document covering provider registration, configuration schema, managed vs external runtime modes, and a staged path to broader audio.cpp usage beyond TTS.

Docs/superpowers/specs/2026-07-03-audio-cpp-tts-integration-design.md

task-12124 - Design-audio.cpp-TTS-provider-and-setup-integration.mdRecord completed design task for audio.cpp TTS integration +70/-0

Record completed design task for audio.cpp TTS integration

• Adds a backlog task file capturing scope, acceptance criteria, and references for the audio.cpp TTS provider design work.

backlog/tasks/task-12124 - Design-audio.cpp-TTS-provider-and-setup-integration.md

task-12125 - Implement-audio.cpp-TTS-provider-and-setup-integration.mdRecord implementation task, verification notes, and acceptance criteria +117/-0

Record implementation task, verification notes, and acceptance criteria

• Adds an implementation task file documenting staged test-driven rollout, verification commands, and known limitations for the audio.cpp provider integration.

backlog/tasks/task-12125 - Implement-audio.cpp-TTS-provider-and-setup-integration.md

Other (3) +733 / -0
install_tts_audio_cpp.pyAdd admin installer/config patch helper for audio.cpp runtime +350/-0

Add admin installer/config patch helper for audio.cpp runtime

• Adds a CLI helper to patch 'tts_providers_config.yaml' with repo-local paths and managed-sidecar settings, optionally enabling the provider. Provides explicit commands for cloning/building audio.cpp and installing models via upstream tooling (no silent downloads).

Helper_Scripts/install_tts_audio_cpp.py

tts_providers_config.yamlAdd disabled-by-default 'audio_cpp' provider configuration block +54/-0

Add disabled-by-default 'audio_cpp' provider configuration block

• Extends provider priority and format preferences to include 'audio_cpp', and adds a full provider configuration section (managed sidecar settings, request allowlist, voice catalog mapping) while keeping it disabled by default.

tldw_Server_API/Config_Files/tts_providers_config.yaml

audio_cpp_sidecar_supervisor.pyAdd managed sidecar lifecycle supervisor for audiocpp_server +329/-0

Add managed sidecar lifecycle supervisor for audiocpp_server

• Implements an async supervisor that selects a loopback port, writes a generated server config under the runtime root, spawns 'audiocpp_server --config ...', waits for '/health', and supports idle shutdown/backoff after failures. Hardens execution by restricting inherited environment variables to an allowlist and preventing arbitrary argument injection.

tldw_Server_API/app/core/TTS/adapters/audio_cpp_sidecar_supervisor.py

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the audio_cpp text-to-speech (TTS) provider integration, allowing tldw_server to interface with an external or managed audiocpp_server. It includes the core adapter, configuration models, an HTTP client, a managed sidecar supervisor, an installer helper script, and comprehensive unit and integration tests. The review feedback highlights opportunities to improve asynchronous performance by offloading synchronous file I/O operations—such as writing the server configuration and cleaning up reference audio files—to background threads using asyncio.to_thread to avoid blocking the main event loop.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +113 to +114
selected_port = self._select_port()
self._write_server_config(selected_port)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The synchronous method _write_server_config is called directly on the event loop, which blocks execution. Consider making this method asynchronous or offloading the file I/O to a background thread using asyncio.to_thread.

Suggested change
selected_port = self._select_port()
self._write_server_config(selected_port)
selected_port = self._select_port()
await self._write_server_config(selected_port)

Comment on lines +211 to +219

def _write_server_config(self, port: int) -> None:
config = self._audio_cpp_config.render_server_config()
config["host"] = self._host
config["port"] = int(port)
self.server_config_path.parent.mkdir(parents=True, exist_ok=True)
self.server_config_path.write_text(
json.dumps(config, indent=2, sort_keys=True) + "\n",
encoding="utf-8",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent blocking the event loop during file system writes, refactor _write_server_config to perform the synchronous directory creation and file writing inside asyncio.to_thread.

Suggested change
def _write_server_config(self, port: int) -> None:
config = self._audio_cpp_config.render_server_config()
config["host"] = self._host
config["port"] = int(port)
self.server_config_path.parent.mkdir(parents=True, exist_ok=True)
self.server_config_path.write_text(
json.dumps(config, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
async def _write_server_config(self, port: int) -> None:
config = self._audio_cpp_config.render_server_config()
config["host"] = self._host
config["port"] = int(port)
await asyncio.to_thread(self._write_server_config_sync, config)
def _write_server_config_sync(self, config: dict[str, Any]) -> None:
self.server_config_path.parent.mkdir(parents=True, exist_ok=True)
self.server_config_path.write_text(
json.dumps(config, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)

Comment on lines +379 to +383
def _cleanup_reference(self, staged_reference: Path | None) -> None:
if staged_reference is None or self._audio_cpp_config.retain_request_artifacts:
return
with suppress(OSError, ValueError):
staged_reference.unlink(missing_ok=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Deleting the reference audio file synchronously using staged_reference.unlink blocks the event loop. Offload this file system operation to a background thread using asyncio.to_thread or schedule it as a background task.

    def _cleanup_reference(self, staged_reference: Path | None) -> None:
        if staged_reference is None or self._audio_cpp_config.retain_request_artifacts:
            return
        asyncio.create_task(self._cleanup_reference_async(staged_reference))

    async def _cleanup_reference_async(self, staged_reference: Path) -> None:
        with suppress(OSError, ValueError):
            await asyncio.to_thread(staged_reference.unlink, missing_ok=True)

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (6) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 74 rules

Grey Divider


Action required

1. AudioCppTTSAdapter.__init__ missing return type 📘 Rule violation ✧ Quality
Description
AudioCppTTSAdapter.__init__ is missing an explicit return type annotation (-> None). This
violates the project requirement for full type hints and can degrade static analysis and tooling
quality.
Code

tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[R72-83]

+    def __init__(self, config: dict[str, Any] | None = None):
+        super().__init__(config)
+        cfg = config or {}
+        self._audio_cpp_config = AudioCppConfig.from_provider_config(cfg, repo_root=Path.cwd())
+        self.sample_rate = int(cfg.get("sample_rate") or 24000)
+        self.max_text_length = int(cfg.get("max_text_length") or 5000)
+        self._client: Any | None = cfg.get("client") or cfg.get("_client")
+        self._owns_client = self._client is None
+        self._sidecar_supervisor: Any | None = cfg.get("sidecar_supervisor") or cfg.get("_sidecar_supervisor")
+        self._owns_sidecar_supervisor = self._sidecar_supervisor is None
+        self._available_models: list[str] = []
+        self._voices = self._parse_voice_catalog(cfg)
Evidence
The compliance rule requires explicit return type hints for all modified/new functions. The new
__init__ definition has typed parameters but no -> None return annotation.

Rule 380614: Require type hints on function parameters and return values
tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[72-83]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`AudioCppTTSAdapter.__init__` lacks an explicit return type annotation (`-> None`), violating the rule that all functions/methods must have parameter and return type hints.

## Issue Context
Constructors should still declare an explicit return type (`-> None`) per the checklist.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[72-83]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Blocking write_text() in ensure_started 📘 Rule violation ➹ Performance
Description
AudioCppSidecarSupervisor.ensure_started() performs synchronous filesystem I/O via Path.mkdir()
and Path.write_text() while running in an async flow. This can block the event loop and reduce
concurrency under load.
Code

tldw_Server_API/app/core/TTS/adapters/audio_cpp_sidecar_supervisor.py[R212-220]

+    def _write_server_config(self, port: int) -> None:
+        config = self._audio_cpp_config.render_server_config()
+        config["host"] = self._host
+        config["port"] = int(port)
+        self.server_config_path.parent.mkdir(parents=True, exist_ok=True)
+        self.server_config_path.write_text(
+            json.dumps(config, indent=2, sort_keys=True) + "\n",
+            encoding="utf-8",
+        )
Evidence
The async I/O compliance rule forbids blocking I/O inside async-capable code paths.
_write_server_config() writes to disk synchronously and is invoked from the async
ensure_started() path.

Rule 380613: Prefer async I/O over blocking calls in async-capable code paths
tldw_Server_API/app/core/TTS/adapters/audio_cpp_sidecar_supervisor.py[94-127]
tldw_Server_API/app/core/TTS/adapters/audio_cpp_sidecar_supervisor.py[212-220]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An async code path writes to disk synchronously (`mkdir`/`write_text`), which can block the event loop.

## Issue Context
`ensure_started()` is `async` and calls `_write_server_config()`, which currently performs direct filesystem writes.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_sidecar_supervisor.py[94-137]
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_sidecar_supervisor.py[212-220]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. AudioCppClient.__init__ lacks docstring 📘 Rule violation ✧ Quality
Description
AudioCppClient.__init__ has no function docstring as its first statement. This violates the
requirement that all modules/classes/functions (including methods) have non-empty docstrings.
Code

tldw_Server_API/app/core/TTS/adapters/audio_cpp_client.py[R53-67]

+    def __init__(
+        self,
+        *,
+        base_url: str,
+        http_client: httpx.AsyncClient | None = None,
+        timeout: float = 300.0,
+        allow_remote_base_url: bool = False,
+    ) -> None:
+        self.base_url = validate_base_url(
+            base_url,
+            allow_remote_base_url=allow_remote_base_url,
+        )
+        self.timeout = timeout
+        self._http_client = http_client or httpx.AsyncClient(timeout=timeout, trust_env=False)
+        self._owns_client = http_client is None
Evidence
The docstring rule requires every function/method to start with a string literal docstring. In the
new __init__, the first statement is executable code (self.base_url = ...), indicating no
docstring is present.

Rule 224214: Require docstrings for all modules, classes, and functions
tldw_Server_API/app/core/TTS/adapters/audio_cpp_client.py[53-67]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New methods are missing required docstrings.

## Issue Context
The compliance checklist requires docstrings for every function/method, with the docstring as the first statement in the body.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_client.py[53-67]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Extra pytest marker asyncio 📘 Rule violation ▣ Testability
Description
New tests are decorated with more than one marker (e.g., @pytest.mark.integration plus
@pytest.mark.asyncio). This violates the requirement that each test has exactly one of the
approved markers.
Code

tldw_Server_API/tests/TTS_NEW/integration/test_audio_cpp_tts_service.py[R119-121]

+@pytest.mark.integration
+@pytest.mark.asyncio
+async def test_factory_routes_audio_cpp_explicit_provider_and_namespaced_model():
Evidence
The checklist explicitly requires exactly one approved pytest marker per test. The new tests add an
additional asyncio marker alongside unit/integration, making them non-compliant.

Rule 380651: Apply appropriate pytest markers to all tests
tldw_Server_API/tests/TTS_NEW/integration/test_audio_cpp_tts_service.py[119-121]
tldw_Server_API/tests/TTS_NEW/unit/adapters/test_audio_cpp_client.py[13-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Tests include multiple markers, but policy requires exactly one of: `unit`, `integration`, `external_api`, `local_llm_service`.

## Issue Context
Async tests can still be async, but marker usage must comply with the rule (e.g., rely on pytest-asyncio configuration instead of an extra marker, or refactor to keep exactly one approved marker).

## Fix Focus Areas
- tldw_Server_API/tests/TTS_NEW/integration/test_audio_cpp_tts_service.py[119-152]
- tldw_Server_API/tests/TTS_NEW/unit/adapters/test_audio_cpp_client.py[13-16]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. suppress(Exception) hides wait errors 📘 Rule violation ☼ Reliability
Description
The sidecar shutdown path uses contextlib.suppress(Exception) around await process.wait(), which
can silently hide unexpected errors. This can make failures harder to diagnose and violates the rule
against swallowing exceptions without justification/logging.
Code

tldw_Server_API/app/core/TTS/adapters/audio_cpp_sidecar_supervisor.py[R309-317]

+            with contextlib.suppress(Exception):
+                await process.wait()
+        finally:
+            self._clear_process_state(process)
+
+    def _clear_process_state(self, process: asyncio.subprocess.Process | None = None) -> None:
+        if process is not None and self._process is not process:
+            return
+        self._process = None
Evidence
The checklist forbids silently swallowing exceptions. The new code suppresses all exceptions
(Exception) during process.wait() without logging or a justification comment.

Rule 224227: Do not silently swallow exceptions
tldw_Server_API/app/core/TTS/adapters/audio_cpp_sidecar_supervisor.py[309-317]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`contextlib.suppress(Exception)` is used to ignore all exceptions during process shutdown without logging or justification.

## Issue Context
The compliance rule requires exceptions to be logged or re-raised, and any intentional suppression should be narrow and justified.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_sidecar_supervisor.py[309-317]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. _sanitize_error_text line too long 📘 Rule violation ✧ Quality
Description
The _sanitize_error_text function definition exceeds typical PEP 8 line-length limits, introducing
a new formatting violation. This can cause formatter/linter failures in CI.
Code

tldw_Server_API/app/core/TTS/adapters/audio_cpp_client.py[36]

+def _sanitize_error_text(text: str | None, *, redaction_terms: list[str] | None = None) -> str:
Evidence
The PEP 8 compliance rule requires new/changed Python code to conform to line length limits. The
added _sanitize_error_text signature is written as a single long line, likely exceeding the
configured limit.

Rule 380616: Enforce PEP 8 for Python code formatting
tldw_Server_API/app/core/TTS/adapters/audio_cpp_client.py[36-36]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new line exceeds the project PEP 8 line-length limit (function signature).

## Issue Context
The compliance checklist requires no new PEP 8 violations in changed code unless explicitly ignored.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_client.py[36-36]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Voice cloning misadvertised 🐞 Bug ≡ Correctness
Description
AudioCppTTSAdapter reports supports_voice_cloning=True even when voice references are rejected
unless managed mode is enabled or external_voice_reference_mode is shared_path. This can cause
clients to rely on capability discovery and then hit runtime TTSValidationError for voice_reference
requests under the default external configuration.
Code

tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[R141-156]

+    async def get_capabilities(self) -> TTSCapabilities:
+        return TTSCapabilities(
+            provider_name="audio.cpp",
+            supported_languages=_SUPPORTED_LANGUAGES,
+            supported_voices=list(self._voices.values()),
+            supported_formats=self.SUPPORTED_FORMATS,
+            max_text_length=self.max_text_length,
+            supports_streaming=True,
+            supports_voice_cloning=True,
+            supports_emotion_control=False,
+            supports_speech_rate=False,
+            supports_pitch_control=False,
+            supports_volume_control=False,
+            supports_ssml=False,
+            supports_phonemes=False,
+            supports_multi_speaker=False,
Evidence
Capabilities claim voice cloning support unconditionally, but the request path enforces
config-dependent rejection of voice_reference, creating a behavior/capability mismatch.

tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[141-167]
tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[336-347]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`AudioCppTTSAdapter.get_capabilities()` always sets `supports_voice_cloning=True`, but `_stage_reference_audio()` rejects `request.voice_reference` unless `managed` is enabled or `external_voice_reference_mode == "shared_path"`. Capability discovery should reflect the actual configured behavior to avoid clients sending reference audio that will be rejected.

## Issue Context
- Default YAML config sets `extra_params.managed: false` and `external_voice_reference_mode: "disabled"`.
- In that configuration, `voice_reference` requests should be treated as unsupported.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[141-167]
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[336-352]

## Implementation notes
- Compute `supports_voice_cloning = self._audio_cpp_config.managed or self._audio_cpp_config.external_voice_reference_mode == "shared_path"`.
- Optionally override `validate_request()` to fail early with a clear message when `voice_reference` is provided but not supported by current config.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
8. Voice reference file permissions 🐞 Bug ⛨ Security
Description
Voice reference audio is staged via Path.write_bytes without explicitly setting restrictive
permissions, so confidentiality depends on umask/directory permissions until cleanup. On hosts with
permissive defaults or shared runtime directories, this can expose sensitive voice-reference audio
to other local users/processes.
Code

tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[R354-357]

+    @staticmethod
+    def _write_reference_audio_sync(path: Path, audio_bytes: bytes) -> None:
+        path.parent.mkdir(parents=True, exist_ok=True)
+        path.write_bytes(audio_bytes)
Evidence
The adapter stages voice references via Path.write_bytes(); contrasting with another adapter shows
a more restrictive temp-file approach for reference audio materialization.

tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[349-357]
tldw_Server_API/app/core/TTS/adapters/omnivoice_adapter.py[521-534]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`_write_reference_audio_sync()` writes reference audio using `Path.write_bytes()`, which does not guarantee restrictive permissions. Voice reference audio is potentially sensitive and should be written with explicit 0600 permissions (or equivalent) in the shared scratch directory.

## Issue Context
Other adapters (e.g., OmniVoice) materialize reference audio using `tempfile.NamedTemporaryFile(...)`, which is typically created with restrictive permissions by default.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[354-384]

## Implementation notes
- Use a secure creation method:
 - Option A: `tempfile.NamedTemporaryFile(dir=..., prefix=..., suffix=..., delete=False)` in `shared_scratch_dir`, then write bytes.
 - Option B: `os.open(path, os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0o600)` and `os.fdopen` to write bytes.
- Keep returning a filesystem path the sidecar can read.
- Preserve existing cleanup behavior (`unlink`) when `retain_request_artifacts` is false.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +72 to +83
def __init__(self, config: dict[str, Any] | None = None):
super().__init__(config)
cfg = config or {}
self._audio_cpp_config = AudioCppConfig.from_provider_config(cfg, repo_root=Path.cwd())
self.sample_rate = int(cfg.get("sample_rate") or 24000)
self.max_text_length = int(cfg.get("max_text_length") or 5000)
self._client: Any | None = cfg.get("client") or cfg.get("_client")
self._owns_client = self._client is None
self._sidecar_supervisor: Any | None = cfg.get("sidecar_supervisor") or cfg.get("_sidecar_supervisor")
self._owns_sidecar_supervisor = self._sidecar_supervisor is None
self._available_models: list[str] = []
self._voices = self._parse_voice_catalog(cfg)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. audiocppttsadapter.init missing return type 📘 Rule violation ✧ Quality

AudioCppTTSAdapter.__init__ is missing an explicit return type annotation (-> None). This
violates the project requirement for full type hints and can degrade static analysis and tooling
quality.
Agent Prompt
## Issue description
`AudioCppTTSAdapter.__init__` lacks an explicit return type annotation (`-> None`), violating the rule that all functions/methods must have parameter and return type hints.

## Issue Context
Constructors should still declare an explicit return type (`-> None`) per the checklist.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[72-83]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +212 to +220
def _write_server_config(self, port: int) -> None:
config = self._audio_cpp_config.render_server_config()
config["host"] = self._host
config["port"] = int(port)
self.server_config_path.parent.mkdir(parents=True, exist_ok=True)
self.server_config_path.write_text(
json.dumps(config, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Blocking write_text() in ensure_started 📘 Rule violation ➹ Performance

AudioCppSidecarSupervisor.ensure_started() performs synchronous filesystem I/O via Path.mkdir()
and Path.write_text() while running in an async flow. This can block the event loop and reduce
concurrency under load.
Agent Prompt
## Issue description
An async code path writes to disk synchronously (`mkdir`/`write_text`), which can block the event loop.

## Issue Context
`ensure_started()` is `async` and calls `_write_server_config()`, which currently performs direct filesystem writes.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_sidecar_supervisor.py[94-137]
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_sidecar_supervisor.py[212-220]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +309 to +317
with contextlib.suppress(Exception):
await process.wait()
finally:
self._clear_process_state(process)

def _clear_process_state(self, process: asyncio.subprocess.Process | None = None) -> None:
if process is not None and self._process is not process:
return
self._process = None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. suppress(exception) hides wait errors 📘 Rule violation ☼ Reliability

The sidecar shutdown path uses contextlib.suppress(Exception) around await process.wait(), which
can silently hide unexpected errors. This can make failures harder to diagnose and violates the rule
against swallowing exceptions without justification/logging.
Agent Prompt
## Issue description
`contextlib.suppress(Exception)` is used to ignore all exceptions during process shutdown without logging or justification.

## Issue Context
The compliance rule requires exceptions to be logged or re-raised, and any intentional suppression should be narrow and justified.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_sidecar_supervisor.py[309-317]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +53 to +67
def __init__(
self,
*,
base_url: str,
http_client: httpx.AsyncClient | None = None,
timeout: float = 300.0,
allow_remote_base_url: bool = False,
) -> None:
self.base_url = validate_base_url(
base_url,
allow_remote_base_url=allow_remote_base_url,
)
self.timeout = timeout
self._http_client = http_client or httpx.AsyncClient(timeout=timeout, trust_env=False)
self._owns_client = http_client is None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

4. audiocppclient.init lacks docstring 📘 Rule violation ✧ Quality

AudioCppClient.__init__ has no function docstring as its first statement. This violates the
requirement that all modules/classes/functions (including methods) have non-empty docstrings.
Agent Prompt
## Issue description
New methods are missing required docstrings.

## Issue Context
The compliance checklist requires docstrings for every function/method, with the docstring as the first statement in the body.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_client.py[53-67]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +119 to +121
@pytest.mark.integration
@pytest.mark.asyncio
async def test_factory_routes_audio_cpp_explicit_provider_and_namespaced_model():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

5. Extra pytest marker asyncio 📘 Rule violation ▣ Testability

New tests are decorated with more than one marker (e.g., @pytest.mark.integration plus
@pytest.mark.asyncio). This violates the requirement that each test has exactly one of the
approved markers.
Agent Prompt
## Issue description
Tests include multiple markers, but policy requires exactly one of: `unit`, `integration`, `external_api`, `local_llm_service`.

## Issue Context
Async tests can still be async, but marker usage must comply with the rule (e.g., rely on pytest-asyncio configuration instead of an extra marker, or refactor to keep exactly one approved marker).

## Fix Focus Areas
- tldw_Server_API/tests/TTS_NEW/integration/test_audio_cpp_tts_service.py[119-152]
- tldw_Server_API/tests/TTS_NEW/unit/adapters/test_audio_cpp_client.py[13-16]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

metadata: dict[str, Any] = field(default_factory=dict)


def _sanitize_error_text(text: str | None, *, redaction_terms: list[str] | None = None) -> str:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

6. _sanitize_error_text line too long 📘 Rule violation ✧ Quality

The _sanitize_error_text function definition exceeds typical PEP 8 line-length limits, introducing
a new formatting violation. This can cause formatter/linter failures in CI.
Agent Prompt
## Issue description
A new line exceeds the project PEP 8 line-length limit (function signature).

## Issue Context
The compliance checklist requires no new PEP 8 violations in changed code unless explicitly ignored.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_client.py[36-36]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +141 to +156
async def get_capabilities(self) -> TTSCapabilities:
return TTSCapabilities(
provider_name="audio.cpp",
supported_languages=_SUPPORTED_LANGUAGES,
supported_voices=list(self._voices.values()),
supported_formats=self.SUPPORTED_FORMATS,
max_text_length=self.max_text_length,
supports_streaming=True,
supports_voice_cloning=True,
supports_emotion_control=False,
supports_speech_rate=False,
supports_pitch_control=False,
supports_volume_control=False,
supports_ssml=False,
supports_phonemes=False,
supports_multi_speaker=False,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

7. Voice cloning misadvertised 🐞 Bug ≡ Correctness

AudioCppTTSAdapter reports supports_voice_cloning=True even when voice references are rejected
unless managed mode is enabled or external_voice_reference_mode is shared_path. This can cause
clients to rely on capability discovery and then hit runtime TTSValidationError for voice_reference
requests under the default external configuration.
Agent Prompt
## Issue description
`AudioCppTTSAdapter.get_capabilities()` always sets `supports_voice_cloning=True`, but `_stage_reference_audio()` rejects `request.voice_reference` unless `managed` is enabled or `external_voice_reference_mode == "shared_path"`. Capability discovery should reflect the actual configured behavior to avoid clients sending reference audio that will be rejected.

## Issue Context
- Default YAML config sets `extra_params.managed: false` and `external_voice_reference_mode: "disabled"`.
- In that configuration, `voice_reference` requests should be treated as unsupported.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[141-167]
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[336-352]

## Implementation notes
- Compute `supports_voice_cloning = self._audio_cpp_config.managed or self._audio_cpp_config.external_voice_reference_mode == "shared_path"`.
- Optionally override `validate_request()` to fail early with a clear message when `voice_reference` is provided but not supported by current config.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +354 to +357
@staticmethod
def _write_reference_audio_sync(path: Path, audio_bytes: bytes) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(audio_bytes)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

8. Voice reference file permissions 🐞 Bug ⛨ Security

Voice reference audio is staged via Path.write_bytes without explicitly setting restrictive
permissions, so confidentiality depends on umask/directory permissions until cleanup. On hosts with
permissive defaults or shared runtime directories, this can expose sensitive voice-reference audio
to other local users/processes.
Agent Prompt
## Issue description
`_write_reference_audio_sync()` writes reference audio using `Path.write_bytes()`, which does not guarantee restrictive permissions. Voice reference audio is potentially sensitive and should be written with explicit 0600 permissions (or equivalent) in the shared scratch directory.

## Issue Context
Other adapters (e.g., OmniVoice) materialize reference audio using `tempfile.NamedTemporaryFile(...)`, which is typically created with restrictive permissions by default.

## Fix Focus Areas
- tldw_Server_API/app/core/TTS/adapters/audio_cpp_adapter.py[354-384]

## Implementation notes
- Use a secure creation method:
  - Option A: `tempfile.NamedTemporaryFile(dir=..., prefix=..., suffix=..., delete=False)` in `shared_scratch_dir`, then write bytes.
  - Option B: `os.open(path, os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0o600)` and `os.fdopen` to write bytes.
- Keep returning a filesystem path the sidecar can read.
- Preserve existing cleanup behavior (`unlink`) when `retain_request_artifacts` is false.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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